Compare commits
No files matched your search
@@ -33,12 +33,6 @@ slow-timeout = { period = "60s" }
|
||||
[profile.ci.junit]
|
||||
path = "junit.xml"
|
||||
|
||||
# Bound the whole test process, including Vulkan host initialization and teardown.
|
||||
# Cold runs can warm multiple pipelines before the first image starts rendering.
|
||||
[[profile.ci.overrides]]
|
||||
filter = "package(=solidcraft-ui-egui) & test(=render_workspace::tests::progressive_image_controls_route_to_the_registered_host)"
|
||||
slow-timeout = { period = "60s", terminate-after = 20 }
|
||||
|
||||
# The action matrix sample runs its designs in parallel workers (ACTION_MATRIX_JOBS, set to match in
|
||||
# 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),
|
||||
|
||||
@@ -3,13 +3,13 @@
|
||||
SolidCraft is a clean-room, pure-Rust parametric 3D CAD app (sketch solver, feature timeline, B-rep kernel, an egui UI that agents drive). This file is a router: the hard rules, then the skill to read for your task. Humans: CONTRIBUTING.md.
|
||||
|
||||
## Start here
|
||||
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Local `plan/` notes are optional.
|
||||
Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelled `help wanted`, `good first issue`, `needs-triage`; architecture in `docs/architecture.md`. Act when the code, docs and rules settle a question; ask only when it changes what users see. Local `plan/` notes are optional.
|
||||
|
||||
## Hard rules (what enforces each)
|
||||
- Independent: never copy another product's code, icons, text or templates; never drive or take data from commercial CAD. `skills/solidcraft-ground-rules/SKILL.md`.
|
||||
- No GPL/LGPL/AGPL code, not even reading it (OpenCASCADE, FreeCAD, SolveSpace, CadQuery, LibreCAD, QCAD): `cargo xtask licences --check`.
|
||||
- Every asset is original or openly licensed with a row in ATTRIBUTION.md; read `assets/AGENTS.md` before icon work: `cargo xtask assets`, `cargo xtask icons`.
|
||||
- Never crash: no unwrap/expect/panic/unsafe in non-test code, errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
|
||||
- Never crash: no unwrap/expect/panic/unsafe in non-test code (unsafe allowlist in crash-safety), errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
|
||||
- Model decisions (what an operation does, which bodies it touches, which face or edge a point or name means, depths, nearness) use exact geometry; a mesh is for display, export, previews and mesh bodies (`xtask/mesh-decisions.txt` only shrinks): `cargo xtask mesh-decisions`.
|
||||
- New kernel cases go to solidcore: `cargo xtask lists`.
|
||||
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
|
||||
@@ -21,14 +21,12 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
|
||||
- A design-format change is ONE new file in `crates/doc/src/format/pending/`: `scripts/test-format-pending.sh`.
|
||||
- A command has input and output schemas (will become required; `schema/unconverted.txt` only shrinks): `skills/solidcraft-commands/SKILL.md`.
|
||||
- New UI strings go in `assets/locales/pending/<slug>.json`, ATTRIBUTION rows in `attribution/pending/<slug>.md`: `cargo xtask lists`.
|
||||
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line: `pr-checks.yml`.
|
||||
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line, and no second PR for an issue a lower-numbered one closes: `pr-checks.yml`.
|
||||
- A regression test goes in the area's existing test file, never a per-bug file (#3226).
|
||||
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR: `skills/solidcraft-contributing/SKILL.md`.
|
||||
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR, never force-push `main`, no pushes just to catch up with `main`: `skills/solidcraft-contributing/SKILL.md`.
|
||||
- A PR's title and first sentence make sense to someone who never saw your chat or the issue: `skills/solidcraft-contributing/SKILL.md`.
|
||||
- Code, comments, commit messages and docs are in English, always: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
|
||||
- Independent work runs in parallel, dependent work stays sequential: `skills/solidcraft-contributing/SKILL.md` ("Working as an agent").
|
||||
- Show observable changes with real screenshots or a recording, captured yourself.
|
||||
- Run `cargo xtask ci` before every commit. `cargo xtask skills` keeps this file and `skills/` honest.
|
||||
- Run `cargo xtask ci` before every commit, without switching branches while it runs. `cargo xtask skills` keeps this file and `skills/` honest.
|
||||
|
||||
## Working on X: read the skill first
|
||||
| Working on | Read |
|
||||
|
||||
@@ -82,6 +82,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/fonts/ibm-plex-mono/IBMPlexMono-Regular.ttf | IBM Corp. (IBM Plex Mono) | https://github.com/IBM/plex (via google/fonts), unmodified; licence text in assets/fonts/ibm-plex-mono/OFL.txt; the interface monospace font | SIL OFL 1.1 |
|
||||
| assets/fonts/ibm-plex-sans/IBMPlexSans-Variable.ttf | IBM Corp. (IBM Plex Sans, variable wdth and wght) | https://github.com/IBM/plex (via google/fonts), unmodified; licence text in assets/fonts/ibm-plex-sans/OFL.txt; the default interface font | SIL OFL 1.1 |
|
||||
| docs/book/src/generated/tool-preview.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/tool-preview.json`; gitignored) | MIT OR Apache-2.0 |
|
||||
| assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
|
||||
| assets/fonts/biz-udp-gothic/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in assets/fonts/biz-udp-gothic/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
|
||||
| assets/fonts/craft-ui-sc/CraftUI-SC.otf | Adobe / Google Inc. (Noto Sans SC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/SC Regular, glyph-subset for assets/locales/zh.json and renamed Craft UI SC (OFL reserved-name); licence text in assets/fonts/craft-ui-sc/OFL.txt; web UI Simplified Chinese | SIL OFL 1.1 |
|
||||
| assets/fonts/craft-ui-tc/CraftUI-TC.otf | Adobe / Google Inc. (Noto Sans TC); SolidCraft subset | https://github.com/notofonts/noto-cjk Sans/SubsetOTF/TC Regular, glyph-subset for assets/locales/zh-TW.json and renamed Craft UI TC (OFL reserved-name); licence text in assets/fonts/craft-ui-tc/OFL.txt; web UI Traditional Chinese | SIL OFL 1.1 |
|
||||
@@ -146,6 +147,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/fonts/craft-ui-tc/OFL.txt | Adobe / Google Inc. (Noto CJK); SolidCraft header | Accompanying OFL for CraftUI-TC.otf | SIL OFL 1.1 |
|
||||
| assets/fonts/ibm-plex-mono/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||
| assets/fonts/ibm-plex-sans/OFL.txt | IBM Corp. | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||
| assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||
| assets/icons/app/linux/exports/hicolor/128x128/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/16x16/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/24x24/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
@@ -213,7 +215,6 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/styles/ui/themes/one-dark.json | SolidCraft contributors | original SolidCraft theme file with colours adapted from the One Dark palette (Atom One Dark by GitHub Atom, https://github.com/atom/atom, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/tokyo-night.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Tokyo Night by enkia, https://github.com/enkia/tokyo-night-vscode-theme, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/solarized-light.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/solarized-dark.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Solarized by Ethan Schoonover, https://github.com/altercation/solarized, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/nord.json | SolidCraft contributors | original SolidCraft theme file adapting the colours of a published palette (Nord by Sven Greb and the Nord contributors, https://github.com/nordtheme/nord, MIT) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/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 |
|
||||
@@ -316,6 +317,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/model/circle_ttt.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/coil.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/colored_components.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/colored_components_off.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/combine.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/component.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
|
||||
@@ -785,63 +787,6 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
|
||||
| assets/locales/pending/theme-blur-ui.json | SolidCraft contributors | Original blur controls, notices and translations | MIT OR Apache-2.0 |
|
||||
| crates/ui-egui/tests/fixtures/theme-blur.json | SolidCraft contributors | Original theme for blur option scenarios and manager review | MIT OR Apache-2.0 |
|
||||
| assets/locales/ka.json | David Machakhelidze (devius) | Original Georgian translation of SolidCraft's English catalog, written with Claude Code from the meaning of each English string; terminology and doubtful strings decided by a native Georgian speaker; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark.svg | SolidCraft contributors | static SolidCraft mark: the five faces and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg (the final frame of assets/styles/web/loading-logo.svg), cropped to the mark's bounds; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-mono-black.svg | SolidCraft contributors | one-colour (black) version of assets/branding/logo/solidcraft-mark.svg, same paths; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-mono-white.svg | SolidCraft contributors | one-colour (white) version of assets/branding/logo/solidcraft-mark.svg, same paths; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-512.png | SolidCraft contributors | transparent 512 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-1024.png | SolidCraft contributors | transparent 1024 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-2048.png | SolidCraft contributors | transparent 2048 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-wordmark.svg | SolidCraft contributors | the name SolidCraft set like the loading logo's wordmark (bold, +0.02 em tracking, #15499e) in IBM Plex Sans Bold from assets/fonts/ibm-plex-sans (SIL OFL 1.1, IBM Corp.), outlined to paths; no font file is embedded | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-lockup-horizontal.svg | SolidCraft contributors | assets/branding/logo/solidcraft-mark.svg beside assets/branding/logo/solidcraft-wordmark.svg, in the proportions of assets/branding/web/source/og.png; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/onyx.json | SolidCraft contributors | original Onyx stone-grey palette, option overlays and semantic icon-role mapping; existing SolidCraft artwork is unchanged | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/ui-tour/window.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`solidcraft --sample`, default dark theme, software renderer, 1280x800) with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/ui-tour/navigate.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (`--sample`, default dark theme, software renderer) orbiting, panning and zooming, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/sketch-tool-hints.json | carlymx | Original sketch tool hints and engine error translations | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/primitive-pick-field-tips.json | SolidCraft contributors | Original primitive pick-field tooltip translations | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/ribbon-panel-hover.json | SolidCraft contributors | Original ribbon panel menu preference labels and translations | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/box-insert-designer.gif | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (2.5× playback, 800 px wide) | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/box-insert-designer.mp4 | Shawn Marx | original recording made with BoxInsertDesigner's own Motion export (its three.js viewer) of its example game `examples/ccg-example` (https://github.com/ShawnMarx/BoxInsertDesigner): a card game box insert whose trays were built by SolidCraft through `solidcraft-cli`; the published card, board and box art in it is pixelated beyond recognition, and no logos or game names are shown (1280×720) | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/agent-http-consent.json | SolidCraft contributors | Original endpoint-specific HTTP consent messages and translations | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/ground-shadow-placement.json | SolidCraft contributors | Original ground-shadow placement preference messages and translations | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/assemblies/components.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making components from two original hinge-leaf bodies, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/assemblies/joint.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) creating a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/assemblies/drag.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dragging a jointed hinge leaf, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/assemblies/drive.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) driving a revolute joint between two hinge leaves, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/extrude.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) extruding a sketch's profile, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/shell.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) shelling a block, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/fillet.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) filleting four edges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/edit.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) editing a feature from the timeline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/hole.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) placing a hole, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/features/pattern.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) patterning a hole around an axis, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/parameters/dialog.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer) showing the Parameters dialog, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/files/menu.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`--sample`, default dark theme, software renderer) showing the File menu, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/getting-started/start.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing the start page with its built-in sample thumbnails, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/getting-started/sample.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) opening the built-in Sample Plate from the start page, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sheet-metal/base.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) making a sheet metal base flange, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sheet-metal/flange.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) adding sheet metal edge flanges, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sheet-metal/flat.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) showing a sheet metal flat pattern, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/create.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) starting a sketch on the Top plane, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/draw.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) drawing a rectangle and a circle with typed values, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/dimension.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) dimensioning a sketch until it is fully constrained, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/parts.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (default dark theme, software renderer, 1280x800) showing a fully constrained sketch, with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/offset.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) offsetting a sketch outline, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/sketching/trim.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (default dark theme, software renderer) trimming a notch in a sketch, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| assets/locales/ca.json | carlymx | Catalan translation of the English catalog, contributed upstream | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/sketch-mirror-auto-line.json | SolidCraft contributors | Original Mirror preference label and translations | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-01-extrude5-edge-frame.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-04-preview-outer-l11.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-05-preview-inner-l8.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-06-result-outer-120.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-07-preview-360.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-08-cancelled.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-13-colliding-id.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-14-surface-l1.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/demo-15-surface-result.png | SamD98 | screenshot of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| docs/screenshots/7452/revolve-selected-edge.mp4 | SamD98 | screen recording of the actual SolidCraft desktop UI showing the revolve axis pick on the Cat Post Topper repro design (docs/screenshots/7452/revolve-edge-frame.solidcraft), for #7452 | MIT OR Apache-2.0 |
|
||||
| assets/locales/bg.json | Yasen-55 | Original translation | MIT OR Apache-2.0 |
|
||||
| crates/sketch/assets/fonts/liberation-sans/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in crates/sketch/assets/fonts/liberation-sans/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
|
||||
| crates/sketch/assets/fonts/liberation-sans/OFL.txt | Red Hat, Inc.; Liberation Fonts contributors | Unmodified accompanying font licence | SIL OFL 1.1 |
|
||||
| assets/locales/pending/drag-to-zoom.json | SolidCraft contributors | Original drag-to-zoom labels and translations | MIT OR Apache-2.0 |
|
||||
|
||||
## Code adapted from sibling projects
|
||||
|
||||
@@ -893,7 +838,7 @@ into THIRD-PARTY-LICENSES.txt when `main` regenerates it.
|
||||
## FreeCAD and Rhino import library
|
||||
|
||||
FreeCAD `.FCStd` and Rhino `.3dm` files are read with [cadmpeg](https://github.com/cadmpeg/cadmpeg)
|
||||
0.6.7 (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 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
|
||||
@@ -911,10 +856,11 @@ copied into SolidCraft.
|
||||
|
||||
| Directory | Source | Licence | Changes |
|
||||
|---|---|---|---|
|
||||
| vendor/accesskit_macos | accesskit_macos 0.26.3, https://github.com/AccessKit/accesskit (The AccessKit contributors) | MIT OR Apache-2.0 | the subclassing adapter and its tests from 0.27.2: dropping the adapter keeps AppKit's key-value observers (vendor/README.md) |
|
||||
|
||||
## Vendored renderer compatibility fix
|
||||
|
||||
`vendor/egui-wgpu` is egui-wgpu 0.37.0 from https://github.com/emilk/egui
|
||||
`vendor/egui-wgpu` is egui-wgpu 0.36.2 from https://github.com/emilk/egui
|
||||
(MIT OR Apache-2.0; both original licence texts are included). Rust sources and
|
||||
its original `egui.wgsl` shader are retained; no images or other artwork are copied.
|
||||
SolidCraft's narrow patch selects a safe 1x MSAA path on the chosen OpenGL adapter
|
||||
|
||||
@@ -88,8 +88,6 @@ The [architecture page](docs/architecture.md) has the layer table. The short ver
|
||||
|
||||
- `crates/geom`: vectors, planes, meshes, measures.
|
||||
- `crates/sketch`: our own constraint solver, plus profile finding.
|
||||
- `crates/dynamics`: multibody dynamics: rigid bodies, joint constraints, gravity and springs,
|
||||
integrated in time (RFC #9719). It depends only on `geom`.
|
||||
- `crates/kernel`: the B-rep kernel boundary. It wraps [truck](https://github.com/ricosjp/truck),
|
||||
and it's the only crate allowed to touch truck. Fillets, chamfers, shell/offset, Delete Face,
|
||||
Split Face, STEP/IGES read and write, and face provenance are our own code here. Patched
|
||||
@@ -279,7 +277,7 @@ mean something else, it needs a format change. **Add one file, and edit nothing
|
||||
change's file, or any `v<N>/` folder: those are fixed once released.
|
||||
|
||||
That's all. The build counts the files: while any are pending it writes the next format number,
|
||||
which no release reads, so a release warns about a design that has your change and keeps what it can't read of it.
|
||||
which no release reads, so a release refuses a design that has your change instead of losing it.
|
||||
A file also lists the pending changes it has (`format_pending`), so a later development build
|
||||
runs only the steps that file is missing. Two pull requests that each add a change merge without
|
||||
a conflict. `crates/doc/src/format/pending/README.md` has an example.
|
||||
@@ -474,16 +472,6 @@ the same area), first resolve the command registry and source conflicts, then ru
|
||||
The generator tests cover stable ordering and additions that leave existing lines
|
||||
unchanged. The initial switch to sorted rows is a one-time larger diff.
|
||||
|
||||
## Pictures in the user guide
|
||||
|
||||
The user guide's screenshots and short clips (`docs/book/src/media/`) are captured from the real
|
||||
app by `examples/demo/doc_shots.py`, one function per figure. Each run starts a fresh app with its
|
||||
own settings, so your preferences never show. When a change makes a figure out of date, run
|
||||
`python3 examples/demo/doc_shots.py <page or shot>` (it needs Xvfb, ffmpeg, Pillow and a release
|
||||
build) and commit the new file. A new figure is a new function there, an ATTRIBUTION row in
|
||||
`attribution/pending/`, and a `<video>` (clips) or image link on the page. Keep a still under
|
||||
about 250 KB and a clip under about 1 MB.
|
||||
|
||||
## Licence
|
||||
|
||||
By contributing you agree that your contribution is licensed as MIT OR Apache-2.0, like the rest
|
||||
|
||||
@@ -4,9 +4,9 @@ version = 4
|
||||
|
||||
[[package]]
|
||||
name = "accesskit"
|
||||
version = "0.25.2"
|
||||
version = "0.24.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4e883d1d28c5fd5568c79ed2d2bd48d9f2fd3945c8974ed0f53690ceddb051be"
|
||||
checksum = "d3b7f7f85a7e5f68090000ed7622545829afd484d210358702ae4cb97dd0c320"
|
||||
dependencies = [
|
||||
"enumn",
|
||||
"serde",
|
||||
@@ -15,12 +15,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_atspi_common"
|
||||
version = "0.21.1"
|
||||
version = "0.18.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24f5baf62e34a4a6c30e3d697e4782f821a7337049c44c3b40576a27dfbb580d"
|
||||
checksum = "1e8c61bee90b42a772d39d06a740207dc71a4e780004ace1db8d99fb1baaa954"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"accesskit_consumer 0.36.0",
|
||||
"atspi-common",
|
||||
"phf",
|
||||
"serde",
|
||||
@@ -29,37 +29,41 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_consumer"
|
||||
version = "0.39.2"
|
||||
version = "0.35.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3da8f27636015191b2264ee1df681503170a632cafd832fdc1edfdd4e3a2fd3"
|
||||
checksum = "53cf47daed85312e763fbf85ceca136e0d7abc68e0a7e12abe11f48172bc3b10"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"hashbrown 0.17.1",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_ios"
|
||||
version = "0.2.2"
|
||||
name = "accesskit_consumer"
|
||||
version = "0.36.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5280724673bbfa8b30a838c013500b719392989be3b97c5a9964062c252c232"
|
||||
checksum = "25e0d7e25d06f4dc21d1774d67146e9e80d6789216cbd4d1e88185b0095dba60"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"hashbrown 0.17.1",
|
||||
"objc2 0.5.3",
|
||||
"objc2-foundation 0.2.2",
|
||||
"objc2-ui-kit 0.2.2",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_consumer"
|
||||
version = "0.38.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5d10a236f96f87d70732e44520046785431ef01d5bcd6b041317bfadd2f88245"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"hashbrown 0.16.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_macos"
|
||||
version = "0.27.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "123828ceeec755b05255e3976cb0852993202de98a0626c97dd8d34246991f84"
|
||||
version = "0.26.3"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"hashbrown 0.17.1",
|
||||
"accesskit_consumer 0.38.0",
|
||||
"hashbrown 0.16.1",
|
||||
"objc2 0.5.3",
|
||||
"objc2-app-kit 0.2.2",
|
||||
"objc2-foundation 0.2.2",
|
||||
@@ -67,9 +71,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_unix"
|
||||
version = "0.24.1"
|
||||
version = "0.21.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5192871228c54f1b0fb4a85cef000e11a3804f25d2165f14afa0daa67f3f6"
|
||||
checksum = "b016ca8db0ea0ea2ceff29a9d6240391492d960716aa471967c00e8cc8cb197c"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_atspi_common",
|
||||
@@ -85,13 +89,13 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_windows"
|
||||
version = "0.35.2"
|
||||
version = "0.32.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "498668b90da9995302a0055aaecad4756932333da2ddef6baee1c00844c430fc"
|
||||
checksum = "eff7009f1a532e917d66970a1e80c965140c6cfbbabbdde3d64e5431e6c78e21"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_consumer",
|
||||
"hashbrown 0.17.1",
|
||||
"accesskit_consumer 0.35.0",
|
||||
"hashbrown 0.16.1",
|
||||
"static_assertions",
|
||||
"windows",
|
||||
"windows-core",
|
||||
@@ -99,12 +103,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "accesskit_winit"
|
||||
version = "0.34.2"
|
||||
version = "0.32.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd4ab4437b520ff03088fbe48676a11e7d44a929535e64b31432c1073e48dbcc"
|
||||
checksum = "1fe9a94394896352cc4660ca2288bd4ef883d83238853c038b44070c8f134313"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"accesskit_ios",
|
||||
"accesskit_macos",
|
||||
"accesskit_unix",
|
||||
"accesskit_windows",
|
||||
@@ -200,7 +203,6 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"windows-sys 0.60.2",
|
||||
"wl-clipboard-rs",
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
@@ -562,9 +564,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-freecad"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fe0ed1935ace2c203b87107a064818dec2bb2ec2f250c4ad121767f3da75d1b"
|
||||
checksum = "3c4a8fa61748cbb3ce80838b00a8cebfe553460583941f5aba6e2b97c16b4d37"
|
||||
dependencies = [
|
||||
"cadmpeg-container",
|
||||
"cadmpeg-core",
|
||||
@@ -578,9 +580,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-rhino"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd2b76bd1d9301c949fbd731fd22cff9d37d10d1f481c754b557c2187bd0171"
|
||||
checksum = "d7af41440d4c1d69e88e612de02cc3c9bad79d03429531e640ac924fef53c6bf"
|
||||
dependencies = [
|
||||
"cadmpeg-container",
|
||||
"cadmpeg-core",
|
||||
@@ -597,9 +599,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-step"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cbce33ed681a28b62d8b01ae64cca2b01f11f31b9a3e5e2455b9ffde915712"
|
||||
checksum = "35e1b6d17cf26b31f59f2edab8ef5c29d2a33216c581fd913f012ad4869bd559"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cadmpeg-container",
|
||||
@@ -614,9 +616,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-container"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03f0d1c22b875294c5fbf4a4738aec8f8c0a4ad035eb28ece404f1dec21f371a"
|
||||
checksum = "5ab7bcef48ac7480b4917820272d85cd2c9533e17904e15b0e4d22f19a6045bd"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"crc32fast",
|
||||
@@ -626,9 +628,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-core"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "795bdc6e2a10ecc0627138874c17e2948a3925cd22c6c6b0c2e4f6621d92f89c"
|
||||
checksum = "e86bb7a40521832504bb1f5953a1c706b2c52aad73bf6d84e1b35e5cb0beae3f"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"memchr",
|
||||
@@ -641,9 +643,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-ir"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5592036516aaf2574b159a86a3d1ae872dc245261de86e4c0ce6773fed00a249"
|
||||
checksum = "b570a1a8c579e407a9fd7bb10fa1900f8827f1d578b8fcb4ea2a056b5f323c72"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cadmpeg-core",
|
||||
@@ -656,9 +658,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-registry"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc163233d5f664337267192848505854de6aeed4843181adf87f58baddcee9a0"
|
||||
checksum = "b2be23469a9bca0b684f1811118fffb581ebd32ded08e6cc8b0716a41f8c1377"
|
||||
dependencies = [
|
||||
"cadmpeg-codec-freecad",
|
||||
"cadmpeg-codec-rhino",
|
||||
@@ -685,18 +687,42 @@ dependencies = [
|
||||
"thiserror 1.0.69",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop"
|
||||
version = "0.14.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "43b32313e937c8721c9d17ee9bf60699a6402b4b7c2cb6ac1bee0ffcaa4b4e5e"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"polling",
|
||||
"rustix 1.1.5",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "95a66a987056935f7efce4ab5668920b5d0dac4a7c99991a67395f13702ddd20"
|
||||
dependencies = [
|
||||
"calloop",
|
||||
"calloop 0.13.0",
|
||||
"rustix 0.38.44",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "calloop-wayland-source"
|
||||
version = "0.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa"
|
||||
dependencies = [
|
||||
"calloop 0.14.5",
|
||||
"rustix 1.1.5",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cbc"
|
||||
version = "0.1.2"
|
||||
@@ -1097,9 +1123,9 @@ checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "ecolor"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31bcbef0d4263a63eb32c4834e5d711613035ce1333468e107d928923e21e6e2"
|
||||
checksum = "0656a7530b9e2841ee7a5f983965bdd9840c32f4012f759cfd44b912cced6b34"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"emath",
|
||||
@@ -1108,9 +1134,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "eframe"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c5642501a65ca657d05f00c643a5c14ff2346cb3818032bfe23d58e1f8e5f84"
|
||||
checksum = "55ad41fac6e3149abb4866e93df37dbd33baa95db525c3368c944584e95a5dc2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1133,7 +1159,6 @@ dependencies = [
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"ron",
|
||||
"send_wrapper",
|
||||
"serde",
|
||||
"static_assertions",
|
||||
"wasm-bindgen",
|
||||
@@ -1146,9 +1171,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bec9144f49b7a28b5f395d3147e325b4fe3e77ba686eac11d1bdf0a7da3f375f"
|
||||
checksum = "dc938cc27cd911415e1e4d151bc56ff9df063d8db081374e87aeb805ea74b2ba"
|
||||
dependencies = [
|
||||
"accesskit",
|
||||
"ahash",
|
||||
@@ -1168,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-wgpu"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1186,34 +1211,32 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "egui-winit"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e4e449f4c4a2a60c7ccb4ba7fa4078417bfe1f9ddc03b5e05ce75748e33a70de"
|
||||
checksum = "98466000559d66ba4db786a30448b01483946add4188e7f10f522a27b4fb89cc"
|
||||
dependencies = [
|
||||
"accesskit_winit",
|
||||
"arboard",
|
||||
"block2 0.6.2",
|
||||
"bytemuck",
|
||||
"egui",
|
||||
"log",
|
||||
"objc2 0.6.5",
|
||||
"objc2-app-kit 0.3.2",
|
||||
"objc2-foundation 0.3.2",
|
||||
"objc2-ui-kit 0.3.2",
|
||||
"profiling",
|
||||
"raw-window-handle",
|
||||
"serde",
|
||||
"smithay-clipboard",
|
||||
"web-time",
|
||||
"webbrowser",
|
||||
"windows-sys 0.61.2",
|
||||
"winit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_glow"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9275ca5cc35a6b7c94e0e8cedf22542f66cb0cf45ed8ab2657c78ff49fec7268"
|
||||
checksum = "606ed4167daf341e5753d703285c4c1d562324b2c07aa8f405cd7d066fe58f3b"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"egui",
|
||||
@@ -1231,9 +1254,9 @@ checksum = "0e9c71c2167ca323c882b99918929403426e2373ea17242ff5653e0d5e1058be"
|
||||
|
||||
[[package]]
|
||||
name = "emath"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e73ce90f54750be8672c02be0743fe47530e6f251e066f0ee2869b59eb297af5"
|
||||
checksum = "52c8d4141cf0f60fb2f7aefaf352d3ffa5171ef0ab09731c64391801e95e48f1"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"serde",
|
||||
@@ -1307,9 +1330,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "epaint"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97bac86ae3dde93fc3951e790951caea739ff39a6c89d909cdaf1d9278dbb1bd"
|
||||
checksum = "a0ec0308dc23130fc1a7d4623a8534c2ed069c6dc84e258ee4f3de99615aa1a3"
|
||||
dependencies = [
|
||||
"ahash",
|
||||
"bytemuck",
|
||||
@@ -1327,16 +1350,15 @@ dependencies = [
|
||||
"skrifa",
|
||||
"smallvec",
|
||||
"unicode-general-category",
|
||||
"unicode-properties",
|
||||
"unicode-segmentation",
|
||||
"vello_cpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "epaint_default_fonts"
|
||||
version = "0.37.0"
|
||||
version = "0.36.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cfc1cc505827f4c23eedc297975a0fd038268d41293f389dd8ff3b574aefab19"
|
||||
checksum = "773fa9c96dd0dbef887e39d0ed6177f141cce4d68e6041df77570aa3702dfa13"
|
||||
|
||||
[[package]]
|
||||
name = "equivalent"
|
||||
@@ -1422,12 +1444,6 @@ version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aedcfb3409746eddb02b9e19ebda1c3394f759a152e48ee875a0844d1b955484"
|
||||
|
||||
[[package]]
|
||||
name = "fixedbitset"
|
||||
version = "0.5.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99"
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.1.10"
|
||||
@@ -1439,12 +1455,6 @@ dependencies = [
|
||||
"zlib-rs",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.2.0"
|
||||
@@ -1641,12 +1651,12 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "glifo"
|
||||
version = "0.3.0"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282a26c1e23de04bdab3e34a21b6f877a479b96737792516b9b8b8f69b6661be"
|
||||
checksum = "ed4a1bb24121291d27230c1b1b44e07d6a9b28cefdb32fe1581dfb84e14f940a"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"foldhash 0.2.0",
|
||||
"foldhash",
|
||||
"hashbrown 0.17.1",
|
||||
"log",
|
||||
"peniko",
|
||||
@@ -1816,15 +1826,6 @@ dependencies = [
|
||||
"smallvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.15.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1"
|
||||
dependencies = [
|
||||
"foldhash 0.1.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.16.1"
|
||||
@@ -1833,7 +1834,7 @@ checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash 0.2.0",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1842,7 +1843,7 @@ version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
||||
dependencies = [
|
||||
"foldhash 0.2.0",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2499,15 +2500,6 @@ version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451"
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "8.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num"
|
||||
version = "0.4.3"
|
||||
@@ -2964,16 +2956,6 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "os_pipe"
|
||||
version = "1.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7d8fae84b431384b68627d0f9b3b1245fcf9f46f6c0e3dc902e9dce64edd1967"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
@@ -3022,22 +3004,11 @@ version = "2.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
|
||||
|
||||
[[package]]
|
||||
name = "petgraph"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455"
|
||||
dependencies = [
|
||||
"fixedbitset",
|
||||
"hashbrown 0.15.5",
|
||||
"indexmap",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "phf"
|
||||
version = "0.14.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "010378780309880b08997fae13be7834dba947d36393bd372f2b1556deb2a2f6"
|
||||
checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf"
|
||||
dependencies = [
|
||||
"phf_macros",
|
||||
"phf_shared",
|
||||
@@ -3046,9 +3017,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_generator"
|
||||
version = "0.14.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aeb62e0959d5a1bebc965f4d15d9e2b7cea002b6b0f5ba8cde6cc26738467100"
|
||||
checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"phf_shared",
|
||||
@@ -3056,9 +3027,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_macros"
|
||||
version = "0.14.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5fa8d0ca26d424d27630da600c6624696e7dec8bf7b3b492b383c5dc49e5e085"
|
||||
checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef"
|
||||
dependencies = [
|
||||
"phf_generator",
|
||||
"phf_shared",
|
||||
@@ -3069,9 +3040,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "phf_shared"
|
||||
version = "0.14.0"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c6fd9027e2d9319be6349febd1db4e8d02aa544921200c9b777720ac34a3aa89"
|
||||
checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266"
|
||||
dependencies = [
|
||||
"siphasher",
|
||||
]
|
||||
@@ -3791,12 +3762,6 @@ version = "1.0.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd"
|
||||
|
||||
[[package]]
|
||||
name = "send_wrapper"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd0b0ec5f1c1ca621c432a25813d8d60c88abe6d3e08a3eb9cf37d97a0fe3d73"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.229"
|
||||
@@ -4021,8 +3986,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3457dea1f0eb631b4034d61d4d8c32074caa6cd1ab2d59f2327bd8461e2c0016"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"calloop",
|
||||
"calloop-wayland-source",
|
||||
"calloop 0.13.0",
|
||||
"calloop-wayland-source 0.3.0",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
@@ -4039,6 +4004,44 @@ dependencies = [
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-client-toolkit"
|
||||
version = "0.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"calloop 0.14.5",
|
||||
"calloop-wayland-source 0.4.1",
|
||||
"cursor-icon",
|
||||
"libc",
|
||||
"log",
|
||||
"memmap2",
|
||||
"rustix 1.1.5",
|
||||
"thiserror 2.0.21",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-csd-frame",
|
||||
"wayland-cursor",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-experimental",
|
||||
"wayland-protocols-misc",
|
||||
"wayland-protocols-wlr",
|
||||
"wayland-scanner",
|
||||
"xkeysym",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smithay-clipboard"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71704c03f739f7745053bde45fa203a46c58d25bc5c4efba1d9a60e9dba81226"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"smithay-client-toolkit 0.20.0",
|
||||
"wayland-backend",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "smol_str"
|
||||
version = "0.2.2"
|
||||
@@ -4150,17 +4153,6 @@ dependencies = [
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-dynamics"
|
||||
version = "0.40.0-rc.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-engine"
|
||||
version = "0.40.0-rc.2"
|
||||
@@ -4227,6 +4219,7 @@ dependencies = [
|
||||
"gethostname",
|
||||
"gltf",
|
||||
"log",
|
||||
"png",
|
||||
"quick-xml",
|
||||
"rustix 1.1.5",
|
||||
"serde",
|
||||
@@ -4237,8 +4230,10 @@ dependencies = [
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-sketch",
|
||||
"solidcraft-testkit",
|
||||
"thiserror 2.0.21",
|
||||
"tiny-skia",
|
||||
"zip",
|
||||
]
|
||||
|
||||
@@ -4283,11 +4278,9 @@ dependencies = [
|
||||
name = "solidcraft-render"
|
||||
version = "0.40.0-rc.2"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"image",
|
||||
"moxcms",
|
||||
"png",
|
||||
"robust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
@@ -4304,6 +4297,7 @@ dependencies = [
|
||||
"harfrust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"thiserror 2.0.21",
|
||||
@@ -4662,6 +4656,7 @@ version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
@@ -4687,17 +4682,6 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tree_magic_mini"
|
||||
version = "3.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8765b90061cba6c22b5831f675da109ae5561588290f9fa2317adab2714d5a6"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"nom",
|
||||
"petgraph",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@@ -4772,12 +4756,6 @@ version = "1.0.26"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "993ad287eb20e8ba755312801cfe1413e0f25d604075863acaddca10b04c1833"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.13.3"
|
||||
@@ -4861,9 +4839,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vello_common"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bbb2141a2bca6e6d598e471fd4d1d7eed8e020aad6a28187edda07f091a325dd"
|
||||
checksum = "b2e9aed918117e8152c9eddfd8362d73c465c23f26c44786aa331707b8a64fa2"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"fearless_simd",
|
||||
@@ -4876,9 +4854,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "vello_cpu"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ace506ca414548966fcf3c283be94eb1cad8254de488791ee698bf438b0890a9"
|
||||
checksum = "ac7349e1f55f6b801c7c277958df4ea53e7f20f21e8014910ad888b2ecda93ea"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
"glifo",
|
||||
@@ -5053,6 +5031,32 @@ dependencies = [
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-experimental"
|
||||
version = "20250721.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-misc"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8"
|
||||
dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-scanner",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wayland-protocols-plasma"
|
||||
version = "0.3.12"
|
||||
@@ -5630,7 +5634,7 @@ dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"block2 0.5.1",
|
||||
"bytemuck",
|
||||
"calloop",
|
||||
"calloop 0.13.0",
|
||||
"cfg_aliases",
|
||||
"concurrent-queue",
|
||||
"core-foundation 0.9.4",
|
||||
@@ -5651,7 +5655,7 @@ dependencies = [
|
||||
"raw-window-handle",
|
||||
"redox_syscall 0.4.1",
|
||||
"rustix 0.38.44",
|
||||
"smithay-client-toolkit",
|
||||
"smithay-client-toolkit 0.19.2",
|
||||
"smol_str",
|
||||
"tracing",
|
||||
"unicode-segmentation",
|
||||
@@ -5694,24 +5698,6 @@ version = "0.57.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e"
|
||||
|
||||
[[package]]
|
||||
name = "wl-clipboard-rs"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "959c7ac3b8afde864f7a7448ac16f29b2b83350b7eccbf77fb781c3999aa6848"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"os_pipe",
|
||||
"rustix 1.1.5",
|
||||
"thiserror 2.0.21",
|
||||
"tree_magic_mini",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
"wayland-protocols-wlr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "writeable"
|
||||
version = "0.6.4"
|
||||
|
||||
@@ -22,7 +22,6 @@ solidcraft-kernel = { path = "crates/kernel" }
|
||||
solidcraft-smlib = { path = "crates/smlib" }
|
||||
solidcraft-step = { path = "crates/step" }
|
||||
solidcraft-render = { path = "crates/render" }
|
||||
solidcraft-dynamics = { path = "crates/dynamics" }
|
||||
solidcraft-gpu = { path = "crates/gpu" }
|
||||
solidcraft-doc = { path = "crates/doc" }
|
||||
solidcraft-io = { path = "crates/io" }
|
||||
@@ -82,9 +81,9 @@ gethostname = "1"
|
||||
# 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.7"
|
||||
cadmpeg-ir = "=0.6.7"
|
||||
cadmpeg-registry = { version = "=0.6.7", 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", "variable-fonts"] }
|
||||
@@ -103,12 +102,11 @@ tiny-skia = { version = "0.12", default-features = false, features = ["std", "si
|
||||
toml = "1"
|
||||
|
||||
# ui
|
||||
# default_fonts: the built-in Hack is the UI's symbol fallback (#2955); 0.37 no longer turns it on.
|
||||
egui = { version = "0.37", features = ["default_fonts"] }
|
||||
eframe = { version = "0.37", default-features = false, features = ["persistence"] }
|
||||
egui = "0.36"
|
||||
eframe = { version = "0.36", default-features = false, features = ["persistence"] }
|
||||
# Instant/SystemTime that also work in the browser (std panics on wasm32-unknown-unknown).
|
||||
web-time = "1"
|
||||
egui-wgpu = "0.37"
|
||||
egui-wgpu = "0.36"
|
||||
# the wgpu egui-wgpu uses, for compute (crates/gpu); backends come from the apps' features
|
||||
wgpu = { version = "30", default-features = false, features = ["std", "wgsl"] }
|
||||
naga = { version = "30", default-features = false }
|
||||
@@ -163,9 +161,12 @@ debug = "line-tables-only"
|
||||
egui-wgpu = { path = "vendor/egui-wgpu" }
|
||||
|
||||
# Deliver trackpad pinch and rotation gestures on Wayland (zwp_pointer_gestures_v1), which
|
||||
# winit 0.30 never binds and eframe 0.37 still requires winit 0.30.
|
||||
# winit 0.30 never binds and eframe 0.36 still requires winit 0.30.
|
||||
winit = { path = "vendor/winit" }
|
||||
|
||||
# macOS: dropping the accessibility adapter leaves AppKit's key-value observers intact (#7535).
|
||||
accesskit_macos = { path = "vendor/accesskit_macos" }
|
||||
|
||||
# Mutation testing (cargo-mutants, docs/mutation-testing.md): the ci profile with incremental
|
||||
# builds, since every mutant rebuilds the crates above the one it changes.
|
||||
[profile.mutants]
|
||||
|
||||
@@ -173,6 +173,12 @@ and the [foundation ledger](docs/drawing-foundations.md) track the bounded imple
|
||||
packages. The first geometry increment defines validated section records in a plane-local
|
||||
frame; the ledger separates that contract from section producers and the Drawing workspace.
|
||||
|
||||
Drawing UI scaffold: the native workspace now supplies the shared four-tab ribbon,
|
||||
source Browser, compact dockable sheet strip and resizable Sheet Settings/options
|
||||
panel. Planned tools remain unavailable; only existing source services execute.
|
||||
Drawing files and paper rendering are still deferred. See the
|
||||
[UI implementation roadmap](docs/drawing-workspace-ui.md).
|
||||
|
||||
Desktop view DXF export runs in the file-operation worker with five-stage progress and
|
||||
cooperative cancellation. Curve tolerance (0.0001–1 mm, default 0.01 mm) is remembered alongside
|
||||
the hidden-edge setting. Cancelled exports preserve the model and the previous destination;
|
||||
@@ -225,12 +231,6 @@ completed drawings publish atomically. The browser still exports synchronously.
|
||||
|
||||
## Render foundation
|
||||
|
||||
- Wood surface images (#1345): native CPU and GPU opaque rendering share physical
|
||||
repeat frames and filtered mip chains. Imported maps retain exact bytes and
|
||||
declared provenance in the design, reopen offline, and expose grain-angle
|
||||
controls. Scalar face appearances suppress body maps. Planar projection and
|
||||
CPU encoding/optical limits remain explicit; see [portable surfaces](docs/render-portable-surfaces-and-stages.md).
|
||||
|
||||
- Original starter appearances (#1722): 34 opaque recipes through existing commands, with six explicitly unavailable glass recipes and preserved legacy looks; see [the contract](docs/render-starter-appearances.md).
|
||||
|
||||
- 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).
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
Third-party software in SolidCraft
|
||||
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 540 crates below.
|
||||
SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 537 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.
|
||||
|
||||
accesskit 0.25.2 (MIT OR Apache-2.0)
|
||||
accesskit_atspi_common 0.21.1 (MIT OR Apache-2.0)
|
||||
accesskit_consumer 0.39.2 (MIT OR Apache-2.0)
|
||||
accesskit_ios 0.2.2 (MIT OR Apache-2.0)
|
||||
accesskit_macos 0.27.2 (MIT OR Apache-2.0)
|
||||
accesskit_unix 0.24.1 (MIT OR Apache-2.0)
|
||||
accesskit_windows 0.35.2 (MIT OR Apache-2.0)
|
||||
accesskit_winit 0.34.2 (Apache-2.0)
|
||||
accesskit 0.24.1 (MIT OR Apache-2.0)
|
||||
accesskit_atspi_common 0.18.1 (MIT OR Apache-2.0)
|
||||
accesskit_consumer 0.35.0 (MIT OR Apache-2.0)
|
||||
accesskit_consumer 0.36.0 (MIT OR Apache-2.0)
|
||||
accesskit_consumer 0.38.0 (MIT OR Apache-2.0)
|
||||
accesskit_macos 0.26.3 (MIT OR Apache-2.0)
|
||||
accesskit_unix 0.21.1 (MIT OR Apache-2.0)
|
||||
accesskit_windows 0.32.1 (MIT OR Apache-2.0)
|
||||
accesskit_winit 0.32.2 (Apache-2.0)
|
||||
adler2 2.0.1 (0BSD OR MIT OR Apache-2.0)
|
||||
aes 0.8.4 (MIT OR Apache-2.0)
|
||||
ahash 0.8.12 (MIT OR Apache-2.0)
|
||||
@@ -56,15 +57,17 @@ 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.7 (Apache-2.0)
|
||||
cadmpeg-codec-rhino 0.6.7 (Apache-2.0)
|
||||
cadmpeg-codec-step 0.6.7 (Apache-2.0)
|
||||
cadmpeg-container 0.6.7 (Apache-2.0)
|
||||
cadmpeg-core 0.6.7 (Apache-2.0)
|
||||
cadmpeg-ir 0.6.7 (Apache-2.0)
|
||||
cadmpeg-registry 0.6.7 (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)
|
||||
calloop-wayland-source 0.4.1 (MIT)
|
||||
cbc 0.1.2 (MIT OR Apache-2.0)
|
||||
cfg-if 1.0.5 (MIT OR Apache-2.0)
|
||||
cgl 0.3.2 (MIT / Apache-2.0)
|
||||
@@ -106,21 +109,21 @@ document-features 0.2.12 (MIT OR Apache-2.0)
|
||||
downcast-rs 1.2.1 (MIT/Apache-2.0)
|
||||
dpi 0.1.2 (Apache-2.0 AND MIT)
|
||||
dyn-clone 1.0.20 (MIT OR Apache-2.0)
|
||||
ecolor 0.37.0 (MIT OR Apache-2.0)
|
||||
eframe 0.37.0 (MIT OR Apache-2.0)
|
||||
egui 0.37.0 (MIT OR Apache-2.0)
|
||||
egui-wgpu 0.37.0 (MIT OR Apache-2.0)
|
||||
egui-winit 0.37.0 (MIT OR Apache-2.0)
|
||||
egui_glow 0.37.0 (MIT OR Apache-2.0)
|
||||
ecolor 0.36.2 (MIT OR Apache-2.0)
|
||||
eframe 0.36.2 (MIT OR Apache-2.0)
|
||||
egui 0.36.2 (MIT OR Apache-2.0)
|
||||
egui-wgpu 0.36.2 (MIT OR Apache-2.0)
|
||||
egui-winit 0.36.2 (MIT OR Apache-2.0)
|
||||
egui_glow 0.36.2 (MIT OR Apache-2.0)
|
||||
either 1.19.0 (MIT OR Apache-2.0)
|
||||
emath 0.37.0 (MIT OR Apache-2.0)
|
||||
emath 0.36.2 (MIT OR Apache-2.0)
|
||||
encoding_rs 0.8.42 ((Apache-2.0 OR MIT) AND BSD-3-Clause)
|
||||
endi 1.1.1 (MIT)
|
||||
enumflags2 0.7.12 (MIT OR Apache-2.0)
|
||||
enumflags2_derive 0.7.12 (MIT OR Apache-2.0)
|
||||
enumn 0.1.14 (MIT OR Apache-2.0)
|
||||
epaint 0.37.0 (MIT OR Apache-2.0)
|
||||
epaint_default_fonts 0.37.0 ((MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0)
|
||||
epaint 0.36.2 (MIT OR Apache-2.0)
|
||||
epaint_default_fonts 0.36.2 ((MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0)
|
||||
equivalent 1.0.2 (Apache-2.0 OR MIT)
|
||||
errno 0.3.14 (MIT OR Apache-2.0)
|
||||
error-code 3.4.0 (BSL-1.0)
|
||||
@@ -131,9 +134,7 @@ fastrand 2.5.0 (Apache-2.0 OR MIT)
|
||||
fax 0.2.7 (MIT)
|
||||
fdeflate 0.3.7 (MIT OR Apache-2.0)
|
||||
fearless_simd 0.4.1 (Apache-2.0 OR MIT)
|
||||
fixedbitset 0.5.7 (MIT OR Apache-2.0)
|
||||
flate2 1.1.10 (MIT OR Apache-2.0)
|
||||
foldhash 0.1.5 (Zlib)
|
||||
foldhash 0.2.0 (Zlib)
|
||||
font-types 0.12.6 (MIT OR Apache-2.0)
|
||||
foreign-types 0.5.0 (MIT/Apache-2.0)
|
||||
@@ -153,7 +154,7 @@ getrandom 0.2.17 (MIT OR Apache-2.0)
|
||||
getrandom 0.3.4 (MIT OR Apache-2.0)
|
||||
getrandom 0.4.3 (MIT OR Apache-2.0)
|
||||
gif 0.14.2 (MIT OR Apache-2.0)
|
||||
glifo 0.3.0 (Apache-2.0 OR MIT)
|
||||
glifo 0.2.0 (Apache-2.0 OR MIT)
|
||||
glow 0.17.0 (MIT OR Apache-2.0 OR Zlib)
|
||||
gltf 1.4.1 (MIT OR Apache-2.0)
|
||||
gltf-derive 1.4.1 (MIT OR Apache-2.0)
|
||||
@@ -167,7 +168,6 @@ gpu-allocator 0.28.0 (MIT OR Apache-2.0)
|
||||
guillotiere 0.7.0 (MIT/Apache-2.0)
|
||||
half 2.7.1 (MIT OR Apache-2.0)
|
||||
harfrust 0.12.0 (MIT)
|
||||
hashbrown 0.15.5 (MIT OR Apache-2.0)
|
||||
hashbrown 0.16.1 (MIT OR Apache-2.0)
|
||||
hashbrown 0.17.1 (MIT OR Apache-2.0)
|
||||
hermit-abi 0.5.3 (MIT OR Apache-2.0)
|
||||
@@ -234,7 +234,6 @@ ndk-sys 0.6.0+11769913 (MIT OR Apache-2.0)
|
||||
nix 0.29.0 (MIT)
|
||||
nix 0.31.3 (MIT)
|
||||
nohash-hasher 0.2.0 (Apache-2.0 OR MIT)
|
||||
nom 8.0.0 (MIT)
|
||||
num 0.4.3 (MIT OR Apache-2.0)
|
||||
num-bigint 0.4.8 (MIT OR Apache-2.0)
|
||||
num-complex 0.4.6 (MIT OR Apache-2.0)
|
||||
@@ -276,17 +275,15 @@ orbclient 0.3.55 (MIT)
|
||||
ordered-float 2.10.1 (MIT)
|
||||
ordered-float 5.5.0 (MIT)
|
||||
ordered-stream 0.2.0 (MIT OR Apache-2.0)
|
||||
os_pipe 1.2.3 (MIT)
|
||||
parking 2.2.1 (Apache-2.0 OR MIT)
|
||||
parking_lot 0.12.5 (MIT OR Apache-2.0)
|
||||
parking_lot_core 0.9.12 (MIT OR Apache-2.0)
|
||||
peniko 0.6.1 (Apache-2.0 OR MIT)
|
||||
percent-encoding 2.3.2 (MIT OR Apache-2.0)
|
||||
petgraph 0.8.3 (MIT OR Apache-2.0)
|
||||
phf 0.14.0 (MIT)
|
||||
phf_generator 0.14.0 (MIT)
|
||||
phf_macros 0.14.0 (MIT)
|
||||
phf_shared 0.14.0 (MIT)
|
||||
phf 0.13.1 (MIT)
|
||||
phf_generator 0.13.1 (MIT)
|
||||
phf_macros 0.13.1 (MIT)
|
||||
phf_shared 0.13.1 (MIT)
|
||||
pin-project 1.1.13 (Apache-2.0 OR MIT)
|
||||
pin-project-internal 1.1.13 (Apache-2.0 OR MIT)
|
||||
pin-project-lite 0.2.17 (Apache-2.0 OR MIT)
|
||||
@@ -355,7 +352,6 @@ security-framework 3.7.0 (MIT OR Apache-2.0)
|
||||
security-framework-sys 2.17.0 (MIT OR Apache-2.0)
|
||||
self_cell 1.3.0 (Apache-2.0 OR GPL-2.0-only)
|
||||
semver 1.0.28 (MIT OR Apache-2.0)
|
||||
send_wrapper 0.6.0 (MIT/Apache-2.0)
|
||||
serde 1.0.229 (MIT OR Apache-2.0)
|
||||
serde-value 0.7.0 (MIT)
|
||||
serde_core 1.0.229 (MIT OR Apache-2.0)
|
||||
@@ -379,6 +375,8 @@ slab 0.4.12 (MIT)
|
||||
slotmap 1.1.1 (Zlib)
|
||||
smallvec 1.16.2 (MIT OR Apache-2.0)
|
||||
smithay-client-toolkit 0.19.2 (MIT)
|
||||
smithay-client-toolkit 0.20.0 (MIT)
|
||||
smithay-clipboard 0.7.3 (MIT)
|
||||
smol_str 0.2.2 (MIT OR Apache-2.0)
|
||||
solidcore 1.0.1 (Apache-2.0)
|
||||
solidcore-record 0.1.0 (Apache-2.0)
|
||||
@@ -411,7 +409,6 @@ toml_writer 1.1.3+spec-1.1.0 (MIT OR Apache-2.0)
|
||||
tracing 0.1.44 (MIT)
|
||||
tracing-attributes 0.1.31 (MIT)
|
||||
tracing-core 0.1.36 (MIT)
|
||||
tree_magic_mini 3.2.2 (MIT)
|
||||
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)
|
||||
@@ -422,7 +419,6 @@ uds_windows 1.2.1 (MIT)
|
||||
unicode-bidi 0.3.18 (MIT OR Apache-2.0)
|
||||
unicode-general-category 1.1.0 (Apache-2.0)
|
||||
unicode-ident 1.0.26 ((MIT OR Apache-2.0) AND Unicode-3.0)
|
||||
unicode-properties 0.1.5 ((MIT OR Apache-2.0) AND Unicode-3.0)
|
||||
unicode-segmentation 1.13.3 (MIT OR Apache-2.0)
|
||||
unicode-width 0.2.2 (MIT OR Apache-2.0)
|
||||
untrusted 0.9.0 (ISC)
|
||||
@@ -432,8 +428,8 @@ url 2.5.8 (MIT OR Apache-2.0)
|
||||
utf8-zero 0.8.1 (MIT OR Apache-2.0)
|
||||
utf8_iter 1.0.4 (Apache-2.0 OR MIT)
|
||||
uuid 1.27.0 (Apache-2.0 OR MIT)
|
||||
vello_common 0.2.0 (Apache-2.0 OR MIT)
|
||||
vello_cpu 0.2.0 (Apache-2.0 OR MIT)
|
||||
vello_common 0.1.0 (Apache-2.0 OR MIT)
|
||||
vello_cpu 0.1.0 (Apache-2.0 OR MIT)
|
||||
wasi 0.11.1+wasi-snapshot-preview1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
||||
wasip2 1.0.4+wasi-0.2.12 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
||||
wasm-bindgen 0.2.129 (MIT OR Apache-2.0)
|
||||
@@ -446,6 +442,8 @@ wayland-client 0.31.15 (MIT)
|
||||
wayland-csd-frame 0.3.0 (MIT)
|
||||
wayland-cursor 0.31.14 (MIT)
|
||||
wayland-protocols 0.32.13 (MIT)
|
||||
wayland-protocols-experimental 20250721.0.1 (MIT)
|
||||
wayland-protocols-misc 0.3.12 (MIT)
|
||||
wayland-protocols-plasma 0.3.12 (MIT)
|
||||
wayland-protocols-wlr 0.3.12 (MIT)
|
||||
wayland-scanner 0.31.11 (MIT)
|
||||
@@ -502,7 +500,6 @@ winit 0.30.13 (Apache-2.0)
|
||||
winnow 1.0.4 (MIT)
|
||||
winreg 0.56.0 (MIT)
|
||||
wit-bindgen 0.57.1 (Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT)
|
||||
wl-clipboard-rs 0.9.5 (MIT OR Apache-2.0)
|
||||
writeable 0.6.4 (Unicode-3.0)
|
||||
x11-dl 2.21.0 (MIT)
|
||||
x11rb 0.13.2 (MIT OR Apache-2.0)
|
||||
@@ -573,14 +570,13 @@ num_enum 0.7.6, num_enum_derive 0.7.6, once_cell 1.21.4, ordered-stream 0.2.0, p
|
||||
pin-project 1.1.13, pin-project-internal 1.1.13, pin-project-lite 0.2.17, piper 0.2.5, polling
|
||||
3.11.0, portable-atomic 1.15.0, portable-atomic-util 0.2.8, proc-macro-crate 3.5.0, proc-macro2
|
||||
1.0.107, quote 1.0.47, ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 1.1.0, rustc-hash 2.1.3,
|
||||
rustix 0.38.44, rustix 1.1.5, rustversion 1.0.23, semver 1.0.28, send_wrapper 0.6.0, serde 1.0.229,
|
||||
serde_core 1.0.229, serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json 1.0.151,
|
||||
serde_path_to_error 0.1.20, serde_repr 0.1.21, simd_cesu8 1.2.0, smol_str 0.2.2, syn 2.0.119, syn
|
||||
3.0.6, thiserror 1.0.69, thiserror 2.0.21, thiserror-impl 1.0.69, thiserror-impl 2.0.21, typed-path
|
||||
0.13.0, typeid 1.0.3, unicode-ident 1.0.26, utf8-zero 0.8.1, wasi 0.11.1+wasi-snapshot-preview1,
|
||||
wasip2 1.0.4+wasi-0.2.12, wit-bindgen 0.57.1, x11-dl 2.21.0, xdg-home 1.3.0, zbus-lockstep 0.5.2,
|
||||
zbus-lockstep-macros 0.5.2, zbus_names 3.0.0, zmij 1.0.23, zvariant_utils 2.1.0, zvariant_utils
|
||||
4.2.0
|
||||
rustix 0.38.44, rustix 1.1.5, rustversion 1.0.23, semver 1.0.28, serde 1.0.229, serde_core 1.0.229,
|
||||
serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json 1.0.151, serde_path_to_error 0.1.20,
|
||||
serde_repr 0.1.21, simd_cesu8 1.2.0, smol_str 0.2.2, syn 2.0.119, syn 3.0.6, thiserror 1.0.69,
|
||||
thiserror 2.0.21, thiserror-impl 1.0.69, thiserror-impl 2.0.21, typed-path 0.13.0, typeid 1.0.3,
|
||||
unicode-ident 1.0.26, utf8-zero 0.8.1, wasi 0.11.1+wasi-snapshot-preview1, wasip2 1.0.4+wasi-0.2.12,
|
||||
wit-bindgen 0.57.1, x11-dl 2.21.0, xdg-home 1.3.0, zbus-lockstep 0.5.2, zbus-lockstep-macros 0.5.2,
|
||||
zbus_names 3.0.0, zmij 1.0.23, zvariant_utils 2.1.0, zvariant_utils 4.2.0
|
||||
================================================================================
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
@@ -1090,26 +1086,25 @@ ahash 0.8.12, arrayvec 0.7.8, async-channel 2.5.0, async-executor 1.14.0, async-
|
||||
0.9.4, core-foundation-sys 0.8.7, core-graphics 0.23.2, core-graphics-types 0.1.3, core_detect
|
||||
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, fixedbitset
|
||||
0.5.7, flate2 1.1.10, form_urlencoded 1.2.2, futures-lite 2.6.1, gethostname 1.1.0, gif 0.14.2, glow
|
||||
0.17.0, gltf 1.4.1, gltf-derive 1.4.1, gltf-json 1.4.1, hashbrown 0.15.5, hashbrown 0.16.1,
|
||||
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.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, petgraph 0.8.3, 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, send_wrapper 0.6.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, tempfile 3.27.0, ttf-parser 0.25.1,
|
||||
unicode-bidi 0.3.18, unicode-properties 0.1.5, 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,
|
||||
wl-clipboard-rs 0.9.5
|
||||
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.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, tempfile 3.27.0, ttf-parser 0.25.1, unicode-bidi 0.3.18, unicode-segmentation 1.13.3,
|
||||
unicode-width 0.2.2, url 2.5.8, uuid 1.27.0, wasi 0.11.1+wasi-snapshot-preview1, wasip2
|
||||
1.0.4+wasi-0.2.12, wasm-bindgen 0.2.129, wasm-bindgen-futures 0.4.79, wasm-bindgen-macro 0.2.129,
|
||||
wasm-bindgen-macro-support 0.2.129, wasm-bindgen-shared 0.2.129, web-sys 0.3.106, weezl 0.1.12,
|
||||
wit-bindgen 0.57.1
|
||||
================================================================================
|
||||
|
||||
Apache License
|
||||
@@ -1315,7 +1310,7 @@ See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
================================================================================
|
||||
allocator-api2 0.2.21, async-trait 0.1.92, dyn-clone 1.0.20, enumn 0.1.14, glifo 0.3.0, itoa 1.0.18,
|
||||
allocator-api2 0.2.21, async-trait 0.1.92, dyn-clone 1.0.20, enumn 0.1.14, glifo 0.2.0, itoa 1.0.18,
|
||||
libc 0.2.190, litrs 1.0.0, num_enum 0.7.6, num_enum_derive 0.7.6, proc-macro2 1.0.107, quote 1.0.47,
|
||||
ref-cast 1.0.27, ref-cast-impl 1.0.27, rustc-hash 2.1.3, rustversion 1.0.23, semver 1.0.28, serde
|
||||
1.0.229, serde_core 1.0.229, serde_derive 1.0.229, serde_derive_internals 0.30.0, serde_json
|
||||
@@ -1535,7 +1530,7 @@ android-activity 0.6.1, arboard 3.6.1, color 0.3.3, fdeflate 0.3.7, fearless_sim
|
||||
image 0.25.10, image-webp 0.2.4, linebender_resource_handle 0.1.1, miniz_oxide 0.8.9, miniz_oxide
|
||||
0.9.1, naga 30.0.1, naga-types 30.0.1, pin-project 1.1.13, pin-project-internal 1.1.13,
|
||||
pin-project-lite 0.2.17, portable-atomic 1.15.0, portable-atomic-util 0.2.8, range-alloc 0.1.5,
|
||||
raw-window-handle 0.6.2, simdutf8 0.1.5, vello_common 0.2.0, vello_cpu 0.2.0, wgpu 30.0.1, wgpu-core
|
||||
raw-window-handle 0.6.2, simdutf8 0.1.5, vello_common 0.1.0, vello_cpu 0.1.0, wgpu 30.0.1, wgpu-core
|
||||
30.0.1, wgpu-core-deps-apple 30.0.1, wgpu-core-deps-emscripten 30.0.1, wgpu-core-deps-wasm 30.0.1,
|
||||
wgpu-core-deps-windows-linux-android 30.0.1, wgpu-hal 30.0.1, wgpu-naga-bridge 30.0.1, wgpu-types
|
||||
30.0.1
|
||||
@@ -2593,7 +2588,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
bit-set 0.10.0, bit-vec 0.9.1, downcast-rs 1.2.1, egui-wgpu 0.37.0, presser 0.3.1
|
||||
bit-set 0.10.0, bit-vec 0.9.1, downcast-rs 1.2.1, egui-wgpu 0.36.2, presser 0.3.1
|
||||
================================================================================
|
||||
|
||||
Apache License
|
||||
@@ -3139,7 +3134,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
calloop 0.13.0, smithay-client-toolkit 0.19.2
|
||||
calloop 0.13.0, calloop 0.14.5, smithay-client-toolkit 0.19.2, smithay-client-toolkit 0.20.0
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2018 Victor Berger
|
||||
@@ -3163,7 +3158,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
calloop-wayland-source 0.3.0
|
||||
calloop-wayland-source 0.3.0, calloop-wayland-source 0.4.1
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2023 Kirill Chibisov
|
||||
@@ -5236,7 +5231,7 @@ have been licensed under extremely permissive terms.
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
================================================================================
|
||||
egui-wgpu 0.37.0
|
||||
egui-wgpu 0.36.2
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2018-2021 Emil Ernerfeldt <emil.ernerfeldt@gmail.com>
|
||||
@@ -5266,7 +5261,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
either 1.19.0, itertools 0.15.0, petgraph 0.8.3
|
||||
either 1.19.0, itertools 0.15.0
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2015
|
||||
@@ -5714,36 +5709,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
fixedbitset 0.5.7
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2015-2017
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
flate2 1.1.10
|
||||
================================================================================
|
||||
@@ -5775,7 +5740,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
foldhash 0.1.5, foldhash 0.2.0
|
||||
foldhash 0.2.0
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2024 Orson Peters
|
||||
@@ -6546,7 +6511,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
glifo 0.3.0
|
||||
glifo 0.2.0
|
||||
================================================================================
|
||||
|
||||
Copyright 2025 the Vello Authors
|
||||
@@ -7173,7 +7138,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
hashbrown 0.15.5, hashbrown 0.16.1, hashbrown 0.17.1
|
||||
hashbrown 0.16.1, hashbrown 0.17.1
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2016 Amanieu d'Antras
|
||||
@@ -9494,31 +9459,6 @@ 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.
|
||||
|
||||
================================================================================
|
||||
nom 8.0.0
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2014-2019 Geoffroy Couprie
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
num_enum 0.7.6, num_enum_derive 0.7.6
|
||||
================================================================================
|
||||
@@ -9607,30 +9547,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
os_pipe 1.2.3
|
||||
================================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
parking 2.2.1
|
||||
================================================================================
|
||||
@@ -9646,7 +9562,7 @@ option. All files in the project carrying such notice may not be
|
||||
copied, modified, or distributed except according to those terms.
|
||||
|
||||
================================================================================
|
||||
phf 0.14.0, phf_generator 0.14.0, phf_macros 0.14.0, phf_shared 0.14.0
|
||||
phf 0.13.1, phf_generator 0.13.1, phf_macros 0.13.1, phf_shared 0.13.1
|
||||
================================================================================
|
||||
|
||||
The MIT License (MIT)
|
||||
@@ -12461,6 +12377,30 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
smithay-clipboard 0.7.3
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2018 Lucas Timmins & Victor Berger
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
solidcore 1.0.1
|
||||
================================================================================
|
||||
@@ -13033,32 +12973,6 @@ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
tree_magic_mini 3.2.2
|
||||
================================================================================
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2017 Aaron Hancock
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
ttf-parser 0.25.1
|
||||
================================================================================
|
||||
@@ -13881,7 +13795,7 @@ carrying such notice may not be copied, modified, or distributed except
|
||||
according to those terms.
|
||||
|
||||
================================================================================
|
||||
unicode-bidi 0.3.18, unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||
unicode-bidi 0.3.18, unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2015 The Rust Project Developers
|
||||
@@ -13955,51 +13869,7 @@ dealings in these Data Files or Software without prior written
|
||||
authorization of the copyright holder.
|
||||
|
||||
================================================================================
|
||||
unicode-properties 0.1.5
|
||||
================================================================================
|
||||
|
||||
UNICODE LICENSE V3
|
||||
|
||||
COPYRIGHT AND PERMISSION NOTICE
|
||||
|
||||
Copyright © 1991-2026 Unicode, Inc.
|
||||
|
||||
NOTICE TO USER: Carefully read the following legal agreement. BY
|
||||
DOWNLOADING, INSTALLING, COPYING OR OTHERWISE USING DATA FILES, AND/OR
|
||||
SOFTWARE, YOU UNEQUIVOCALLY ACCEPT, AND AGREE TO BE BOUND BY, ALL OF THE
|
||||
TERMS AND CONDITIONS OF THIS AGREEMENT. IF YOU DO NOT AGREE, DO NOT
|
||||
DOWNLOAD, INSTALL, COPY, DISTRIBUTE OR USE THE DATA FILES OR SOFTWARE.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of data files and any associated documentation (the "Data Files") or
|
||||
software and any associated documentation (the "Software") to deal in the
|
||||
Data Files or Software without restriction, including without limitation
|
||||
the rights to use, copy, modify, merge, publish, distribute, and/or sell
|
||||
copies of the Data Files or Software, and to permit persons to whom the
|
||||
Data Files or Software are furnished to do so, provided that either (a)
|
||||
this copyright and permission notice appear with all copies of the Data
|
||||
Files or Software, or (b) this copyright and permission notice appear in
|
||||
associated Documentation.
|
||||
|
||||
THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
|
||||
KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
|
||||
THIRD PARTY RIGHTS.
|
||||
|
||||
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS NOTICE
|
||||
BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES,
|
||||
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
|
||||
WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
|
||||
ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THE DATA
|
||||
FILES OR SOFTWARE.
|
||||
|
||||
Except as contained in this notice, the name of a copyright holder shall
|
||||
not be used in advertising or otherwise to promote the sale, use or other
|
||||
dealings in these Data Files or Software without prior written
|
||||
authorization of the copyright holder.
|
||||
|
||||
================================================================================
|
||||
unicode-properties 0.1.5, unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||
unicode-segmentation 1.13.3, unicode-width 0.2.2
|
||||
================================================================================
|
||||
|
||||
Licensed under the Apache License, Version 2.0
|
||||
@@ -14145,7 +14015,7 @@ IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
vello_common 0.2.0, vello_cpu 0.2.0
|
||||
vello_common 0.1.0, vello_cpu 0.1.0
|
||||
================================================================================
|
||||
|
||||
Copyright 2020 the Vello Authors
|
||||
@@ -14176,8 +14046,8 @@ DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
wayland-backend 0.3.17, wayland-client 0.31.15, wayland-cursor 0.31.14, wayland-protocols 0.32.13,
|
||||
wayland-protocols-plasma 0.3.12, wayland-protocols-wlr 0.3.12, wayland-scanner 0.31.11, wayland-sys
|
||||
0.31.11
|
||||
wayland-protocols-experimental 20250721.0.1, wayland-protocols-misc 0.3.12, wayland-protocols-plasma
|
||||
0.3.12, wayland-protocols-wlr 0.3.12, wayland-scanner 0.31.11, wayland-sys 0.31.11
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2015 Elinor Berger
|
||||
@@ -15072,36 +14942,6 @@ 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.
|
||||
|
||||
================================================================================
|
||||
wl-clipboard-rs 0.9.5
|
||||
================================================================================
|
||||
|
||||
Copyright (c) 2019 Ivan Molodetskikh
|
||||
|
||||
Permission is hereby granted, free of charge, to any
|
||||
person obtaining a copy of this software and associated
|
||||
documentation files (the "Software"), to deal in the
|
||||
Software without restriction, including without
|
||||
limitation the rights to use, copy, modify, merge,
|
||||
publish, distribute, sublicense, and/or sell copies of
|
||||
the Software, and to permit persons to whom the Software
|
||||
is furnished to do so, subject to the following
|
||||
conditions:
|
||||
|
||||
The above copyright notice and this permission notice
|
||||
shall be included in all copies or substantial portions
|
||||
of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF
|
||||
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
|
||||
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
|
||||
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR
|
||||
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
DEALINGS IN THE SOFTWARE.
|
||||
|
||||
================================================================================
|
||||
x11rb 0.13.2, x11rb-protocol 0.13.2
|
||||
================================================================================
|
||||
@@ -16127,34 +15967,35 @@ Crates whose package carries no licence file (their licence is named in Cargo.to
|
||||
texts are as in SolidCraft's own LICENSE-MIT and LICENSE-APACHE)
|
||||
================================================================================
|
||||
|
||||
accesskit 0.25.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_atspi_common 0.21.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_consumer 0.39.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_ios 0.2.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_macos 0.27.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_unix 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_windows 0.35.2: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_winit 0.34.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_atspi_common 0.18.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_consumer 0.35.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_consumer 0.36.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_consumer 0.38.0: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_macos 0.26.3: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_unix 0.21.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_windows 0.32.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
accesskit_winit 0.32.2: Apache-2.0 (https://github.com/AccessKit/accesskit)
|
||||
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.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-codec-rhino 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-codec-step 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-container 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-core 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-ir 0.6.7: Apache-2.0 (https://github.com/cadmpeg/cadmpeg)
|
||||
cadmpeg-registry 0.6.7: 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)
|
||||
dispatch 0.2.0: MIT (http://github.com/SSheldon/rust-dispatch)
|
||||
dispatch2 0.3.1: Zlib OR Apache-2.0 OR MIT (https://github.com/madsmtm/objc2)
|
||||
ecolor 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
||||
eframe 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/eframe)
|
||||
egui 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
||||
egui-winit 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui-winit)
|
||||
egui_glow 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui_glow)
|
||||
emath 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/emath)
|
||||
epaint 0.37.0: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/epaint)
|
||||
epaint_default_fonts 0.37.0: (MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0 (https://github.com/emilk/egui/tree/main/crates/epaint_default_fonts)
|
||||
ecolor 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
||||
eframe 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/eframe)
|
||||
egui 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui)
|
||||
egui-winit 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui-winit)
|
||||
egui_glow 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/egui_glow)
|
||||
emath 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/emath)
|
||||
epaint 0.36.2: MIT OR Apache-2.0 (https://github.com/emilk/egui/tree/main/crates/epaint)
|
||||
epaint_default_fonts 0.36.2: (MIT OR Apache-2.0) AND OFL-1.1 AND Ubuntu-font-1.0 (https://github.com/emilk/egui/tree/main/crates/epaint_default_fonts)
|
||||
jni 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
|
||||
jni-macros 0.22.4: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-rs)
|
||||
jni-sys-macros 0.4.1: MIT OR Apache-2.0 (https://github.com/jni-rs/jni-sys)
|
||||
@@ -16198,7 +16039,7 @@ spirv 0.4.0+sdk-1.4.341.0: Apache-2.0 (https://github.com/gfx-rs/rspirv)
|
||||
Bundled fonts (SIL Open Font License, Version 1.1)
|
||||
================================================================================
|
||||
|
||||
- LiberationSans-Regular.ttf (crates/sketch/assets/fonts/liberation-sans/LiberationSans-Regular.ttf)
|
||||
- LiberationSans-Regular.ttf (assets/fonts/liberation-sans/LiberationSans-Regular.ttf)
|
||||
Copyright 2012 Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google)
|
||||
Source: https://github.com/liberationfonts
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ use std::process::{Command, ExitCode};
|
||||
const TARGET: &str = "solidcraft-cli";
|
||||
|
||||
fn main() -> ExitCode {
|
||||
solidcraft_engine::note!("solvecraft-cli is deprecated and will be removed in a later release: use solidcraft-cli");
|
||||
eprintln!("solvecraft-cli is deprecated and will be removed in a later release: use solidcraft-cli");
|
||||
let sibling = std::env::current_exe()
|
||||
.ok()
|
||||
.and_then(|exe| exe.parent().map(|dir| dir.join(format!("{TARGET}{}", std::env::consts::EXE_SUFFIX))))
|
||||
@@ -23,7 +23,7 @@ fn main() -> ExitCode {
|
||||
None => ExitCode::FAILURE,
|
||||
},
|
||||
Err(e) => {
|
||||
solidcraft_engine::note!("solvecraft-cli: could not run {}: {e}", program.to_string_lossy());
|
||||
eprintln!("solvecraft-cli: could not run {}: {e}", program.to_string_lossy());
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,10 @@ fn replay(report: &Value) -> Result<(), String> {
|
||||
Some(d) => super::load(d)?,
|
||||
None => solidcraft_engine::Session::default(),
|
||||
};
|
||||
// The ids the session drew from there, so the commands give the same objects the same ids.
|
||||
if let Some(seed) = script["ids"].as_u64() {
|
||||
s.seed_ids(seed);
|
||||
}
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
let loc = info.location().map_or_else(|| "(unknown)".to_string(), |l| format!("{}:{}", l.file(), l.line()));
|
||||
let msg = info.payload_as_str().unwrap_or("(not a text message)").to_string();
|
||||
|
||||
@@ -33,8 +33,8 @@ pub fn one(args: &[String]) -> Result<(), String> {
|
||||
std::panic::set_hook(Box::new(|_| {
|
||||
PANICS.fetch_add(1, std::sync::atomic::Ordering::SeqCst);
|
||||
}));
|
||||
solidcraft_engine::note!("design {seed}");
|
||||
let run = fuzz::run_with(seed, steps, &mut |st| solidcraft_engine::note!("step {}", serde_json::to_string(st).unwrap_or_default()));
|
||||
eprintln!("design {seed}");
|
||||
let run = fuzz::run_with(seed, steps, &mut |st| eprintln!("step {}", serde_json::to_string(st).unwrap_or_default()));
|
||||
let mut design = json!({
|
||||
"outcome": run.outcome,
|
||||
"steps": run.steps.iter().map(|s| s.kind).collect::<Vec<_>>(),
|
||||
@@ -45,13 +45,13 @@ pub fn one(args: &[String]) -> Result<(), String> {
|
||||
if run.outcome.is_bug() {
|
||||
design["script"] = run.script();
|
||||
if minimise {
|
||||
solidcraft_engine::note!("minimise");
|
||||
eprintln!("minimise");
|
||||
let m = fuzz::minimise(&run);
|
||||
design["minimal"] = json!({"outcome": m.outcome, "steps": m.steps.iter().map(|s| s.kind).collect::<Vec<_>>(), "script": m.script()});
|
||||
}
|
||||
}
|
||||
let (a, b) = solidcraft_engine::fuzz::boolean_shapes(seed);
|
||||
solidcraft_engine::note!("boolean {seed} {} {}", serde_json::to_string(&a).unwrap_or_default(), serde_json::to_string(&b).unwrap_or_default());
|
||||
eprintln!("boolean {seed} {} {}", serde_json::to_string(&a).unwrap_or_default(), serde_json::to_string(&b).unwrap_or_default());
|
||||
let b = boolean_case(seed);
|
||||
let bp = PANICS.swap(0, std::sync::atomic::Ordering::SeqCst);
|
||||
println!("{}", json!({"seed": seed, "design": design, "boolean": b, "boolean_panics": bp}));
|
||||
@@ -162,14 +162,11 @@ fn record(v: &Value, out_dir: Option<&str>) {
|
||||
let d = &v["design"];
|
||||
let bug = matches!(d["outcome"]["kind"].as_str(), Some("failed" | "panic" | "invalid" | "volume"));
|
||||
if bug || v.get("timeout").is_some() || v.get("crash").is_some() {
|
||||
solidcraft_engine::note!(
|
||||
"seed {seed}: {}",
|
||||
if bug { format!("{} {}", d["outcome"]["kind"], d["outcome"]["detail"]) } else { format!("{v}") }
|
||||
);
|
||||
eprintln!("seed {seed}: {}", if bug { format!("{} {}", d["outcome"]["kind"], d["outcome"]["detail"]) } else { format!("{v}") });
|
||||
}
|
||||
let bk = v["boolean"]["outcome"].as_str().unwrap_or("ok");
|
||||
if bk != "ok" {
|
||||
solidcraft_engine::note!("seed {seed}: boolean {bk} {}", v["boolean"]["detail"]);
|
||||
eprintln!("seed {seed}: boolean {bk} {}", v["boolean"]["detail"]);
|
||||
}
|
||||
if let Some(dir) = out_dir {
|
||||
use std::io::Write;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
//! `kernel-cases --export=DIR`: one kernel rig case as a corpus entry for solidcore's rig. The
|
||||
//! case's commands run as the rig runs them while the kernel's operations are traced
|
||||
//! (`smlib_trace`); the entry holds every operation in execution order, including discarded
|
||||
//! tool construction and handled refusals, each operand that came in as a record (the
|
||||
//! (`smlib_trace`); the entry holds the operation (or the chain of operations) that made the
|
||||
//! bodies the case ends with, each operand that came in as a record given as that record (the
|
||||
//! B-rep record, solidcore-record's JSON), the fix set, the case's expected values and the rig's
|
||||
//! own result. A case whose result no kernel operation alone accounts for is written too, with
|
||||
//! the reasons, so nothing is dropped silently.
|
||||
@@ -98,6 +98,32 @@ impl<'a> Trace<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// The operations `ends` need, in the order they ran: each body's maker, and its operands' makers
|
||||
/// in turn, back to bodies made from records. Operands made by nothing traced are listed in
|
||||
/// `unknown`.
|
||||
fn chain(t: &Trace<'_>, ends: &[u64], unknown: &mut BTreeSet<u64>) -> Vec<usize> {
|
||||
let mut need = BTreeSet::new();
|
||||
let mut todo: Vec<u64> = ends.to_vec();
|
||||
while let Some(id) = todo.pop() {
|
||||
if t.imports.contains_key(&id) {
|
||||
continue;
|
||||
}
|
||||
match t.producer.get(&id) {
|
||||
Some(&i) => {
|
||||
if need.insert(i)
|
||||
&& let Some(c) = t.calls.get(i)
|
||||
{
|
||||
todo.extend(c.1.iter().copied());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
unknown.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
need.into_iter().collect()
|
||||
}
|
||||
|
||||
/// The entry of case `dir`, run with result `out`, into `root/<area>/<id>.json`, and the fix set
|
||||
/// into `root/fixes.json`.
|
||||
pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<(), String> {
|
||||
@@ -194,18 +220,14 @@ pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<()
|
||||
}
|
||||
|
||||
let mut unknown = BTreeSet::new();
|
||||
let mut available: BTreeSet<u64> = t.imports.keys().copied().collect();
|
||||
for (_, inputs, _, outputs) in &t.calls {
|
||||
unknown.extend(inputs.iter().filter(|id| !available.contains(id)).copied());
|
||||
if let Ok(made) = outputs {
|
||||
available.extend(made.iter().copied());
|
||||
}
|
||||
}
|
||||
unknown.extend(ends.iter().filter(|id| !available.contains(id)).copied());
|
||||
let mut order = chain(&t, &ends, &mut unknown);
|
||||
if !unknown.is_empty() {
|
||||
why_not.push(format!("operands made by nothing traced: {unknown:?}"));
|
||||
}
|
||||
// Operand records: every body a call takes, or the case ends with, that came in as a record.
|
||||
if let Some(i) = failed_call.filter(|i| !order.contains(i)) {
|
||||
order.push(i);
|
||||
}
|
||||
// Operand records: every body the chain takes, or the case ends with, that came in as a record.
|
||||
let mut bodies = BTreeMap::new();
|
||||
let given = |id: u64, bodies: &mut BTreeMap<String, Value>| {
|
||||
if let Some((record, trusted, splines)) = t.imports.get(&id) {
|
||||
@@ -213,21 +235,14 @@ pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<()
|
||||
}
|
||||
};
|
||||
let mut calls: Vec<Value> = Vec::new();
|
||||
for (i, (op, inputs, params, outputs)) in t.calls.iter().enumerate() {
|
||||
for (op, inputs, params, outputs) in order.iter().filter_map(|&i| t.calls.get(i)) {
|
||||
for &id in inputs.iter() {
|
||||
given(id, &mut bodies);
|
||||
}
|
||||
let mut c = json!({"op": op, "inputs": inputs.iter().map(|i| format!("b{i}")).collect::<Vec<_>>(), "params": params});
|
||||
match outputs {
|
||||
Ok(made) => c["outputs"] = json!(made.iter().map(|i| format!("b{i}")).collect::<Vec<_>>()),
|
||||
Err(e) => {
|
||||
c["error"] = json!(e);
|
||||
// The application handled this refusal. Replay must continue to any later
|
||||
// calls and grade the final bodies, even when this was the last attempt.
|
||||
if failed_call != Some(i) {
|
||||
c["continued"] = json!(true);
|
||||
}
|
||||
}
|
||||
Err(e) => c["error"] = json!(e),
|
||||
}
|
||||
calls.push(c);
|
||||
}
|
||||
@@ -275,134 +290,6 @@ pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<()
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn empty_record() -> Brep {
|
||||
Brep { version: 4, tolerance: 1e-6, regions: Vec::new(), shells: Vec::new(), faces: Vec::new(), edges: Vec::new(), vertices: Vec::new() }
|
||||
}
|
||||
|
||||
fn call(op: &'static str, inputs: &[u64], outputs: Result<Vec<u64>, String>) -> Event {
|
||||
Event::Call { op, inputs: inputs.to_vec(), params: json!({}), outputs }
|
||||
}
|
||||
|
||||
fn exported_case(name: &str, events: Vec<Event>, ran: Result<Vec<(String, Brep)>, String>) -> Value {
|
||||
let root = std::env::temp_dir().join(format!("solidcraft-export-{}-{name}", std::process::id()));
|
||||
let dir = root.join("tests/kernel-cases/traces").join(name);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
std::fs::write(dir.join("expected.json"), "{}").unwrap();
|
||||
let output = root.join("export");
|
||||
write(&output, dir.to_str().unwrap(), &json!({"status": "pass"}), Capture { events, ran: Some(ran) }).unwrap();
|
||||
let entry = output.join("traces").join(format!("{name}.json"));
|
||||
serde_json::from_str(&std::fs::read_to_string(entry).unwrap()).unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discarded_tool_construction_is_exported_in_execution_order() {
|
||||
let record = empty_record();
|
||||
let e = exported_case(
|
||||
"discarded-tool",
|
||||
vec![call("extrude", &[], Ok(vec![1])), call("extrude", &[], Ok(vec![2])), Event::Export { id: 1, record: Box::new(record.clone()) }],
|
||||
Ok(vec![("Part".into(), record)]),
|
||||
);
|
||||
assert_eq!(e["calls"].as_array().unwrap().len(), 2);
|
||||
assert_eq!(e["calls"][0]["outputs"], json!(["b1"]));
|
||||
assert_eq!(e["calls"][1]["outputs"], json!(["b2"]));
|
||||
assert_eq!(e["traced_ops"], json!(["extrude", "extrude"]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handled_refusals_are_exported_without_becoming_terminal() {
|
||||
let record = empty_record();
|
||||
let e = exported_case(
|
||||
"handled-refusal",
|
||||
vec![
|
||||
call("extrude", &[], Ok(vec![1])),
|
||||
call("fillet", &[1], Err("too large".into())),
|
||||
call("fillet", &[1], Ok(vec![2])),
|
||||
Event::Export { id: 2, record: Box::new(record.clone()) },
|
||||
],
|
||||
Ok(vec![("Part".into(), record)]),
|
||||
);
|
||||
assert_eq!(e["calls"].as_array().unwrap().len(), 3);
|
||||
assert_eq!(e["calls"][1]["error"], "too large");
|
||||
assert_eq!(e["calls"][1]["continued"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_handled_last_attempt_keeps_the_retained_body_as_the_result() {
|
||||
let record = empty_record();
|
||||
let e = exported_case(
|
||||
"handled-last-attempt",
|
||||
vec![
|
||||
call("extrude", &[], Ok(vec![1])),
|
||||
call("fillet", &[1], Err("too large".into())),
|
||||
Event::Export { id: 1, record: Box::new(record.clone()) },
|
||||
],
|
||||
Ok(vec![("Part".into(), record)]),
|
||||
);
|
||||
assert_eq!(e["calls"][1]["continued"], true);
|
||||
assert_eq!(e["result"], json!([{"body": "Part", "made": "b1"}]));
|
||||
assert_eq!(e["operation_expressible"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_final_refusal_still_ends_replay() {
|
||||
let e = exported_case(
|
||||
"terminal-refusal",
|
||||
vec![
|
||||
call("extrude", &[], Ok(vec![1])),
|
||||
call("fillet", &[1], Err("too large".into())),
|
||||
call("fillet", &[1], Err("no room".into())),
|
||||
call("copy", &[1], Ok(vec![2])),
|
||||
],
|
||||
Err("no room".into()),
|
||||
);
|
||||
assert_eq!(e["calls"].as_array().unwrap().len(), 4);
|
||||
assert_eq!(e["calls"][1]["continued"], true);
|
||||
assert!(e["calls"][2].get("continued").is_none());
|
||||
assert_eq!(e["outcome"]["kernel_error"], "no room");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discarded_operations_still_require_their_operand_records() {
|
||||
let record = empty_record();
|
||||
let e = exported_case(
|
||||
"missing-tool-operand",
|
||||
vec![call("extrude", &[], Ok(vec![1])), call("copy", &[99], Ok(vec![2])), Event::Export { id: 1, record: Box::new(record.clone()) }],
|
||||
Ok(vec![("Part".into(), record)]),
|
||||
);
|
||||
assert_eq!(e["operation_expressible"], false);
|
||||
assert!(e["why_not"].as_array().unwrap().iter().any(|why| why.as_str().unwrap().contains("99")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn discarded_operations_keep_their_imported_operands() {
|
||||
let record = empty_record();
|
||||
let e = exported_case(
|
||||
"imported-tool-operand",
|
||||
vec![
|
||||
call("extrude", &[], Ok(vec![1])),
|
||||
Event::Import { id: 9, record: Box::new(record.clone()), trusted: false, keeping_splines: true },
|
||||
call("copy", &[9], Ok(vec![2])),
|
||||
Event::Export { id: 1, record: Box::new(record.clone()) },
|
||||
],
|
||||
Ok(vec![("Part".into(), record.clone())]),
|
||||
);
|
||||
assert_eq!(e["bodies"]["b9"], json!({"record": record, "trusted": false, "keeping_splines": true}));
|
||||
assert_eq!(e["calls"][1]["inputs"], json!(["b9"]));
|
||||
assert_eq!(e["operation_expressible"], true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_later_output_cannot_supply_an_earlier_discarded_operation() {
|
||||
let record = empty_record();
|
||||
let e = exported_case(
|
||||
"future-tool-operand",
|
||||
vec![call("copy", &[9], Ok(vec![2])), call("extrude", &[], Ok(vec![9])), Event::Export { id: 9, record: Box::new(record.clone()) }],
|
||||
Ok(vec![("Part".into(), record)]),
|
||||
);
|
||||
assert_eq!(e["operation_expressible"], false);
|
||||
assert!(e["why_not"].as_array().unwrap().iter().any(|why| why.as_str().unwrap().contains("9")));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_area_is_the_path_under_kernel_cases() {
|
||||
assert_eq!(area_and_id("tests/kernel-cases/booleans/1894-x").unwrap(), ("booleans".into(), "1894-x".into()));
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
|
||||
//! solidcraft-cli kernel-cases [--json] [--dumps=DIR] [--validation=MODE] <case-dir>... the kernel regression rig (tests/kernel-cases)
|
||||
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
|
||||
//! [--param-check off|warn|strict] [--compact]
|
||||
//! [--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
|
||||
@@ -62,9 +62,9 @@ struct Command {
|
||||
}
|
||||
|
||||
/// `run`'s lines in the usage.
|
||||
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n [--param-check off|warn|strict] [--quality standard|fine|finest]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--quality: how closely an STL, OBJ or 3MF --out follows curved faces, as file.export's quality: a chord of\n 2e-4, 5e-5 or 1e-5 of each body's size, never under 1 µm, default standard)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n (--profile also records where the time goes, SMLib's calls, kernel operations, features, commands and import stages,\n and prints the table to stderr: count, total, self time and longest per span; --profile-out FILE writes the spans\n as a Chrome trace for chrome://tracing or ui.perfetto.dev; SOLIDCRAFT_PROFILE=1 does the same for any command)\n";
|
||||
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n [--param-check off|warn|strict]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n (--profile also records where the time goes, SMLib's calls, kernel operations, features, commands and import stages,\n and prints the table to stderr: count, total, self time and longest per span; --profile-out FILE writes the spans\n as a Chrome trace for chrome://tracing or ui.perfetto.dev; SOLIDCRAFT_PROFILE=1 does the same for any command)\n";
|
||||
/// `eval`'s lines in the usage.
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] [--validation each_step|each_feature|off] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile, --profile-out and --validation as for run)\n";
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile and --profile-out as for run)\n";
|
||||
/// `script`'s lines in the usage.
|
||||
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.sc|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
|
||||
/// `bench-open`'s lines in the usage.
|
||||
@@ -91,7 +91,7 @@ const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [-
|
||||
const USAGE_FUZZ: &str =
|
||||
" 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.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu] [--compact]\n (--compact, SOLIDCRAFT_MCP_COMPACT=1 or the app's Preferences › AI (with --connect): compact tool output for\n agents: inspect_design summarises sketches and takes `sketches` by name and a `bodies` filter, and tool JSON\n is not pretty-printed; off by default)\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
|
||||
const USAGE_MCP: &str = " solidcraft-cli mcp [--headless] [--in design.sc|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu]\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
|
||||
/// `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.
|
||||
@@ -154,7 +154,7 @@ fn main() -> ExitCode {
|
||||
}
|
||||
};
|
||||
if let Err(e) = max_file_size(&mut args) {
|
||||
solidcraft_engine::note!("error: {e}");
|
||||
eprintln!("error: {e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
let r = match args.first().map(String::as_str) {
|
||||
@@ -235,7 +235,7 @@ fn main() -> ExitCode {
|
||||
match r {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
solidcraft_engine::note!("error: {e}");
|
||||
eprintln!("error: {e}");
|
||||
ExitCode::FAILURE
|
||||
}
|
||||
}
|
||||
@@ -318,19 +318,6 @@ fn load_checked(path: &str, check: solidcraft_engine::schema::ParamCheck) -> Res
|
||||
/// `load_checked`, the features computed under `validation` (`diagnostics.validation`), which
|
||||
/// the session keeps.
|
||||
fn load_with(path: &str, check: solidcraft_engine::schema::ParamCheck, validation: solidcraft_engine::kernel::Validation) -> Result<Session, String> {
|
||||
let s = read_session(path, check, validation)?;
|
||||
// A newer SolidCraft's file opens all the same; stderr says so, stdout stays the caller's.
|
||||
for warning in s.doc.newer_file.iter().flat_map(|file| file.warnings()) {
|
||||
eprintln!("{path}: warning: {warning}");
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
fn read_session(
|
||||
path: &str,
|
||||
check: solidcraft_engine::schema::ParamCheck,
|
||||
validation: solidcraft_engine::kernel::Validation,
|
||||
) -> Result<Session, String> {
|
||||
let mut s = Session::default();
|
||||
s.param_check = check;
|
||||
s.set_validation(validation);
|
||||
@@ -364,7 +351,7 @@ fn run_script_warning(s: &mut Session, path: &str, script: &Value) -> Result<Vec
|
||||
/// Step `step`'s `param_warnings` on stderr, one per line, named after the script at `path`.
|
||||
fn print_param_warnings(path: &str, step: usize, r: &Value) {
|
||||
for w in r.get("param_warnings").and_then(Value::as_array).into_iter().flatten() {
|
||||
solidcraft_engine::note!("{path}: step {}: {}", step + 1, w.as_str().unwrap_or_default());
|
||||
eprintln!("{path}: step {}: {}", step + 1, w.as_str().unwrap_or_default());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -451,12 +438,11 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
known_args(
|
||||
"run",
|
||||
args,
|
||||
&["--in", "--out", "--save", "--json-out", "--param-check", "--profile-out", "--validation", "--quality"],
|
||||
&["--in", "--out", "--save", "--json-out", "--param-check", "--profile-out", "--validation"],
|
||||
&["--quiet", "--results", "--timings", "--profile", "--allow-errors"],
|
||||
true,
|
||||
)?;
|
||||
let validation = validation_flag("run", args)?;
|
||||
let quality = export_quality("run", args)?;
|
||||
let base = flag(args, "--in");
|
||||
let path = positional(args);
|
||||
if path.is_none() && base.is_none() {
|
||||
@@ -498,17 +484,11 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
// A feature that fails leaves the bodies as they were before it (a parameter change can
|
||||
// break a feature without failing the command that made it): writing them would hand out
|
||||
// the wrong part with success, so fail the run unless asked to write it anyway.
|
||||
// A feature only a newer SolidCraft can build isn't a broken design: saving the design keeps it
|
||||
// and hands out no part. Exporting bodies without it would, so that still stops.
|
||||
if !args.iter().any(|a| a == "--allow-errors") {
|
||||
timeline_ok(&s, flags(args, "--out").is_empty())?;
|
||||
timeline_ok(&s)?;
|
||||
}
|
||||
for out in flags(args, "--out") {
|
||||
let mut params = json!({"path": out});
|
||||
if let Some(q) = quality {
|
||||
params["quality"] = json!(q);
|
||||
}
|
||||
s.execute("file.export", ¶ms).map_err(|e| e.to_string())?;
|
||||
s.execute("file.export", &json!({"path": out})).map_err(|e| e.to_string())?;
|
||||
}
|
||||
if let Some(p) = flag(args, "--save") {
|
||||
s.execute("file.save_as", &json!({"path": p})).map_err(|e| e.to_string())?;
|
||||
@@ -549,20 +529,6 @@ fn open_validating(
|
||||
}
|
||||
}
|
||||
|
||||
/// `--quality standard|fine|finest`: how closely the meshes `--out` writes (STL, OBJ, 3MF)
|
||||
/// follow curved faces, as `file.export`'s `quality` (#12270). Checked before anything runs;
|
||||
/// given twice it is refused, since only one quality can apply.
|
||||
fn export_quality<'a>(cmd: &str, args: &'a [String]) -> Result<Option<&'a str>, String> {
|
||||
if flags(args, "--quality").len() > 1 {
|
||||
return Err(format!("{cmd}: --quality is given more than once"));
|
||||
}
|
||||
match flag(args, "--quality") {
|
||||
Some(q) if solidcraft_engine::io::MeshQuality::from_name(q).is_some() => Ok(Some(q)),
|
||||
Some(q) => Err(format!("{cmd}: --quality must be standard, fine or finest, not `{q}`")),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
/// `--validation MODE` (`diagnostics.validation`): the default, each_feature, when not given.
|
||||
fn validation_flag(cmd: &str, args: &[String]) -> Result<solidcraft_engine::kernel::Validation, String> {
|
||||
match flag(args, "--validation") {
|
||||
@@ -595,12 +561,8 @@ fn known_args(cmd: &str, args: &[String], valued: &[&str], switches: &[&str], po
|
||||
}
|
||||
|
||||
/// Every failing timeline feature, one per line, as an error; Ok when none fails.
|
||||
fn timeline_ok(s: &Session, skip_newer_features: bool) -> Result<(), String> {
|
||||
let newer = |id: u64| {
|
||||
skip_newer_features && s.doc.feature(id).is_some_and(|f| matches!(f.kind, solidcraft_engine::doc::FeatureKind::NewerFileFeature { .. }))
|
||||
};
|
||||
let failed: Vec<String> =
|
||||
s.model.results.iter().filter(|r| !newer(r.id)).filter_map(|r| r.error.as_ref().map(|e| format!(" {}: {e}", r.name))).collect();
|
||||
fn timeline_ok(s: &Session) -> Result<(), String> {
|
||||
let failed: Vec<String> = s.model.results.iter().filter_map(|r| r.error.as_ref().map(|e| format!(" {}: {e}", r.name))).collect();
|
||||
if failed.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -611,15 +573,13 @@ fn timeline_ok(s: &Session, skip_newer_features: bool) -> Result<(), String> {
|
||||
))
|
||||
}
|
||||
|
||||
/// Runs `solidcraft-cli eval` (and its `inspect` alias), opening the input design or script with the requested `--validation` mode and reporting both `inspect.measure` and `document.inspect`.
|
||||
fn cmd_eval(args: &[String]) -> Result<(), String> {
|
||||
let json_out = json_out(args)?;
|
||||
known_args("eval", args, &["--in", "--json-out", "--profile-out", "--validation"], &["--profile"], true)?;
|
||||
let validation = validation_flag("eval", args)?;
|
||||
let mut s = open_validating(flag(args, "--in"), positional(args), solidcraft_engine::schema::ParamCheck::Off, validation)?;
|
||||
known_args("eval", args, &["--in", "--json-out", "--profile-out"], &["--profile"], true)?;
|
||||
let mut s = open(flag(args, "--in"), positional(args), solidcraft_engine::schema::ParamCheck::Off)?;
|
||||
let m = s.execute("inspect.measure", &json!({})).map_err(|e| e.to_string())?;
|
||||
let d = s.execute("document.inspect", &json!({})).map_err(|e| e.to_string())?;
|
||||
let mut out = json!({
|
||||
let out = json!({
|
||||
"body_count": m["body_count"],
|
||||
"bodies": m["bodies"],
|
||||
"total": m["total"],
|
||||
@@ -627,10 +587,6 @@ fn cmd_eval(args: &[String]) -> Result<(), String> {
|
||||
"params": d["params"],
|
||||
"sketches": d["sketches"],
|
||||
});
|
||||
if let Some(file) = &s.doc.newer_file {
|
||||
out["newer_file"] = file.report();
|
||||
out["warnings"] = json!(file.warnings());
|
||||
}
|
||||
print_json(&out, json_out)
|
||||
}
|
||||
|
||||
@@ -642,10 +598,10 @@ fn cmd_script(args: &[String]) -> Result<(), String> {
|
||||
let out = flag(args, "--out");
|
||||
let r = s.execute("timeline.export_script", &json!({"path": out})).map_err(|e| e.to_string())?;
|
||||
for what in r["omitted"].as_array().into_iter().flatten().filter_map(Value::as_str) {
|
||||
solidcraft_engine::note!("note: the script leaves out {what}");
|
||||
eprintln!("note: the script leaves out {what}");
|
||||
}
|
||||
match out {
|
||||
Some(out) => solidcraft_engine::note!("{out}: {} steps", r["steps"]),
|
||||
Some(out) => eprintln!("{out}: {} steps", r["steps"]),
|
||||
None => println!("{}", pretty(&r["script"])),
|
||||
}
|
||||
Ok(())
|
||||
@@ -676,7 +632,7 @@ fn cmd_gpu(args: &[String]) -> Result<(), String> {
|
||||
);
|
||||
match s.lands_on {
|
||||
Some(solidcraft_gpu::select::Landing::Cpu) => {
|
||||
solidcraft_engine::note!("warning: {}", solidcraft_engine::capture::CPU_WARNING);
|
||||
eprintln!("warning: {}", solidcraft_engine::capture::CPU_WARNING);
|
||||
Ok(())
|
||||
}
|
||||
Some(_) => Ok(()),
|
||||
@@ -753,7 +709,7 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
||||
std::fs::write(out, &png.bytes).map_err(|e| format!("{out}: {e}"))?;
|
||||
let mut reply = json!({"path": out, "width": w, "height": h, "bytes": png.bytes.len(), "renderer": png.renderer});
|
||||
if let Some(w) = solidcraft_engine::capture::warning(png.renderer) {
|
||||
solidcraft_engine::note!("warning: {w}");
|
||||
eprintln!("warning: {w}");
|
||||
reply["warning"] = json!(w);
|
||||
}
|
||||
println!("{}", pretty(&reply));
|
||||
@@ -836,15 +792,11 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
||||
return Err("mcp: --render-gpu installs a headless image host; a connected application owns its own device".into());
|
||||
}
|
||||
install_renderer();
|
||||
let options = solidcraft_mcp::Options::from_args_and_env(args).map_err(|e| format!("mcp: {e}"))?;
|
||||
if options.compact {
|
||||
eprintln!("solidcraft mcp: compact output (inspect_design summarises sketches; tool JSON is not pretty-printed)");
|
||||
}
|
||||
let scope = (!args.iter().any(|a| a == "--allow-any-path"))
|
||||
.then(|| solidcraft_mcp::agent_scope(flags(args, "--allow-path").into_iter().map(std::path::PathBuf::from)));
|
||||
if let Some(s) = &scope {
|
||||
let roots: Vec<String> = s.roots().iter().map(|r| r.display().to_string()).collect();
|
||||
solidcraft_engine::note!(
|
||||
eprintln!(
|
||||
"solidcraft mcp: files limited to {} and the folders of designs opened or saved here (--allow-path DIR adds one, --allow-any-path lifts the limit)",
|
||||
roots.join(", ")
|
||||
);
|
||||
@@ -852,7 +804,7 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
||||
.and_then(|p| std::path::Path::new(p).parent())
|
||||
.map(|d| if d.as_os_str().is_empty() { std::path::Path::new(".") } else { d });
|
||||
if let Some(why) = design_dir.and_then(solidcraft_engine::io::vfs::design_folder_refusal) {
|
||||
solidcraft_engine::note!("solidcraft mcp: the design's folder is left out because {why}; add it with --allow-path if you mean it");
|
||||
eprintln!("solidcraft mcp: the design's folder is left out because {why}; add it with --allow-path if you mean it");
|
||||
}
|
||||
}
|
||||
// How calls that break a command's parameter contract are treated, headless or bridged.
|
||||
@@ -866,7 +818,7 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
||||
// Serve without the app too: the tools connect once it runs, and say it isn't until then.
|
||||
let mut remote = solidcraft_mcp::Remote::new(addr, flag(args, "--token")).with_param_check(param_check);
|
||||
if let Err(e) = remote.try_connect() {
|
||||
solidcraft_engine::note!(
|
||||
eprintln!(
|
||||
"solidcraft mcp: no SolidCraft app at {addr} yet ({e}); start it with `solidcraft --control {port}` \
|
||||
(or SOLIDCRAFT_CONTROL_PORT={port}), tool calls connect once it runs"
|
||||
);
|
||||
@@ -883,9 +835,9 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
||||
(None, None) => {
|
||||
let auto = !args.iter().any(|a| a == "--headless");
|
||||
if auto && let Some(port) = discover_app() {
|
||||
solidcraft_engine::note!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
|
||||
eprintln!("solidcraft mcp: bridged to the running app at 127.0.0.1:{port} (--headless starts a session of its own)");
|
||||
let remote = solidcraft_mcp::Remote::new(&format!("127.0.0.1:{port}"), flag(args, "--token")).with_param_check(param_check);
|
||||
return serve_mcp(Box::new(remote), scope, options);
|
||||
return serve_mcp(Box::new(remote), scope);
|
||||
}
|
||||
let mut headless = solidcraft_mcp::Headless::default();
|
||||
headless.session.param_check = param_check;
|
||||
@@ -900,17 +852,13 @@ fn cmd_mcp(args: &[String]) -> Result<(), String> {
|
||||
}
|
||||
}
|
||||
};
|
||||
serve_mcp(backend, scope, options)
|
||||
serve_mcp(backend, scope)
|
||||
}
|
||||
|
||||
fn serve_mcp(
|
||||
backend: Box<dyn solidcraft_mcp::Backend>,
|
||||
scope: Option<solidcraft_engine::io::vfs::PathScope>,
|
||||
options: solidcraft_mcp::Options,
|
||||
) -> Result<(), String> {
|
||||
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).with_options(options).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string())
|
||||
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
|
||||
|
||||
@@ -188,26 +188,3 @@ fn max_file_size_raises_the_limit_and_refuses_bad_sizes() {
|
||||
assert!(!output.status.success() && !stderr.contains("too large"), "{stderr}");
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
}
|
||||
|
||||
/// #10111: a line for a stderr whose reader has gone (the pipe closed before it, as an agent may
|
||||
/// leave the MCP server's, and the app that one starts) is dropped, and the command ends as it
|
||||
/// would with stderr open: here a script that isn't there fails with 1, where `eprintln!`
|
||||
/// panicked with "failed printing to stderr" (101).
|
||||
#[test]
|
||||
fn a_closed_stderr_does_not_stop_the_cli() {
|
||||
for _ in 0..5 {
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"))
|
||||
.args(["run", "no-such-script.json"])
|
||||
.stdout(Stdio::null())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()
|
||||
.unwrap();
|
||||
drop(child.stderr.take());
|
||||
assert_eq!(child.wait().unwrap().code(), Some(1));
|
||||
}
|
||||
// The old name's alias writes its deprecation notice first: an agent still configured with
|
||||
// `solvecraft-cli mcp` must get the server, not that panic.
|
||||
let mut child = Command::new(env!("CARGO_BIN_EXE_solvecraft-cli")).arg("--help").stdout(Stdio::null()).stderr(Stdio::piped()).spawn().unwrap();
|
||||
drop(child.stderr.take());
|
||||
assert_eq!(child.wait().unwrap().code(), Some(0));
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
//! `mcp --compact` and `SOLIDCRAFT_MCP_COMPACT` turn on compact tool output; without them the
|
||||
//! server's tools are as they always were.
|
||||
|
||||
use std::io::Write;
|
||||
use std::process::{Command, Stdio};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
/// `inspect_design`'s input schema as `solidcraft-cli mcp` with `args` and `env` lists it, or
|
||||
/// the server's stderr when it does not start.
|
||||
fn inspect_schema(args: &[&str], env: Option<&str>) -> Result<Value, String> {
|
||||
let mut cmd = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli"));
|
||||
// --headless: never bridge to (and read the preference of) an app the developer has running.
|
||||
cmd.args(["mcp", "--headless", "--allow-any-path"]).args(args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped());
|
||||
match env {
|
||||
Some(v) => cmd.env("SOLIDCRAFT_MCP_COMPACT", v),
|
||||
None => cmd.env_remove("SOLIDCRAFT_MCP_COMPACT"),
|
||||
};
|
||||
let mut child = cmd.spawn().unwrap();
|
||||
{
|
||||
let mut stdin = child.stdin.take().unwrap();
|
||||
// The server may refuse to start and close stdin first.
|
||||
let _ = writeln!(stdin, "{}", json!({"jsonrpc": "2.0", "id": 1, "method": "tools/list"}));
|
||||
}
|
||||
let out = child.wait_with_output().unwrap();
|
||||
if !out.status.success() {
|
||||
return Err(String::from_utf8_lossy(&out.stderr).into_owned());
|
||||
}
|
||||
let reply: Value = serde_json::from_slice(&out.stdout).unwrap();
|
||||
let tools = reply["result"]["tools"].as_array().cloned().unwrap_or_default();
|
||||
Ok(tools.into_iter().find(|t| t["name"] == "inspect_design").map(|t| t["inputSchema"].clone()).unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn compact_output_is_off_unless_asked() {
|
||||
let plain = inspect_schema(&[], None).unwrap();
|
||||
assert_eq!(plain["properties"]["sketches"]["type"], "boolean", "{plain}");
|
||||
assert!(plain["properties"].get("bodies").is_none(), "{plain}");
|
||||
assert_eq!(inspect_schema(&[], Some("0")).unwrap(), plain);
|
||||
|
||||
for (args, env) in [(&["--compact"][..], None), (&[][..], Some("1")), (&[][..], Some("true"))] {
|
||||
let compact = inspect_schema(args, env).unwrap();
|
||||
assert!(compact["properties"]["bodies"].is_object(), "{args:?} {env:?}: {compact}");
|
||||
}
|
||||
|
||||
let e = inspect_schema(&[], Some("maybe")).unwrap_err();
|
||||
assert!(e.contains("SOLIDCRAFT_MCP_COMPACT"), "{e}");
|
||||
}
|
||||
@@ -60,25 +60,15 @@ fn mcp_connect_serves_before_the_app_runs_and_reaches_it_once_it_does() {
|
||||
let listener = TcpListener::bind(&addr).unwrap();
|
||||
let app = std::thread::spawn(move || {
|
||||
let (stream, _) = listener.accept().unwrap();
|
||||
let mut lines = BufReader::new(stream.try_clone().unwrap());
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
lines.read_line(&mut line).unwrap();
|
||||
let req: Value = serde_json::from_str(&line).unwrap();
|
||||
// The server first asks for the agents' output options (the compact preference: off).
|
||||
let options = req["method"] == "mcp.options";
|
||||
let reply = if req["token"] != json!(token) {
|
||||
json!({"id": req["id"], "ok": false, "error": format!("unauthorized: token {}", req["token"])})
|
||||
} else if options {
|
||||
json!({"id": req["id"], "ok": true, "result": {"compact": false}})
|
||||
} else {
|
||||
json!({"id": req["id"], "ok": true, "result": {"bodies": [], "method": req["method"]}})
|
||||
};
|
||||
writeln!(&stream, "{reply}").unwrap();
|
||||
if !options {
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut line = String::new();
|
||||
BufReader::new(stream.try_clone().unwrap()).read_line(&mut line).unwrap();
|
||||
let req: Value = serde_json::from_str(&line).unwrap();
|
||||
let reply = if req["token"] == json!(token) {
|
||||
json!({"id": req["id"], "ok": true, "result": {"bodies": [], "method": req["method"]}})
|
||||
} else {
|
||||
json!({"id": req["id"], "ok": false, "error": format!("unauthorized: token {}", req["token"])})
|
||||
};
|
||||
writeln!(&stream, "{reply}").unwrap();
|
||||
});
|
||||
let r = ask(4, "tools/call", measure);
|
||||
assert_ne!(r["result"]["isError"], true, "{r}");
|
||||
@@ -121,21 +111,10 @@ fn mcp_without_arguments_finds_the_running_app() {
|
||||
break (stream, line);
|
||||
}
|
||||
};
|
||||
let mut lines = BufReader::new(stream.try_clone().unwrap());
|
||||
let mut line = line;
|
||||
// The server first asks for the agents' output options (the compact preference: off).
|
||||
loop {
|
||||
let req: Value = serde_json::from_str(&line).unwrap();
|
||||
let ok = req["token"] == json!(token);
|
||||
let options = req["method"] == "mcp.options";
|
||||
let result = if options { json!({"compact": false}) } else { json!({"bodies": [], "method": req["method"]}) };
|
||||
writeln!(&stream, "{}", json!({"id": req["id"], "ok": ok, "result": result, "error": "unauthorized"})).unwrap();
|
||||
if !options {
|
||||
break;
|
||||
}
|
||||
line.clear();
|
||||
lines.read_line(&mut line).unwrap();
|
||||
}
|
||||
let 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"])
|
||||
|
||||
@@ -67,9 +67,6 @@ fn run_rejects_invalid_options_before_script_execution_or_output_writes() {
|
||||
(&["--out", "--quiet"][..], "--out needs a value"),
|
||||
(&["--ouut", "part.step"][..], "doesn't take --ouut"),
|
||||
(&["--unknown"][..], "doesn't take --unknown"),
|
||||
(&["--quality"][..], "--quality needs a value"),
|
||||
(&["--quality", "best"][..], "--quality must be standard, fine or finest, not `best`"),
|
||||
(&["--quality", "standard", "--quality", "best"][..], "--quality is given more than once"),
|
||||
];
|
||||
for existing in [false, true] {
|
||||
let saved = dir.0.join(format!("{existing}.solidcraft"));
|
||||
@@ -162,7 +159,7 @@ fn cli_loads_both_design_encodings_by_contents() {
|
||||
}
|
||||
|
||||
/// #1369: a pending concept that did not land in the numbered release must not be silently
|
||||
/// dropped on open/resave, in either encoding: it opens with a warning, and saving keeps it.
|
||||
/// 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};
|
||||
@@ -176,29 +173,34 @@ fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
|
||||
let source = dir.0.join(format!("experimental-{version}.{extension}"));
|
||||
std::fs::write(&source, &bytes).unwrap();
|
||||
let mut session = Session::default();
|
||||
// It opens, and the reply says what it is.
|
||||
let reply = session.execute("doc.open", &json!({"path": source})).unwrap();
|
||||
let notice = reply["newer_file"]["notice"].as_str().unwrap().to_string();
|
||||
assert!(notice.contains("experimental-card") && notice.starts_with("Saved by a newer SolidCraft"), "{notice}");
|
||||
assert_eq!(reply["warnings"][0], notice.as_str());
|
||||
assert_eq!(session.doc.name, "Experimental");
|
||||
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_eq!(output.status.code(), Some(0), "{error}");
|
||||
assert!(error.contains("warning: Saved by a newer SolidCraft") && error.contains("experimental-card"), "{error}");
|
||||
// Saved, it still says it has the change, and opens the same way.
|
||||
let saved = io::read_design(&std::fs::read(&destination).unwrap()).unwrap();
|
||||
assert!(saved.format_pending.iter().any(|name| name == "experimental-card"));
|
||||
assert!(saved.newer_file.is_some());
|
||||
assert!(error.contains("experimental-card") && error.contains("update SolidCraft"), "{error}");
|
||||
if existing {
|
||||
assert_eq!(std::fs::read(&backup).unwrap(), b"existing design");
|
||||
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);
|
||||
}
|
||||
@@ -206,43 +208,6 @@ fn unknown_pending_concepts_preserve_the_open_design_and_output_files() {
|
||||
}
|
||||
}
|
||||
|
||||
/// `eval` and `run` on a design one format newer, with a feature kind this version doesn't have:
|
||||
/// the rest builds, the reply names it, and the saved file still holds it, under the file's number.
|
||||
#[test]
|
||||
fn the_cli_opens_a_newer_design_and_saving_keeps_what_it_cant_read() {
|
||||
use solidcraft_engine::{Session, doc, io};
|
||||
|
||||
let dir = Scratch::new();
|
||||
let mut session = Session::default();
|
||||
session.execute("solid.box", &json!({"length": 2, "width": 3, "height": 4})).unwrap();
|
||||
let mut file: Value = serde_json::from_slice(&io::write_design(&session.doc)).unwrap();
|
||||
let newer = doc::format::format_string(doc::format::FORMAT + 1);
|
||||
file["format"] = json!(newer);
|
||||
let future = json!({"id": 900, "name": "Lattice1", "type": "gyroid_lattice", "cell": 2.5});
|
||||
file["features"].as_array_mut().unwrap().push(future.clone());
|
||||
let source = dir.0.join("newer.json");
|
||||
std::fs::write(&source, file.to_string()).unwrap();
|
||||
|
||||
let output = cli().args(["eval"]).arg(&source).output().unwrap();
|
||||
let error = String::from_utf8_lossy(&output.stderr);
|
||||
assert_eq!(output.status.code(), Some(0), "{error}");
|
||||
assert!(error.contains("warning: Saved by a newer SolidCraft") && error.contains("Lattice1"), "{error}");
|
||||
let reply: Value = serde_json::from_slice(&output.stdout).unwrap();
|
||||
assert_eq!(reply["body_count"], 1);
|
||||
assert!(reply["newer_file"]["not_understood"][0].as_str().unwrap().contains("gyroid_lattice"), "{reply}");
|
||||
assert!(reply["warnings"][0].as_str().unwrap().starts_with("Saved by a newer SolidCraft"), "{reply}");
|
||||
|
||||
for extension in ["json", "sc"] {
|
||||
let destination = dir.0.join(format!("saved.{extension}"));
|
||||
let output = cli().args(["run", "--in"]).arg(&source).arg("--save").arg(&destination).output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(0), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
let saved = io::read_design(&std::fs::read(&destination).unwrap()).unwrap();
|
||||
assert_eq!(saved.format, newer, "never a lower number");
|
||||
let again: Value = serde_json::from_slice(&io::write_design(&saved)).unwrap();
|
||||
assert_eq!(again["features"].as_array().unwrap().last().unwrap(), &future);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn known_pending_concepts_and_released_files_round_trip_with_inert_metadata() {
|
||||
use solidcraft_engine::{doc, io};
|
||||
@@ -291,38 +256,6 @@ fn results_still_save_and_export_the_script_once() {
|
||||
}
|
||||
}
|
||||
|
||||
/// The triangles of a binary STL: the count after its 80-byte header.
|
||||
fn stl_triangles(path: &Path) -> u32 {
|
||||
let bytes = std::fs::read(path).unwrap();
|
||||
u32::from_le_bytes(bytes[80..84].try_into().unwrap())
|
||||
}
|
||||
|
||||
/// #12270: `run --out part.stl --quality fine` writes the mesh at that quality, as
|
||||
/// `file.export`'s `quality` does, without a script to say so.
|
||||
#[test]
|
||||
fn run_exports_meshes_at_the_quality_asked_for() {
|
||||
let dir = Scratch::new();
|
||||
let script = dir.0.join("cylinder.json");
|
||||
std::fs::write(&script, json!({"commands": [{"command": "solid.cylinder", "params": {"diameter": 40, "height": 10}}]}).to_string()).unwrap();
|
||||
let export = |name: &str, quality: Option<&str>| {
|
||||
let stl = dir.0.join(name);
|
||||
let mut command = cli();
|
||||
command.arg("run").arg(&script).arg("--quiet").arg("--out").arg(&stl);
|
||||
if let Some(q) = quality {
|
||||
command.args(["--quality", q]);
|
||||
}
|
||||
let output = command.output().unwrap();
|
||||
assert!(output.status.success(), "{quality:?}: {}", String::from_utf8_lossy(&output.stderr));
|
||||
stl_triangles(&stl)
|
||||
};
|
||||
let default = export("default.stl", None);
|
||||
let standard = export("standard.stl", Some("standard"));
|
||||
let fine = export("fine.stl", Some("fine"));
|
||||
let finest = export("finest.stl", Some("finest"));
|
||||
assert_eq!(default, standard);
|
||||
assert!(standard < fine && fine < finest, "standard {standard}, fine {fine}, finest {finest}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn results_with_base_design_and_quiet_still_report_and_save() {
|
||||
let dir = Scratch::new();
|
||||
@@ -527,16 +460,13 @@ fn snapshot_runs_its_script_on_the_in_design() {
|
||||
}
|
||||
}
|
||||
|
||||
/// An unknown command's error suggests the id that was meant.
|
||||
/// An unknown command's error suggests the id that was meant first; other close ids (such as
|
||||
/// `drawing.open`) may follow it.
|
||||
#[test]
|
||||
fn unknown_command_suggests_the_closest_id() {
|
||||
let output = cli().args(["exec", "file.open"]).output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
assert!(
|
||||
String::from_utf8_lossy(&output.stderr).contains("did you mean `doc.open` or `panel.open`?"),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
assert!(String::from_utf8_lossy(&output.stderr).contains("did you mean `doc.open`"), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
/// #1833: `--timings` reports each command's time instead of the measurement, `--profile` adds
|
||||
|
||||
@@ -25,7 +25,6 @@ solidcraft-engine = { workspace = true }
|
||||
solidcraft-ui-egui = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
url = "2"
|
||||
percent-encoding = "2"
|
||||
|
||||
@@ -34,6 +33,7 @@ eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit"] }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
serde_json = { workspace = true }
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "Location", "Navigator", "StorageManager",
|
||||
"FileSystemHandle", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemGetFileOptions", "FileSystemGetDirectoryOptions", "FileSystemWritableFileStream",
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
<meta property="og:site_name" content="SolidCraft">
|
||||
<meta property="og:title" content="SolidCraft: try it in your browser">
|
||||
<meta property="og:description" content="Open-source parametric CAD built in Rust. Desktop, browser, and MCP for AI agents.">
|
||||
<meta property="og:url" content="https://app.solidcraft.io/">
|
||||
<meta property="og:url" content="https://solidcraft.ai/">
|
||||
<meta property="og:image" content="https://docs.solidcraft.io/og.png">
|
||||
<meta property="og:image:width" content="1280">
|
||||
<meta property="og:image:height" content="640">
|
||||
@@ -26,6 +26,15 @@
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/craft-ui-sc/OFL.txt" data-target-path="fonts/craft-ui-sc" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/craft-ui-tc/CraftUI-TC.otf" data-target-path="fonts" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/craft-ui-tc/OFL.txt" data-target-path="fonts/craft-ui-tc" />
|
||||
<!-- A drawing's Arabic, Korean and Chinese text: each fetched once, when a drawing needs it. -->
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-arabic/NotoSansArabic-Regular.ttf" data-target-path="fonts/noto-sans-arabic" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-arabic/OFL.txt" data-target-path="fonts/noto-sans-arabic" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-kr/NotoSansKR-Regular.otf" data-target-path="fonts/noto-sans-kr" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-kr/OFL.txt" data-target-path="fonts/noto-sans-kr" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-sc/NotoSansSC-Regular.otf" data-target-path="fonts/noto-sans-sc" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-sc/OFL.txt" data-target-path="fonts/noto-sans-sc" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-tc/NotoSansTC-Regular.otf" data-target-path="fonts/noto-sans-tc" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/fonts/noto-sans-tc/OFL.txt" data-target-path="fonts/noto-sans-tc" />
|
||||
<link data-trunk rel="css" href="../../assets/styles/web/loading.css" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/styles/web/loading-logo.svg" />
|
||||
<!-- Installable as an app (manifest.webmanifest); the icons are the Linux exports of the app mark. -->
|
||||
|
||||
@@ -553,7 +553,7 @@ mod web {
|
||||
/// Open a design remembered from an earlier session (a recent one, after a reload): ask once
|
||||
/// for access to the file (call in the click), read it into the engine's files and queue it
|
||||
/// to open. A file that is gone is forgotten and dropped from the recent designs.
|
||||
pub fn restore(files: &Rc<Files>, path: &str, on_opened: Option<Box<dyn FnOnce()>>) {
|
||||
pub fn restore(files: &Rc<Files>, path: &str) {
|
||||
let Some((id, name)) = parse(path) else { return };
|
||||
let (files, path, id, name) = (files.clone(), path.to_string(), id.to_string(), name.to_string());
|
||||
spawn_local(async move {
|
||||
@@ -568,9 +568,6 @@ mod web {
|
||||
files.opened.borrow_mut().push(path);
|
||||
files.wake();
|
||||
touch(&id).await;
|
||||
if let Some(done) = on_opened {
|
||||
done();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let gone = e.contains("NotFoundError") || e.contains("could not be found") || e.contains("no longer remembers");
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
//! and go out as downloads (exports, and "Download design"). Where the File System Access API
|
||||
//! exists a design also lives on the computer's disk, saved and opened through the browser's file
|
||||
//! dialogs (`disk.rs`). An autosave of unsaved work is kept in OPFS and offered again after a
|
||||
//! reload or a crash. With "keep unsaved work in this browser" on, each open design's unsaved
|
||||
//! changes are kept there instead, one copy per design (`keep.rs`, #10220).
|
||||
//! reload or a crash.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
@@ -14,7 +13,7 @@ use std::rc::Rc;
|
||||
use js_sys::{Array, Function, Object, Reflect, Uint8Array};
|
||||
use solidcraft_engine::io::vfs;
|
||||
|
||||
use crate::{disk, keep};
|
||||
use crate::disk;
|
||||
use wasm_bindgen::{JsCast as _, JsValue, closure::Closure};
|
||||
use wasm_bindgen_futures::{JsFuture, spawn_local};
|
||||
use web_sys::{
|
||||
@@ -80,16 +79,6 @@ pub struct Files {
|
||||
pub recovery: RefCell<Option<(String, Option<String>)>>,
|
||||
/// The app's context, to wake it when a browser call finishes.
|
||||
pub ctx: RefCell<Option<egui::Context>>,
|
||||
/// This page's id: the copies it keeps are named after it (`keep::entry_id`).
|
||||
pub keep_page: RefCell<String>,
|
||||
/// Kept copies stored by this page, and the design revision each holds once its write ended.
|
||||
pub kept: RefCell<std::collections::HashMap<String, Option<u64>>>,
|
||||
/// Kept copies being written or removed now (one browser call at a time per copy).
|
||||
pub keeping: RefCell<std::collections::HashSet<String>>,
|
||||
/// When (ms) the last write of a copy failed, to wait before trying again (`keep::may_write`).
|
||||
pub kept_failed: RefCell<std::collections::HashMap<String, f64>>,
|
||||
/// Copies earlier pages left, newer than their design's last save: offered at start.
|
||||
pub keep_offered: RefCell<Vec<keep::Entry>>,
|
||||
}
|
||||
|
||||
impl Files {
|
||||
@@ -299,9 +288,6 @@ pub fn start(files: &Rc<Files>) {
|
||||
let _ = JsFuture::from(p).await;
|
||||
});
|
||||
}
|
||||
let page = keep::page_id(js_sys::Date::now(), js_sys::Math::random());
|
||||
hold_page_lock(&page);
|
||||
*files.keep_page.borrow_mut() = page;
|
||||
let files = files.clone();
|
||||
spawn_local(async move {
|
||||
match disk::known_ids().await {
|
||||
@@ -314,7 +300,6 @@ pub fn start(files: &Rc<Files>) {
|
||||
files.say("This page can't use the browser's file storage (it needs https or localhost): designs are kept in local storage, a few MB at most.", false);
|
||||
}
|
||||
let mut entries = Vec::new();
|
||||
let kept = kept_copies(&all);
|
||||
let has_recovery = all.iter().any(|(n, _, _)| n == RECOVERY);
|
||||
let legacy = (!has_recovery).then(|| LEGACY_RECOVERY.into_iter().find(|l| all.iter().any(|(n, _, _)| n == l))).flatten();
|
||||
for (name, bytes, modified) in all {
|
||||
@@ -330,7 +315,7 @@ pub fn start(files: &Rc<Files>) {
|
||||
*files.recovery.borrow_mut() = Some((n, meta["path"].as_str().map(str::to_string)));
|
||||
continue;
|
||||
}
|
||||
if name != RECOVERY && keep::parse_name(&name).is_none() {
|
||||
if name != RECOVERY {
|
||||
entries.push(Entry { name: name.clone(), size: bytes.len() as u64, modified });
|
||||
}
|
||||
vfs::insert(&format!("{OPFS}{name}"), bytes);
|
||||
@@ -341,7 +326,6 @@ pub fn start(files: &Rc<Files>) {
|
||||
if !vfs::exists(&format!("{OPFS}{RECOVERY}")) {
|
||||
*files.recovery.borrow_mut() = None;
|
||||
}
|
||||
sort_kept(&files, kept, &live_pages().await);
|
||||
}
|
||||
Err(e) => files.say(format!("browser storage: {e}"), true),
|
||||
}
|
||||
@@ -359,7 +343,7 @@ pub fn flush(files: &Rc<Files>) {
|
||||
{
|
||||
let mut e = files.entries.borrow_mut();
|
||||
e.retain(|x| x.name != name);
|
||||
if name != RECOVERY && name != RECOVERY_META && keep::parse_name(&name).is_none() {
|
||||
if name != RECOVERY && name != RECOVERY_META {
|
||||
e.push(Entry { name: name.clone(), size: bytes.len() as u64, modified: js_sys::Date::now() });
|
||||
e.sort_by_key(|a| a.name.to_lowercase());
|
||||
}
|
||||
@@ -489,15 +473,7 @@ pub fn recovery_path() -> String {
|
||||
/// A recovery file lives in OPFS, but its relative links belong to the original design's
|
||||
/// folder (which can be on disk). Resolve them before Open sees the temporary file location.
|
||||
pub fn prepare_recovery(original_path: Option<&str>) -> Result<String, String> {
|
||||
prepare_copy(recovery_path(), original_path)
|
||||
}
|
||||
|
||||
/// The same for a kept copy (#10220).
|
||||
pub fn prepare_kept(id: &str, original_path: Option<&str>) -> Result<String, String> {
|
||||
prepare_copy(format!("{OPFS}{}", keep::design_file(id)), original_path)
|
||||
}
|
||||
|
||||
fn prepare_copy(path: String, original_path: Option<&str>) -> Result<String, String> {
|
||||
let path = recovery_path();
|
||||
let bytes = vfs::read(&path, solidcraft_engine::io::MAX_DESIGN_BYTES).map_err(|e| e.to_string())?;
|
||||
let mut doc = solidcraft_engine::io::read_design(&bytes).map_err(|e| e.to_string())?;
|
||||
solidcraft_engine::resolve_design_links(&mut doc, original_path);
|
||||
@@ -670,149 +646,3 @@ pub fn take_drops(files: &Rc<Files>, raw: &mut egui::RawInput, theme: bool) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Hold this page's browser lock while it runs, so other tabs know its kept copies are in use
|
||||
/// (`keep.rs`). Browsers without the Web Locks API hold none; their copies are then offered by
|
||||
/// any later page.
|
||||
fn hold_page_lock(page: &str) {
|
||||
let Some(locks) = web_locks() else { return };
|
||||
let Ok(request) = Reflect::get(&locks, &"request".into()).and_then(|f| f.dyn_into::<Function>()) else { return };
|
||||
// A promise that never settles: the lock is let go only when the page goes.
|
||||
let hold = Closure::<dyn FnMut(JsValue) -> js_sys::Promise>::new(|_lock: JsValue| js_sys::Promise::new(&mut |_, _| {}));
|
||||
let name = format!("{}{page}", keep::LOCK_PREFIX);
|
||||
if let Err(e) = request.call2(&locks, &name.into(), hold.as_ref().unchecked_ref()) {
|
||||
log::info!("keep: no page lock: {}", err(e));
|
||||
}
|
||||
// The callback lives as long as the page.
|
||||
hold.forget();
|
||||
}
|
||||
|
||||
fn web_locks() -> Option<JsValue> {
|
||||
let nav = web_sys::window()?.navigator();
|
||||
Reflect::get(&nav, &"locks".into()).ok().filter(|l| l.is_object())
|
||||
}
|
||||
|
||||
/// The pages still open (in this browser profile) that hold their lock.
|
||||
async fn live_pages() -> Vec<String> {
|
||||
let query = async {
|
||||
let locks = web_locks().ok_or("no Web Locks")?;
|
||||
let f: Function = Reflect::get(&locks, &"query".into()).map_err(err)?.dyn_into().map_err(|_| "no query".to_string())?;
|
||||
let p: js_sys::Promise = f.call0(&locks).map_err(err)?.dyn_into().map_err(|_| "no promise".to_string())?;
|
||||
let state = JsFuture::from(p).await.map_err(err)?;
|
||||
let held: Array = Reflect::get(&state, &"held".into()).map_err(err)?.dyn_into().map_err(|_| "no list".to_string())?;
|
||||
Ok::<_, String>(
|
||||
held.iter()
|
||||
.take(10_000)
|
||||
.filter_map(|l| Reflect::get(&l, &"name".into()).ok()?.as_string())
|
||||
.filter_map(|n| n.strip_prefix(keep::LOCK_PREFIX).map(str::to_string))
|
||||
.collect(),
|
||||
)
|
||||
};
|
||||
query.await.unwrap_or_else(|e| {
|
||||
log::info!("keep: open pages unknown ({e})");
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
|
||||
/// The kept copies among the stored files: (copy, when its design's file was last written), and
|
||||
/// the ids of designs stored without their metadata.
|
||||
struct Found {
|
||||
copies: Vec<(keep::Entry, Option<f64>)>,
|
||||
orphans: Vec<String>,
|
||||
}
|
||||
|
||||
fn kept_copies(all: &[(String, Vec<u8>, f64)]) -> Found {
|
||||
let modified = |path: &str| path.strip_prefix(OPFS).and_then(|n| all.iter().find(|(name, _, _)| name == n)).map(|(_, _, m)| *m);
|
||||
let mut copies = Vec::new();
|
||||
let mut designs = Vec::new();
|
||||
for (name, bytes, _) in all {
|
||||
match keep::parse_name(name) {
|
||||
Some((id, keep::Part::Meta)) => {
|
||||
if let Some(e) = keep::Entry::parse(id, bytes) {
|
||||
let saved = e.path.as_deref().and_then(modified);
|
||||
copies.push((e, saved));
|
||||
}
|
||||
}
|
||||
Some((id, keep::Part::Design)) => designs.push(id.to_string()),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
// A copy is whole only with its design.
|
||||
copies.retain(|(e, _)| designs.contains(&e.id));
|
||||
let orphans = designs.into_iter().filter(|id| !copies.iter().any(|(e, _)| &e.id == id)).collect();
|
||||
Found { copies, orphans }
|
||||
}
|
||||
|
||||
/// Offer the copies earlier pages left that hold unsaved work, and remove those their design's
|
||||
/// later save made stale (and parts without the rest), leaving open pages' copies alone.
|
||||
fn sort_kept(files: &Rc<Files>, found: Found, live: &[String]) {
|
||||
let mut offered = Vec::new();
|
||||
let mut gone = Vec::new();
|
||||
for (e, saved) in found.copies {
|
||||
match keep::fate(&e, live, saved) {
|
||||
keep::Fate::Offer => offered.push(e),
|
||||
keep::Fate::Drop => gone.push(e.id),
|
||||
keep::Fate::Leave => {}
|
||||
}
|
||||
}
|
||||
gone.extend(found.orphans.into_iter().filter(|id| !keep::page_of(id).is_some_and(|p| live.iter().any(|l| l == p))));
|
||||
keep::order_offered(&mut offered);
|
||||
*files.keep_offered.borrow_mut() = offered;
|
||||
for id in gone {
|
||||
remove_kept(files, &id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The path (in the engine's files) a kept copy opens from.
|
||||
pub fn kept_path(id: &str) -> String {
|
||||
format!("{OPFS}{}", keep::design_file(id))
|
||||
}
|
||||
|
||||
/// Write the copy of a design with unsaved changes (`bytes`, described by `entry`) that holds
|
||||
/// its revision `revision`. Its metadata goes last, so a copy is never offered half written.
|
||||
pub fn write_kept(files: &Rc<Files>, entry: &keep::Entry, bytes: Vec<u8>, revision: u64) {
|
||||
let id = entry.id.clone();
|
||||
if !files.keeping.borrow_mut().insert(id.clone()) {
|
||||
return;
|
||||
}
|
||||
files.kept.borrow_mut().entry(id.clone()).or_insert(None);
|
||||
let meta = entry.to_json();
|
||||
let files = files.clone();
|
||||
spawn_local(async move {
|
||||
let r = async {
|
||||
write_file(&keep::design_file(&id), &bytes).await?;
|
||||
write_file(&keep::meta_file(&id), meta.as_bytes()).await
|
||||
};
|
||||
match r.await {
|
||||
Ok(()) => {
|
||||
files.kept.borrow_mut().insert(id.clone(), Some(revision));
|
||||
files.kept_failed.borrow_mut().remove(&id);
|
||||
}
|
||||
Err(e) => {
|
||||
files.kept_failed.borrow_mut().insert(id.clone(), js_sys::Date::now());
|
||||
files.say(format!("keeping unsaved work in this browser: {e}"), true)
|
||||
}
|
||||
}
|
||||
files.keeping.borrow_mut().remove(&id);
|
||||
files.wake();
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove kept copy `id` (its design was saved, closed, discarded or restored).
|
||||
pub fn remove_kept(files: &Rc<Files>, id: &str) {
|
||||
let id = id.to_string();
|
||||
if !files.keeping.borrow_mut().insert(id.clone()) {
|
||||
return;
|
||||
}
|
||||
let _ = vfs::remove(&kept_path(&id));
|
||||
files.kept_failed.borrow_mut().remove(&id);
|
||||
let files = files.clone();
|
||||
spawn_local(async move {
|
||||
// The metadata first: what's left without it is never offered.
|
||||
let _ = remove_file(&keep::meta_file(&id)).await;
|
||||
let _ = remove_file(&keep::design_file(&id)).await;
|
||||
files.kept.borrow_mut().remove(&id);
|
||||
files.keeping.borrow_mut().remove(&id);
|
||||
files.wake();
|
||||
});
|
||||
}
|
||||
@@ -23,9 +23,9 @@ fn prompt() {
|
||||
let _ = js_sys::Reflect::set(&window, &OFFER.into(), &JsValue::NULL);
|
||||
}
|
||||
let shown = js_sys::Reflect::get(&offer, &"prompt".into())
|
||||
.and_then(|f| f.dyn_into::<js_sys::Function>())
|
||||
.and_then(|f| f.dyn_into::<js_sys::Function>().map_err(JsValue::from))
|
||||
.and_then(|f| f.call0(&offer))
|
||||
.and_then(|p| p.dyn_into::<js_sys::Promise>());
|
||||
.and_then(|p| p.dyn_into::<js_sys::Promise>().map_err(JsValue::from));
|
||||
match shown {
|
||||
// Accepted or dismissed, the browser handles it; a refusal is only logged.
|
||||
Ok(p) => wasm_bindgen_futures::spawn_local(async move {
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
//! Keep unsaved work in this browser (#10220): the decisions, in plain Rust (tested natively).
|
||||
//!
|
||||
//! With Preferences › General › "Keep unsaved work in this browser instead of asking when the tab
|
||||
//! closes" on, each open design with unsaved changes has a copy in the browser's private storage:
|
||||
//! `keep-<id>.sc` (the design) and `keep-<id>.json` (its name, its file and when it last
|
||||
//! changed). The copy is written shortly after each change ([`due`]) and again when the page is
|
||||
//! hidden, so it is already current when the tab closes; the page then doesn't ask through the
|
||||
//! browser's "leave site?" box ([`asks_before_unload`]). A design that is saved, closed or
|
||||
//! discarded has its copy removed. The next visit offers the copies newer than their design's
|
||||
//! last save ([`fate`]) in SolidCraft's own window, answered with [`plan`].
|
||||
//!
|
||||
//! An id is `<page>-<n>`: the page that wrote it (each page load has its own) and the design's
|
||||
//! number in that page. A page holds a browser lock named after it while it runs, so another tab
|
||||
//! never offers, or removes, the copies of a page that is still open.
|
||||
#![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
/// The start of every kept file's name.
|
||||
pub const PREFIX: &str = "keep-";
|
||||
/// The browser lock a page holds while it runs: this, then its page id.
|
||||
pub const LOCK_PREFIX: &str = "solidcraft.keep.";
|
||||
/// A copy is written once the design has been still this long (ms)…
|
||||
pub const QUIET_MS: f64 = 800.0;
|
||||
/// …or at the latest this long after the first change it doesn't hold yet (ms).
|
||||
pub const MAX_WAIT_MS: f64 = 4000.0;
|
||||
/// The most copies offered at once (the rest wait for the next visit).
|
||||
pub const MAX_OFFERED: usize = 32;
|
||||
const MAX_ID: usize = 64;
|
||||
const MAX_NAME: usize = 256;
|
||||
const MAX_PATH: usize = 4096;
|
||||
/// Copies' metadata is small: a damaged or hostile one is not read.
|
||||
pub const MAX_META_BYTES: usize = 64 * 1024;
|
||||
|
||||
/// Ids are short and safe in a file name: ASCII letters, digits and dashes, with a page part.
|
||||
pub fn valid_id(id: &str) -> bool {
|
||||
!id.is_empty() && id.len() <= MAX_ID && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') && page_of(id).is_some()
|
||||
}
|
||||
|
||||
/// The page that wrote copy `id`.
|
||||
pub fn page_of(id: &str) -> Option<&str> {
|
||||
id.rsplit_once('-').map(|(page, _)| page).filter(|p| !p.is_empty())
|
||||
}
|
||||
|
||||
/// This page's id, from the time it started (ms since 1970) and a random number in 0..1:
|
||||
/// letters and digits only.
|
||||
pub fn page_id(now_ms: f64, random: f64) -> String {
|
||||
let now = if now_ms.is_finite() && now_ms > 0.0 { now_ms.min(1e15) as u64 } else { 0 };
|
||||
let random = if random.is_finite() { (random.clamp(0.0, 1.0) * f64::from(0xff_ffff)) as u64 } else { 0 };
|
||||
format!("{now:x}{random:06x}")
|
||||
}
|
||||
|
||||
/// The copy of design `uid` (`Session::uid`) open in page `page`.
|
||||
pub fn entry_id(page: &str, uid: u64) -> String {
|
||||
format!("{page}-{uid}")
|
||||
}
|
||||
|
||||
/// The stored file names of copy `id`: its design and its metadata.
|
||||
pub fn design_file(id: &str) -> String {
|
||||
format!("{PREFIX}{id}.{}", solidcraft_engine::io::DESIGN_EXT)
|
||||
}
|
||||
pub fn meta_file(id: &str) -> String {
|
||||
format!("{PREFIX}{id}.json")
|
||||
}
|
||||
|
||||
/// Which part of a copy a stored file is.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Part {
|
||||
Design,
|
||||
Meta,
|
||||
}
|
||||
|
||||
/// A stored file's copy id and part; `None` for every other file.
|
||||
pub fn parse_name(name: &str) -> Option<(&str, Part)> {
|
||||
let rest = name.strip_prefix(PREFIX)?;
|
||||
let (id, part) = if let Some(id) = rest.strip_suffix(".json") {
|
||||
(id, Part::Meta)
|
||||
} else {
|
||||
(rest.strip_suffix(solidcraft_engine::io::DESIGN_EXT)?.strip_suffix('.')?, Part::Design)
|
||||
};
|
||||
valid_id(id).then_some((id, part))
|
||||
}
|
||||
|
||||
/// A kept copy, as its metadata describes it.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Entry {
|
||||
pub id: String,
|
||||
/// The design's name.
|
||||
pub name: String,
|
||||
/// Its file (`None`: never saved).
|
||||
pub path: Option<String>,
|
||||
/// When the design last changed (ms since 1970).
|
||||
pub changed_at: f64,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn to_json(&self) -> String {
|
||||
json!({"name": self.name, "path": self.path, "changed_at": self.changed_at}).to_string()
|
||||
}
|
||||
|
||||
/// Read copy `id`'s metadata. Anything malformed, oversized or not finite is no copy.
|
||||
pub fn parse(id: &str, bytes: &[u8]) -> Option<Entry> {
|
||||
if !valid_id(id) || bytes.len() > MAX_META_BYTES {
|
||||
return None;
|
||||
}
|
||||
let v: Value = serde_json::from_slice(bytes).ok()?;
|
||||
let name = v.get("name")?.as_str()?.chars().take(MAX_NAME).collect::<String>();
|
||||
let path = match v.get("path") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(p) => Some(p.as_str().filter(|p| !p.is_empty() && p.len() <= MAX_PATH)?.to_string()),
|
||||
};
|
||||
let changed_at = v.get("changed_at")?.as_f64().filter(|t| t.is_finite() && *t >= 0.0)?;
|
||||
Some(Entry { id: id.to_string(), name, path, changed_at })
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a copy that last changed at `changed_at` holds work its design's file doesn't:
|
||||
/// the file was last written at `saved_at` (ms since 1970; `None` or unknown: never saved, or
|
||||
/// the time can't be read, so the copy is kept rather than lost).
|
||||
pub fn newer_than_save(changed_at: f64, saved_at: Option<f64>) -> bool {
|
||||
match saved_at {
|
||||
Some(saved) if saved.is_finite() && saved > 0.0 => changed_at > saved,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// What the next visit does with a copy it finds.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Fate {
|
||||
/// Offer it: "Restore unsaved design…?".
|
||||
Offer,
|
||||
/// Its design was saved after it: remove it without asking.
|
||||
Drop,
|
||||
/// Its page is still open (another tab): leave it alone.
|
||||
Leave,
|
||||
}
|
||||
|
||||
/// The copy `entry`, whose design's file was last written at `saved_at`, while the pages
|
||||
/// `live` are open.
|
||||
pub fn fate(entry: &Entry, live: &[String], saved_at: Option<f64>) -> Fate {
|
||||
if page_of(&entry.id).is_some_and(|p| live.iter().any(|l| l == p)) {
|
||||
Fate::Leave
|
||||
} else if newer_than_save(entry.changed_at, saved_at) {
|
||||
Fate::Offer
|
||||
} else {
|
||||
Fate::Drop
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies to offer first: the most recently changed, at most [`MAX_OFFERED`].
|
||||
pub fn order_offered(entries: &mut Vec<Entry>) {
|
||||
entries.sort_by(|a, b| b.changed_at.total_cmp(&a.changed_at).then_with(|| a.id.cmp(&b.id)));
|
||||
entries.truncate(MAX_OFFERED);
|
||||
}
|
||||
|
||||
/// Whether a design's copy is due to be written at `now`: the design last changed at
|
||||
/// `last_change`, and the copy has lacked a change since `first_unwritten` (all ms).
|
||||
pub fn due(now: f64, first_unwritten: f64, last_change: f64) -> bool {
|
||||
now - last_change >= QUIET_MS || now - first_unwritten >= MAX_WAIT_MS
|
||||
}
|
||||
|
||||
/// How long after a failed write of a design's copy it isn't tried again (ms).
|
||||
pub const RETRY_MS: f64 = 30_000.0;
|
||||
|
||||
/// Whether a copy whose write last failed at `failed_at` (ms; `None`: it hasn't) may be written
|
||||
/// at `now`: not before [`RETRY_MS`] has passed, so a full or denied store isn't hit every tick.
|
||||
pub fn may_write(now: f64, failed_at: Option<f64>) -> bool {
|
||||
failed_at.is_none_or(|t| !(now >= t) || now - t >= RETRY_MS)
|
||||
}
|
||||
|
||||
/// Whether `wanted` is the design now open, given the open design's path.
|
||||
pub fn is_open(open: Option<&str>, wanted: &str) -> bool {
|
||||
open == Some(wanted)
|
||||
}
|
||||
|
||||
/// Whether leaving the page asks through the browser's "leave site?" box: there is unsaved work,
|
||||
/// and either the user still wants to be asked or it isn't all kept in the browser yet (with the
|
||||
/// keep preference off it never is, so there the box always stays).
|
||||
pub fn asks_before_unload(persists: bool, keep: bool, also_ask: bool, any_unsaved: bool, all_kept: bool) -> bool {
|
||||
persists && any_unsaved && (also_ask || !(keep && all_kept))
|
||||
}
|
||||
|
||||
/// An answer in the "Restore unsaved design?" window.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Answer {
|
||||
/// Open the copy, with its unsaved changes, as the design it was (saving goes to its file).
|
||||
Restore,
|
||||
/// Open the design's file as last saved; the copy goes.
|
||||
OpenSaved,
|
||||
/// The copy goes.
|
||||
Discard,
|
||||
}
|
||||
|
||||
/// What an answer does.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Plan {
|
||||
/// Open the copy (and, once it is open, give it back its name and file).
|
||||
pub open_copy: bool,
|
||||
/// Open this saved file.
|
||||
pub open_saved: Option<String>,
|
||||
/// Remove the copy.
|
||||
pub drop_copy: bool,
|
||||
}
|
||||
|
||||
/// What `answer` does to copy `entry`; `saved_exists`: its file can still be opened. `None`: the
|
||||
/// answer isn't offered for it (Open saved version of a design never saved, or gone).
|
||||
pub fn plan(answer: Answer, entry: &Entry, saved_exists: bool) -> Option<Plan> {
|
||||
match answer {
|
||||
Answer::Restore => Some(Plan { open_copy: true, open_saved: None, drop_copy: true }),
|
||||
Answer::OpenSaved => {
|
||||
let path = entry.path.clone().filter(|_| saved_exists)?;
|
||||
Some(Plan { open_copy: false, open_saved: Some(path), drop_copy: true })
|
||||
}
|
||||
Answer::Discard => Some(Plan { open_copy: false, open_saved: None, drop_copy: true }),
|
||||
}
|
||||
}
|
||||
|
||||
/// The answers the window offers for `entry`, in order.
|
||||
pub fn answers(entry: &Entry, saved_exists: bool) -> Vec<Answer> {
|
||||
[Answer::Restore, Answer::OpenSaved, Answer::Discard].into_iter().filter(|a| plan(*a, entry, saved_exists).is_some()).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(id: &str, changed_at: f64, path: Option<&str>) -> Entry {
|
||||
Entry { id: id.into(), name: "Bracket".into(), path: path.map(str::to_string), changed_at }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_write_waits_before_the_next_try() {
|
||||
assert!(may_write(1000.0, None));
|
||||
assert!(!may_write(1000.0 + RETRY_MS - 1.0, Some(1000.0)));
|
||||
assert!(may_write(1000.0 + RETRY_MS, Some(1000.0)));
|
||||
// A clock that went back doesn't block for good.
|
||||
assert!(may_write(10.0, Some(1000.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_design_counts_as_opened_only_at_its_own_path() {
|
||||
assert!(is_open(Some("a.sc"), "a.sc"));
|
||||
assert!(!is_open(Some("b.sc"), "a.sc"));
|
||||
assert!(!is_open(None, "a.sc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copies_are_named_by_page_and_design_and_nothing_else_reads_as_one() {
|
||||
let page = page_id(1_700_000_000_000.0, 0.5);
|
||||
assert!(page.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
let id = entry_id(&page, 7);
|
||||
assert!(valid_id(&id));
|
||||
assert_eq!(page_of(&id), Some(page.as_str()));
|
||||
assert_eq!(parse_name(&design_file(&id)), Some((id.as_str(), Part::Design)));
|
||||
assert_eq!(parse_name(&meta_file(&id)), Some((id.as_str(), Part::Meta)));
|
||||
for other in ["part.sc", "recovery.sc", "recovery.json", "keep-.sc", "keep-x.sc", "keep-a/b-1.sc", "keep-a-1.step", "keep-a-1sc"] {
|
||||
assert_eq!(parse_name(other), None, "{other}");
|
||||
}
|
||||
// Hostile clocks still give a usable id.
|
||||
for (now, random) in [(f64::NAN, f64::NAN), (f64::INFINITY, -1.0), (-5.0, 9.0)] {
|
||||
assert!(valid_id(&entry_id(&page_id(now, random), 1)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_round_trips_and_hostile_metadata_is_no_copy() {
|
||||
let e = entry("a1-3", 1234.0, Some("/opfs/Bracket.sc"));
|
||||
assert_eq!(Entry::parse("a1-3", e.to_json().as_bytes()), Some(e.clone()));
|
||||
let never_saved = entry("a1-4", 99.0, None);
|
||||
assert_eq!(Entry::parse("a1-4", never_saved.to_json().as_bytes()), Some(never_saved));
|
||||
for bad in [
|
||||
"",
|
||||
"[]",
|
||||
r#"{"name":"x","changed_at":"soon"}"#,
|
||||
r#"{"name":"x","changed_at":-1}"#,
|
||||
r#"{"name":"x","changed_at":1e999}"#,
|
||||
r#"{"name":"x","path":7,"changed_at":1}"#,
|
||||
r#"{"name":"x","path":"","changed_at":1}"#,
|
||||
r#"{"changed_at":1}"#,
|
||||
] {
|
||||
assert_eq!(Entry::parse("a1-3", bad.as_bytes()), None, "{bad}");
|
||||
}
|
||||
assert_eq!(Entry::parse("../x", e.to_json().as_bytes()), None);
|
||||
assert_eq!(Entry::parse("a1-3", &vec![b' '; MAX_META_BYTES + 1]), None);
|
||||
let long = json!({"name": "n".repeat(10_000), "changed_at": 1}).to_string();
|
||||
assert_eq!(Entry::parse("a1-3", long.as_bytes()).map(|e| e.name.len()), Some(MAX_NAME));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_copy_newer_than_the_last_save_is_offered_an_older_one_dropped_an_open_pages_left() {
|
||||
let e = entry("p1-1", 2000.0, Some("/opfs/Bracket.sc"));
|
||||
// Changed after the file was last written: offered.
|
||||
assert_eq!(fate(&e, &[], Some(1000.0)), Fate::Offer);
|
||||
// Saved since (in this tab or another): nothing to offer.
|
||||
assert_eq!(fate(&e, &[], Some(2000.0)), Fate::Drop);
|
||||
assert_eq!(fate(&e, &[], Some(3000.0)), Fate::Drop);
|
||||
// Never saved, or the save time can't be read: kept rather than lost.
|
||||
for saved in [None, Some(0.0), Some(f64::NAN), Some(f64::INFINITY), Some(-4.0)] {
|
||||
assert_eq!(fate(&e, &[], saved), Fate::Offer, "{saved:?}");
|
||||
}
|
||||
// Its page is still open in another tab: neither offered nor removed.
|
||||
assert_eq!(fate(&e, &["p1".into()], Some(1000.0)), Fate::Leave);
|
||||
assert_eq!(fate(&e, &["p1".into()], Some(3000.0)), Fate::Leave);
|
||||
assert_eq!(fate(&e, &["p2".into()], Some(1000.0)), Fate::Offer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_newest_copies_are_offered_first_and_at_most_a_windowful() {
|
||||
let mut list: Vec<Entry> = (0..40).map(|i| entry(&format!("p-{i}"), f64::from(i), None)).collect();
|
||||
order_offered(&mut list);
|
||||
assert_eq!(list.len(), MAX_OFFERED);
|
||||
assert_eq!(list.first().map(|e| e.id.as_str()), Some("p-39"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_copy_is_written_once_the_design_is_still_or_after_the_longest_wait() {
|
||||
// Still changing, not waited long: not yet.
|
||||
assert!(!due(1000.0, 900.0, 900.0));
|
||||
// Still for QUIET_MS: due.
|
||||
assert!(due(900.0 + QUIET_MS, 900.0, 900.0));
|
||||
// Changing all the time: due after MAX_WAIT_MS anyway.
|
||||
assert!(!due(5000.0, 2000.0, 4900.0));
|
||||
assert!(due(2000.0 + MAX_WAIT_MS, 2000.0, 2000.0 + MAX_WAIT_MS - 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_browser_asks_before_unload_unless_told_not_to_and_every_copy_is_current() {
|
||||
// Keeping off: exactly as before, unsaved work asks, whatever the second option says.
|
||||
assert!(asks_before_unload(true, false, true, true, true));
|
||||
assert!(asks_before_unload(true, false, true, true, false));
|
||||
assert!(asks_before_unload(true, false, false, true, true));
|
||||
// Keeping on, asking on (the default): both, the box and the copy.
|
||||
assert!(asks_before_unload(true, true, true, true, true));
|
||||
// Keeping on, asking off, every unsaved design kept: no browser box.
|
||||
assert!(!asks_before_unload(true, true, false, true, true));
|
||||
// Keeping on, asking off, a copy still being written (or failed): the browser still asks.
|
||||
assert!(asks_before_unload(true, true, false, true, false));
|
||||
// Nothing unsaved, or an embedded page that keeps nothing: never.
|
||||
assert!(!asks_before_unload(true, true, true, false, false));
|
||||
assert!(!asks_before_unload(true, false, true, false, false));
|
||||
assert!(!asks_before_unload(false, false, true, true, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_open_saved_and_discard_each_do_one_thing_and_always_remove_the_copy() {
|
||||
let saved = entry("p-1", 5.0, Some("/opfs/Bracket.sc"));
|
||||
assert_eq!(plan(Answer::Restore, &saved, true), Some(Plan { open_copy: true, open_saved: None, drop_copy: true }));
|
||||
assert_eq!(
|
||||
plan(Answer::OpenSaved, &saved, true),
|
||||
Some(Plan { open_copy: false, open_saved: Some("/opfs/Bracket.sc".into()), drop_copy: true })
|
||||
);
|
||||
assert_eq!(plan(Answer::Discard, &saved, true), Some(Plan { open_copy: false, open_saved: None, drop_copy: true }));
|
||||
assert_eq!(answers(&saved, true), [Answer::Restore, Answer::OpenSaved, Answer::Discard]);
|
||||
// A design never saved, or whose file is gone, has no saved version to open.
|
||||
let never = entry("p-2", 5.0, None);
|
||||
assert_eq!(plan(Answer::OpenSaved, &never, true), None);
|
||||
assert_eq!(plan(Answer::OpenSaved, &saved, false), None);
|
||||
assert_eq!(answers(&never, false), [Answer::Restore, Answer::Discard]);
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ impl Download {
|
||||
}
|
||||
if solidcraft_ui_egui::documents::any_dirty(app) {
|
||||
let mut answer = None;
|
||||
let closed = ui_kit::Window::new(egui::Id::unique("open_link"), "status_warning", solidcraft_ui_egui::i18n::tr("web.open_link"))
|
||||
let closed = ui_kit::Window::new(egui::Id::new("open_link"), "status_warning", solidcraft_ui_egui::i18n::tr("web.open_link"))
|
||||
.required()
|
||||
.show_body(ctx, |ui| {
|
||||
ui.label(solidcraft_ui_egui::i18n::tr("web.open_link_confirm"));
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
//! Runs the same [`solidcraft_ui_egui::SolveApp`] as the desktop app through eframe's web runner
|
||||
//! (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release` from this
|
||||
//! directory (output in `dist/web`). URL flags: `?webgl` forces WebGL2 (remembered until
|
||||
//! `?webgpu`), `?sample` opens the sample part, `?embed=editor` runs it as an editing embed in a
|
||||
//! host page (no application bar or start page; nothing kept in the browser; #8517).
|
||||
//! `?webgpu`), `?sample` opens the sample part.
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -19,13 +18,16 @@ mod easyprint;
|
||||
mod files;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod install;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod keep;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod linked;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod space_mouse;
|
||||
|
||||
/// The folder the page serves its fonts from: index.html copies each there, and a drawing's text
|
||||
/// fetches its fonts from it (#4890).
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
const FONTS: &str = "fonts";
|
||||
|
||||
/// 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.)
|
||||
@@ -45,8 +47,6 @@ fn query_flag(query: &str, name: &str) -> bool {
|
||||
struct Launch {
|
||||
open: Option<String>,
|
||||
embed: bool,
|
||||
/// `?embed=editor` (#8517): an editing embed in a host page that keeps the files itself.
|
||||
editor: bool,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
@@ -57,19 +57,16 @@ impl Launch {
|
||||
for (key, value) in url::form_urlencoded::parse(query.trim_start_matches('?').as_bytes()) {
|
||||
match key.as_ref() {
|
||||
"open" if launch.open.is_none() => launch.open = Some(value.into_owned()),
|
||||
"embed" if value == "editor" => launch.editor = true,
|
||||
"embed" => embed = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
launch.embed = embed && launch.open.is_some() && !launch.editor;
|
||||
launch.embed = embed && launch.open.is_some();
|
||||
launch
|
||||
}
|
||||
|
||||
/// The browser's own storage (designs, autosave, recovery, preferences) is used: not when
|
||||
/// embedded, where the host keeps what is kept.
|
||||
fn persists(&self) -> bool {
|
||||
!self.embed && !self.editor
|
||||
!self.embed
|
||||
}
|
||||
|
||||
fn full_app_url(&self, mut url: url::Url) -> String {
|
||||
@@ -186,7 +183,6 @@ mod web {
|
||||
use crate::api::Api;
|
||||
use crate::disk::{self, SaveTarget};
|
||||
use crate::files::{self, Files, Intent};
|
||||
use crate::keep;
|
||||
use crate::space_mouse::SpaceMouse;
|
||||
|
||||
const CANVAS_ID: &str = "solidcraft_canvas";
|
||||
@@ -246,9 +242,7 @@ mod web {
|
||||
}
|
||||
|
||||
/// The browser's "leave site? changes you made may not be saved" prompt while a design has
|
||||
/// unsaved changes (the page can't show its own dialog when it is being closed). With "keep
|
||||
/// unsaved work in this browser" on and "still ask" off, there is nothing to ask once every
|
||||
/// unsaved design's copy is current (`keep::asks_before_unload`, #10220).
|
||||
/// unsaved changes (the page can't show its own dialog when it is being closed).
|
||||
fn ask_before_unload(window: &web_sys::Window) {
|
||||
let on_unload = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::BeforeUnloadEvent)>::new(|e: web_sys::BeforeUnloadEvent| {
|
||||
if UNSAVED.with(std::cell::Cell::get) {
|
||||
@@ -261,50 +255,6 @@ mod web {
|
||||
on_unload.forget();
|
||||
}
|
||||
|
||||
/// With "keep unsaved work in this browser" on, the page brings every unsaved design's copy up
|
||||
/// to date when it is hidden or goes (`pagehide`, `visibilitychange`): the browser may not
|
||||
/// wait for that write, so the copies are written shortly after each change as well
|
||||
/// (`Shell::keep_tick`, #10220).
|
||||
fn keep_when_hidden(window: &web_sys::Window, runner: &eframe::WebRunner) {
|
||||
let keep_now = {
|
||||
let runner = runner.clone();
|
||||
move || {
|
||||
if let Some(mut shell) = runner.app_mut::<Shell>() {
|
||||
shell.keep_tick(None, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
let on_hide = {
|
||||
let keep_now = keep_now.clone();
|
||||
wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::Event)>::new(move |_: web_sys::Event| keep_now())
|
||||
};
|
||||
let on_visibility = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::Event)>::new(move |_: web_sys::Event| {
|
||||
let hidden = web_sys::window()
|
||||
.and_then(|w| w.document())
|
||||
.and_then(|d| js_sys::Reflect::get(&d, &"visibilityState".into()).ok())
|
||||
.and_then(|v| v.as_string())
|
||||
.is_some_and(|v| v == "hidden");
|
||||
if hidden {
|
||||
keep_now();
|
||||
}
|
||||
});
|
||||
let _ = window.add_event_listener_with_callback("pagehide", on_hide.as_ref().unchecked_ref());
|
||||
if let Some(document) = window.document() {
|
||||
let _ = document.add_event_listener_with_callback("visibilitychange", on_visibility.as_ref().unchecked_ref());
|
||||
}
|
||||
// The listeners live as long as the page.
|
||||
on_hide.forget();
|
||||
on_visibility.forget();
|
||||
}
|
||||
|
||||
/// One open design followed for its kept copy: its revision, when that last changed, and since
|
||||
/// when its copy has lacked a change (`None`: current, or being written).
|
||||
struct Track {
|
||||
revision: u64,
|
||||
last_change: f64,
|
||||
first_unwritten: Option<f64>,
|
||||
}
|
||||
|
||||
/// 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.
|
||||
@@ -342,6 +292,8 @@ mod web {
|
||||
const CHINESE_TC_FONT_PATH: &str = "./fonts/CraftUI-TC.otf";
|
||||
|
||||
type FontLoadResult = Rc<std::cell::RefCell<Option<Result<(String, Vec<u8>), String>>>>;
|
||||
/// The fetches of drawing fonts that have finished, for the next frame to hand on.
|
||||
type DrawingFontsDone = Rc<std::cell::RefCell<Vec<(solidcraft_engine::sketch::fallback::WebFont, Result<Vec<u8>, String>)>>>;
|
||||
|
||||
struct Shell {
|
||||
app: SolveApp,
|
||||
@@ -372,48 +324,27 @@ mod web {
|
||||
font_fetch_kind: Option<&'static str>,
|
||||
/// The language the fonts were last set up for (a CJK UI leads with the CJK face).
|
||||
font_language: String,
|
||||
/// The fonts a drawing's text asked for, other than Japanese, each fetched once a session.
|
||||
drawing_fonts_asked: std::collections::HashSet<solidcraft_engine::sketch::fallback::WebFont>,
|
||||
drawing_fonts_done: DrawingFontsDone,
|
||||
space_mouse: SpaceMouse,
|
||||
wheel_gestures: solidcraft_ui_egui::WebWheelGestures,
|
||||
/// The settings this tab wrote or loaded last, and those another tab wrote since (#8644).
|
||||
prefs_writer: crate::PrefsWriter,
|
||||
prefs_warning: Option<String>,
|
||||
other_tab_prefs: PrefsInbox,
|
||||
/// The open designs followed for their kept copy, by `Session::uid` (#10220).
|
||||
keep_track: std::collections::HashMap<u64, Track>,
|
||||
/// Every unsaved design's kept copy is current (and the preference is on).
|
||||
all_kept: bool,
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
/// The settings as they are kept between visits: with the user's own language, not a
|
||||
/// session-only English fallback.
|
||||
fn persisted_prefs(&mut self, previous: impl FnOnce() -> Result<Option<String>, String>) -> Option<String> {
|
||||
fn persisted_prefs(&mut self) -> String {
|
||||
let saved = self.font.persisted_language(&self.app.preferences.language).to_owned();
|
||||
let live = std::mem::replace(&mut self.app.preferences.language, saved);
|
||||
let (prefs, warning) = match self.app.try_prefs() {
|
||||
Ok(prefs) => (Some(prefs), None),
|
||||
Err(reason) => match previous().and_then(|previous| self.app.prefs_with_stored_panel_layout(previous.as_deref())) {
|
||||
Ok(prefs) => (Some(prefs), Some(format!("Panel layout changes were not saved: {reason}"))),
|
||||
Err(error) => (None, Some(format!("Settings were not written: {error}; panel layout: {reason}"))),
|
||||
},
|
||||
};
|
||||
if self.prefs_warning != warning {
|
||||
if let Some(warning) = &warning {
|
||||
log::warn!("{warning}");
|
||||
}
|
||||
self.prefs_warning = warning;
|
||||
}
|
||||
let prefs = self.app.prefs();
|
||||
self.app.preferences.language = live;
|
||||
prefs
|
||||
}
|
||||
|
||||
fn stored_prefs() -> Result<Option<String>, String> {
|
||||
let storage = web_sys::window()
|
||||
.and_then(|window| window.local_storage().ok().flatten())
|
||||
.ok_or_else(|| "browser settings storage is unavailable".to_owned())?;
|
||||
storage.get_item(&crate::prefs_key()).map_err(|_| "browser settings could not be read".to_owned())
|
||||
}
|
||||
|
||||
/// A settings change goes to the browser's storage in the frame it is made, and from
|
||||
/// there to the other open tabs (#8644). Input is what changes settings, so a frame
|
||||
/// without any is skipped; eframe's own save still catches anything else.
|
||||
@@ -421,7 +352,7 @@ mod web {
|
||||
if !self.launch.persists() || ctx.input(|i| i.events.is_empty()) {
|
||||
return;
|
||||
}
|
||||
let Some(prefs) = self.persisted_prefs(Self::stored_prefs) else { return };
|
||||
let prefs = self.persisted_prefs();
|
||||
let Some(prefs) = self.prefs_writer.take(prefs) else { return };
|
||||
let stored = web_sys::window().and_then(|w| w.local_storage().ok().flatten()).map(|s| s.set_item(&crate::prefs_key(), &prefs));
|
||||
if !matches!(stored, Some(Ok(()))) {
|
||||
@@ -435,9 +366,8 @@ mod web {
|
||||
fn load_other_tabs_prefs(&mut self) {
|
||||
let Some(prefs) = self.other_tab_prefs.take() else { return };
|
||||
self.app.load_prefs(&prefs);
|
||||
if let Some(held) = self.persisted_prefs(Self::stored_prefs) {
|
||||
self.prefs_writer.loaded(held);
|
||||
}
|
||||
let held = self.persisted_prefs();
|
||||
self.prefs_writer.loaded(held);
|
||||
}
|
||||
|
||||
/// Files from the computer, writes to store, messages and the autosave.
|
||||
@@ -516,14 +446,8 @@ mod web {
|
||||
self.sync_recents();
|
||||
}
|
||||
let now = ctx.input(|i| i.time);
|
||||
// With "keep unsaved work in this browser" on, each design's own copy takes over from
|
||||
// the single autosave (#10220).
|
||||
if self.app.preferences.keep_unsaved_in_browser && self.autosaved.is_some() {
|
||||
files::autosave(&self.files, None);
|
||||
self.autosaved = None;
|
||||
}
|
||||
// Unsaved work is kept (at most every AUTOSAVE_S); saved work needs no autosave.
|
||||
if self.files.ready.get() && self.files.recovery.borrow().is_none() && !self.app.preferences.keep_unsaved_in_browser {
|
||||
if self.files.ready.get() && self.files.recovery.borrow().is_none() {
|
||||
let s = &self.app.session;
|
||||
let changed = s.is_dirty() && self.autosaved != Some(s.revision);
|
||||
let wait = AUTOSAVE_S - (now - self.autosaved_at);
|
||||
@@ -542,175 +466,6 @@ mod web {
|
||||
if !self.files.ready.get() || !self.files.incoming.borrow().is_empty() {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(100));
|
||||
}
|
||||
self.all_kept = self.keep_tick(Some(ctx), false);
|
||||
}
|
||||
|
||||
/// Keep each open design's unsaved changes in the browser, when the preference asks for
|
||||
/// it: a design's copy is written once it has been still a moment (`keep::due`), or at
|
||||
/// once with `now` (the page is being hidden); a copy whose design is saved or closed, or
|
||||
/// with the preference off, goes. Answers whether every unsaved design's copy is current.
|
||||
fn keep_tick(&mut self, ctx: Option<&egui::Context>, now: bool) -> bool {
|
||||
if !self.launch.persists() || !self.files.ready.get() {
|
||||
return false;
|
||||
}
|
||||
let Shell { app, files, keep_track, .. } = self;
|
||||
let on = app.preferences.keep_unsaved_in_browser;
|
||||
let time = js_sys::Date::now();
|
||||
let page = files.keep_page.borrow().clone();
|
||||
let mut wanted = std::collections::HashSet::new();
|
||||
let mut all_kept = true;
|
||||
let mut wake: Option<f64> = None;
|
||||
let mut error = None;
|
||||
let mut soon = |ms: f64| wake = Some(wake.map_or(ms, |w: f64| w.min(ms)));
|
||||
if on {
|
||||
for s in solidcraft_ui_egui::documents::open_sessions(app).into_iter().filter(|s| s.is_dirty()) {
|
||||
let id = keep::entry_id(&page, s.uid);
|
||||
wanted.insert(s.uid);
|
||||
let t = keep_track.entry(s.uid).or_insert(Track { revision: s.revision, last_change: time, first_unwritten: None });
|
||||
if t.revision != s.revision {
|
||||
t.revision = s.revision;
|
||||
t.last_change = time;
|
||||
}
|
||||
if files.kept.borrow().get(&id).copied().flatten() == Some(s.revision) {
|
||||
t.first_unwritten = None;
|
||||
continue;
|
||||
}
|
||||
all_kept = false;
|
||||
if files.keeping.borrow().contains(&id) {
|
||||
soon(100.0);
|
||||
continue;
|
||||
}
|
||||
let first = *t.first_unwritten.get_or_insert(time);
|
||||
let failed = files.kept_failed.borrow().get(&id).copied();
|
||||
if !keep::may_write(time, failed) {
|
||||
// The last write failed: try again after a while.
|
||||
soon(keep::RETRY_MS);
|
||||
continue;
|
||||
}
|
||||
if now || keep::due(time, first, t.last_change) {
|
||||
match solidcraft_engine::io::write_design_container(&s.design()) {
|
||||
Ok(bytes) => {
|
||||
let entry = keep::Entry { id, name: s.doc.name.clone(), path: s.path.clone(), changed_at: t.last_change };
|
||||
files::write_kept(files, &entry, bytes, s.revision);
|
||||
t.first_unwritten = None;
|
||||
}
|
||||
Err(e) => {
|
||||
files.kept_failed.borrow_mut().insert(id, time);
|
||||
error = Some(format!("keeping unsaved work in this browser: {e}"));
|
||||
}
|
||||
}
|
||||
soon(100.0);
|
||||
} else {
|
||||
soon((keep::QUIET_MS - (time - t.last_change)).min(keep::MAX_WAIT_MS - (time - first)).max(16.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
// This page's copies no design needs any more: saved, closed, or the preference off.
|
||||
let stale: Vec<String> = files
|
||||
.kept
|
||||
.borrow()
|
||||
.keys()
|
||||
.filter(|id| keep::page_of(id) == Some(page.as_str()))
|
||||
.filter(|id| !wanted.iter().any(|uid| keep::entry_id(&page, *uid) == **id))
|
||||
.cloned()
|
||||
.collect();
|
||||
for id in stale {
|
||||
files::remove_kept(files, &id);
|
||||
soon(100.0);
|
||||
}
|
||||
keep_track.retain(|uid, _| wanted.contains(uid));
|
||||
if let Some(e) = error {
|
||||
app.set_status(e, true);
|
||||
}
|
||||
if let (Some(ctx), Some(ms)) = (ctx, wake) {
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs_f64(ms / 1000.0));
|
||||
}
|
||||
on && all_kept
|
||||
}
|
||||
|
||||
/// Offer the copies an earlier visit kept (#10220): "Restore unsaved design: <name>,
|
||||
/// changed <time>?", one window for all of them, each with Restore, Open saved version
|
||||
/// (when it has a file) and Discard.
|
||||
fn keep_prompt(&mut self, ctx: &egui::Context) {
|
||||
let offered = self.files.keep_offered.borrow().clone();
|
||||
if offered.is_empty() || !self.files.ready.get() {
|
||||
return;
|
||||
}
|
||||
let now = (js_sys::Date::now() / 1000.0) as u64;
|
||||
let rows: Vec<_> = offered
|
||||
.iter()
|
||||
.map(|e| solidcraft_ui_egui::recovery_ui::BrowserCopy {
|
||||
name: &e.name,
|
||||
when: solidcraft_ui_egui::recovery_ui::ago_between(now, (e.changed_at / 1000.0) as u64),
|
||||
open_saved: keep::answers(e, self.saved_exists(e)).contains(&keep::Answer::OpenSaved),
|
||||
})
|
||||
.collect();
|
||||
let act = solidcraft_ui_egui::recovery_ui::browser_copies_window(ctx, &self.app.preferences.language, &rows);
|
||||
let Some((index, answer)) = act else { return };
|
||||
let Some(entry) = offered.get(index) else { return };
|
||||
let answer = match answer {
|
||||
solidcraft_ui_egui::recovery_ui::CopyAnswer::Restore => keep::Answer::Restore,
|
||||
solidcraft_ui_egui::recovery_ui::CopyAnswer::OpenSaved => keep::Answer::OpenSaved,
|
||||
solidcraft_ui_egui::recovery_ui::CopyAnswer::Discard => keep::Answer::Discard,
|
||||
};
|
||||
self.keep_answer(entry, answer);
|
||||
}
|
||||
|
||||
/// Whether a kept copy's design file can still be opened: in the browser's storage, or a
|
||||
/// file on the computer this browser remembers.
|
||||
fn saved_exists(&self, e: &keep::Entry) -> bool {
|
||||
e.path.as_deref().is_some_and(|p| vfs::exists(p) || disk::parse(p).is_some_and(|(id, _)| self.files.disk_known.borrow().contains(id)))
|
||||
}
|
||||
|
||||
/// Act on an answer in the "Restore unsaved design?" window (`keep::plan`).
|
||||
fn keep_answer(&mut self, entry: &keep::Entry, answer: keep::Answer) {
|
||||
let Some(plan) = keep::plan(answer, entry, self.saved_exists(entry)) else { return };
|
||||
if plan.open_copy {
|
||||
let copy = match files::prepare_kept(&entry.id, entry.path.as_deref()) {
|
||||
Ok(copy) => copy,
|
||||
Err(error) => {
|
||||
self.app.set_status(error, true);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.app.open_path(©);
|
||||
if self.app.session.path.as_deref() != Some(copy.as_str()) {
|
||||
// Not opened (the status says why): it stays offered.
|
||||
return;
|
||||
}
|
||||
// It is its own design again (saving goes to its file), with its changes unsaved.
|
||||
self.app.session.path = entry.path.clone();
|
||||
self.app.session.doc_mut().name = entry.name.clone();
|
||||
self.app.session.mark_unsaved();
|
||||
self.app.home.open = false;
|
||||
}
|
||||
if let Some(path) = &plan.open_saved {
|
||||
if vfs::exists(path) {
|
||||
self.app.open_path(path);
|
||||
if !keep::is_open(self.app.session.path.as_deref(), path) {
|
||||
// Not opened (the status says why): the copy stays offered.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// A file on the computer: the browser asks for access again, in this click.
|
||||
// The copy goes once the file has opened, not before: if access is refused,
|
||||
// the picker is dismissed or the file is gone, it stays offered.
|
||||
let (files, id) = (self.files.clone(), entry.id.clone());
|
||||
disk::restore(
|
||||
&self.files,
|
||||
path,
|
||||
Some(Box::new(move || {
|
||||
files.keep_offered.borrow_mut().retain(|e| e.id != id);
|
||||
files::remove_kept(&files, &id);
|
||||
})),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if plan.drop_copy {
|
||||
self.files.keep_offered.borrow_mut().retain(|e| e.id != entry.id);
|
||||
files::remove_kept(&self.files, &entry.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The start page's recent designs are the stored ones: the remembered order first (those
|
||||
@@ -747,7 +502,7 @@ mod web {
|
||||
return;
|
||||
}
|
||||
let mut act: Option<(&'static str, String)> = None;
|
||||
let closed = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::unique("sc_browser_files"), "folder", "Files in this browser")
|
||||
let closed = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::new("sc_browser_files"), "folder", "Files in this browser")
|
||||
// Room for long names; the kit still caps the window at the browser's width.
|
||||
.cards(4.0)
|
||||
.show_body(ctx, |ui| {
|
||||
@@ -849,9 +604,7 @@ mod web {
|
||||
self.files.panel.set(false);
|
||||
}
|
||||
"insert" => {
|
||||
if !solidcraft_ui_egui::mesh_units::ask(&mut self.app, &path, true) {
|
||||
self.app.insert_path(&path);
|
||||
}
|
||||
self.app.insert_path(&path);
|
||||
self.files.panel.set(false);
|
||||
}
|
||||
"download" => match vfs::read(&path, solidcraft_engine::io::MAX_DESIGN_BYTES) {
|
||||
@@ -896,16 +649,10 @@ mod web {
|
||||
*r = new.clone();
|
||||
}
|
||||
}
|
||||
if let Some(at) = self.app.home.opened_at.remove(&old) {
|
||||
self.app.home.opened_at.insert(new.clone(), at);
|
||||
}
|
||||
// A forgotten design follows its new name.
|
||||
if self.app.home.forgotten.remove(&old) {
|
||||
self.app.home.forgotten.insert(new.clone());
|
||||
self.app.home.forgotten.insert(new);
|
||||
}
|
||||
// An open tab of that file keeps its path and, when the name came
|
||||
// from the file, the title that matches the file list (#11375).
|
||||
solidcraft_ui_egui::documents::follow_file_rename(&mut self.app, &old, &new);
|
||||
}
|
||||
Err(e) => self.app.set_status(e, true),
|
||||
}
|
||||
@@ -919,7 +666,7 @@ 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;
|
||||
let _ = solidcraft_ui_egui::ui_kit::Window::new(egui::Id::unique("sc_browser_recovery"), "restore", "Recover unsaved work?")
|
||||
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."));
|
||||
@@ -955,13 +702,6 @@ mod web {
|
||||
files::autosave(&self.files, None);
|
||||
}
|
||||
|
||||
/// Whether leaving the page asks through the browser's own box (`keep::asks_before_unload`).
|
||||
fn asks_before_unload(&self) -> bool {
|
||||
let any = solidcraft_ui_egui::documents::any_dirty(&self.app);
|
||||
let prefs = &self.app.preferences;
|
||||
keep::asks_before_unload(self.launch.persists(), prefs.keep_unsaved_in_browser, prefs.keep_unsaved_also_asks, any, self.all_kept)
|
||||
}
|
||||
|
||||
/// Whether the shell already has the on-demand face the live language needs.
|
||||
fn script_font_ready(&self) -> bool {
|
||||
match solidcraft_ui_egui::fonts::web_script_font_kind(solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language)) {
|
||||
@@ -990,6 +730,10 @@ mod web {
|
||||
self.chinese_kind = Some("zh-TW");
|
||||
}
|
||||
_ => {
|
||||
solidcraft_engine::sketch::fallback::provide_web_font(
|
||||
solidcraft_engine::sketch::fallback::WebFont::Japanese,
|
||||
bytes.clone(),
|
||||
);
|
||||
self.cjk_bytes = Some(bytes);
|
||||
}
|
||||
}
|
||||
@@ -1003,6 +747,9 @@ mod web {
|
||||
_ => JAPANESE_FONT_PATH,
|
||||
};
|
||||
log::warn!("failed to fetch script font from {path}: {err}");
|
||||
if !matches!(kind, Some("zh" | "zh-TW")) {
|
||||
solidcraft_engine::sketch::fallback::web_font_failed(solidcraft_engine::sketch::fallback::WebFont::Japanese);
|
||||
}
|
||||
false
|
||||
}
|
||||
};
|
||||
@@ -1038,10 +785,12 @@ mod web {
|
||||
self.font_language = effective.to_owned();
|
||||
}
|
||||
|
||||
/// CJK text on screen with no Japanese font yet (an English UI showing a Japanese name):
|
||||
/// fetch the Japanese font, so the text does not stay tofu (#720).
|
||||
/// CJK text on screen with no Japanese font yet (an English UI showing a Japanese name), or a
|
||||
/// drawing's text waiting for one: fetch the Japanese font, so the text does not stay tofu (#720).
|
||||
fn watch_text_for_font(&mut self, ctx: &egui::Context) {
|
||||
if self.font.watching_text() && solidcraft_ui_egui::fonts::painted_cjk(ctx) && self.font.text_needs_font() {
|
||||
let japanese = solidcraft_engine::sketch::fallback::web_fonts_wanted().contains(&solidcraft_engine::sketch::fallback::WebFont::Japanese);
|
||||
let wanted = solidcraft_ui_egui::fonts::painted_cjk(ctx) || japanese;
|
||||
if self.font.watching_text() && wanted && self.font.text_needs_font() {
|
||||
self.fetch_font(ctx, "ja");
|
||||
}
|
||||
}
|
||||
@@ -1062,6 +811,33 @@ mod web {
|
||||
ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
|
||||
/// The other fonts a drawing's text waits for (Arabic, Chinese, Korean): each fetched once,
|
||||
/// and handed to the text when it comes. Japanese comes with the interface's CJK font.
|
||||
fn fetch_drawing_fonts(&mut self, ctx: &egui::Context) {
|
||||
use solidcraft_engine::sketch::fallback::{self, WebFont};
|
||||
for (font, res) in self.drawing_fonts_done.borrow_mut().drain(..) {
|
||||
match res {
|
||||
Ok(bytes) => fallback::provide_web_font(font, bytes),
|
||||
Err(err) => {
|
||||
log::warn!("failed to fetch the drawing font {}/{}: {err}", crate::FONTS, font.file());
|
||||
fallback::web_font_failed(font);
|
||||
}
|
||||
}
|
||||
ctx.request_repaint();
|
||||
}
|
||||
for font in fallback::web_fonts_wanted() {
|
||||
if font == WebFont::Japanese || !self.drawing_fonts_asked.insert(font) {
|
||||
continue;
|
||||
}
|
||||
let (done, ctx) = (self.drawing_fonts_done.clone(), ctx.clone());
|
||||
wasm_bindgen_futures::spawn_local(async move {
|
||||
let res = fetch_font_bytes(&format!("./{}/{}", crate::FONTS, font.file())).await;
|
||||
done.borrow_mut().push((font, res));
|
||||
ctx.request_repaint();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl eframe::App for Shell {
|
||||
@@ -1069,11 +845,12 @@ mod web {
|
||||
// First: a language another tab chose fetches its font this frame.
|
||||
self.load_other_tabs_prefs();
|
||||
self.tick_font(ctx);
|
||||
self.fetch_drawing_fonts(ctx);
|
||||
self.app.logic(ctx);
|
||||
self.api.poll();
|
||||
self.space_mouse.logic(&mut self.app, ctx);
|
||||
self.files_tick(ctx);
|
||||
UNSAVED.with(|u| u.set(self.asks_before_unload()));
|
||||
UNSAVED.with(|u| u.set(solidcraft_ui_egui::documents::any_dirty(&self.app)));
|
||||
DESIGN_SHOWN.with(|d| d.set(solidcraft_ui_egui::toolbar::design_shown(&self.app)));
|
||||
}
|
||||
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
@@ -1093,7 +870,7 @@ mod web {
|
||||
}
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
self.linked.tick(&mut self.app, ui.ctx());
|
||||
UNSAVED.with(|u| u.set(self.asks_before_unload()));
|
||||
UNSAVED.with(|u| u.set(self.launch.persists() && solidcraft_ui_egui::documents::any_dirty(&self.app)));
|
||||
if self.launch.embed {
|
||||
ui_kit::Container::new(ui.ctx(), ui_kit::Surface::Chrome).edge("embed_header", ui_kit::Edge::Top).show(ui, |ui| {
|
||||
if ui_kit::button(ui, solidcraft_ui_egui::i18n::tr("web.open_full"), ui_kit::Button::Normal).clicked()
|
||||
@@ -1108,15 +885,10 @@ mod web {
|
||||
if self.launch.persists() {
|
||||
self.file_panel(&ctx);
|
||||
self.recovery_prompt(&ctx);
|
||||
self.keep_prompt(&ctx);
|
||||
}
|
||||
self.watch_text_for_font(&ctx);
|
||||
self.store_prefs(&ctx);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
|
||||
Some(&mut *self)
|
||||
}
|
||||
fn persist_egui_memory(&self) -> bool {
|
||||
// eframe keeps this under one fixed key: only stable uses it, so a pre-release never rewrites it.
|
||||
self.launch.persists() && crate::channel::current().is_empty()
|
||||
@@ -1125,7 +897,7 @@ mod web {
|
||||
if !self.launch.persists() {
|
||||
return;
|
||||
}
|
||||
let Some(prefs) = self.persisted_prefs(|| Ok(storage.get_string(&crate::prefs_key()))) else { return };
|
||||
let prefs = self.persisted_prefs();
|
||||
storage.set_string(&crate::prefs_key(), prefs);
|
||||
}
|
||||
}
|
||||
@@ -1191,8 +963,7 @@ mod web {
|
||||
{
|
||||
create.instance_descriptor.backends = eframe::wgpu::Backends::GL;
|
||||
}
|
||||
let runner = eframe::WebRunner::new();
|
||||
let result = runner
|
||||
let result = eframe::WebRunner::new()
|
||||
.start(
|
||||
canvas,
|
||||
options,
|
||||
@@ -1204,7 +975,7 @@ mod web {
|
||||
}
|
||||
let (panel, opener, saver, shown) = (files.clone(), files.clone(), files.clone(), files.clone());
|
||||
let (restorable, restorer) = (files.clone(), files.clone());
|
||||
let services = if !launch.persists() { Services::default() } else { Services {
|
||||
let services = if launch.embed { Services::default() } else { Services {
|
||||
// Open shows the browser's open dialog for a design where it has one
|
||||
// (the file keeps its handle, so Save writes back to it), else the
|
||||
// browser's files window. A command that reads another kind of file
|
||||
@@ -1236,7 +1007,7 @@ mod web {
|
||||
can_restore: Some(Box::new(move |path: &str| {
|
||||
disk::parse(path).is_some_and(|(id, _)| restorable.disk_known.borrow().contains(id))
|
||||
})),
|
||||
restore_file: Some(Box::new(move |path: &str| disk::restore(&restorer, path, None))),
|
||||
restore_file: Some(Box::new(move |path: &str| disk::restore(&restorer, path))),
|
||||
download_design: Some(Box::new(files::download)),
|
||||
easyprint_upload: Some(std::sync::Arc::new(crate::easyprint::upload)),
|
||||
browser_files: Some(Box::new(move || shown.panel.set(true))),
|
||||
@@ -1248,7 +1019,6 @@ mod web {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let mut app = SolveApp::new(Session::default(), services).with_control(receiver);
|
||||
app.embedded = launch.embed;
|
||||
app.embed_editor = launch.editor;
|
||||
// Listening before reading: settings another tab writes from now on are
|
||||
// newer than those read here.
|
||||
let other_tab_prefs = PrefsInbox::default();
|
||||
@@ -1299,23 +1069,16 @@ mod web {
|
||||
chinese_kind: None,
|
||||
font_fetch_kind: None,
|
||||
font_language,
|
||||
drawing_fonts_asked: Default::default(),
|
||||
drawing_fonts_done: Default::default(),
|
||||
space_mouse: SpaceMouse::default(),
|
||||
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
|
||||
prefs_writer: crate::PrefsWriter::default(),
|
||||
prefs_warning: None,
|
||||
other_tab_prefs,
|
||||
keep_track: Default::default(),
|
||||
all_kept: false,
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
if result.is_ok()
|
||||
&& crate::Launch::parse(&query()).persists()
|
||||
&& let Some(window) = web_sys::window()
|
||||
{
|
||||
keep_when_hidden(&window, &runner);
|
||||
}
|
||||
if let Some(el) = document.get_element_by_id(LOADING_ID) {
|
||||
match result {
|
||||
Ok(()) => dismiss_loading(el),
|
||||
@@ -1368,17 +1131,6 @@ mod tests {
|
||||
assert!(full.fragment().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embed_editor_needs_no_design_link_and_keeps_nothing_in_the_browser() {
|
||||
let editor = Launch::parse("?embed=editor");
|
||||
assert!(editor.editor && !editor.embed && !editor.persists());
|
||||
let linked = Launch::parse("?embed=editor&open=https://example.org/a.sc");
|
||||
assert!(linked.editor && !linked.embed, "an editing embed may open a linked design, still editable");
|
||||
let viewer = Launch::parse("?embed=viewer&open=https://example.org/a.sc");
|
||||
assert!(viewer.embed && !viewer.editor, "any other value is the viewer embed, as before");
|
||||
assert!(!Launch::parse("?other=editor").editor);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn design_links_refuse_hostile_schemes_credentials_and_sizes() {
|
||||
assert!(design_url("https://example.org/arm.solidcraft?download=1", "http://solidcraft.io").is_ok());
|
||||
@@ -1484,4 +1236,20 @@ mod tests {
|
||||
assert_eq!(settings_written(key, None, None), None, "clearing all storage leaves this tab's settings");
|
||||
assert_eq!(settings_written(key, Some("rc:solidcraft.prefs"), Some("{}".into())), None, "another channel's settings are not loaded");
|
||||
}
|
||||
|
||||
/// Each font a drawing's text can ask for on the web is one index.html copies, from an asset
|
||||
/// in the repository, into the folder the app fetches it from.
|
||||
#[test]
|
||||
fn each_drawing_font_is_copied_where_it_is_fetched() {
|
||||
let page = include_str!("../index.html");
|
||||
let here = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
for font in solidcraft_engine::sketch::fallback::WebFont::ALL {
|
||||
let served = format!("{FONTS}/{}", font.file());
|
||||
let (folder, file) = served.rsplit_once('/').unwrap_or_default();
|
||||
let copy = page.lines().find(|l| l.contains("rel=\"copy-file\"") && l.contains(&format!("/{file}\""))).unwrap_or_default();
|
||||
assert!(copy.contains(&format!("data-target-path=\"{folder}\"")), "index.html doesn't copy {served}: {copy}");
|
||||
let from = copy.split("href=\"").nth(1).and_then(|rest| rest.split('"').next()).unwrap_or_default();
|
||||
assert!(here.join(from).is_file(), "{served}: no {from}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -68,9 +68,7 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
|
||||
Ok(l) => {
|
||||
if let Some(port) = listen_on_listener(l, token.clone(), tx, ctx, false) {
|
||||
if matches!(listen_on, control_auth::Listen::Default) {
|
||||
solidcraft_engine::note!(
|
||||
"solidcraft: control channel on the default port 127.0.0.1:{port} (Preferences > Assistant turns it off)"
|
||||
);
|
||||
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) {
|
||||
@@ -79,14 +77,14 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
|
||||
*p = Some((port, token));
|
||||
}
|
||||
}
|
||||
Err(e) => solidcraft_engine::note!("solidcraft: could not write the control discovery file ({e})"),
|
||||
Err(e) => eprintln!("solidcraft: could not write the control discovery file ({e})"),
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => solidcraft_engine::note!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
|
||||
Err(e) => eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}"),
|
||||
}
|
||||
}
|
||||
None => solidcraft_engine::note!("solidcraft: control server not started: no random token available"),
|
||||
None => eprintln!("solidcraft: control server not started: no random token available"),
|
||||
}
|
||||
rx
|
||||
}
|
||||
@@ -97,13 +95,13 @@ pub fn start(listen_on: control_auth::Listen, ctx: egui::Context) -> Receiver<Co
|
||||
/// requests go to `tx`, where the chat's gate reads them; one may wait long for the user's Allow.
|
||||
pub fn listen_agent(tx: Sender<ControlRequest>, ctx: egui::Context) -> Option<u16> {
|
||||
let Some(token) = new_token() else {
|
||||
solidcraft_engine::note!("solidcraft: agent chat endpoint not started: no random token available");
|
||||
eprintln!("solidcraft: agent chat endpoint not started: no random token available");
|
||||
return None;
|
||||
};
|
||||
let listener = match TcpListener::bind(("127.0.0.1", 0)) {
|
||||
Ok(l) => l,
|
||||
Err(e) => {
|
||||
solidcraft_engine::note!("solidcraft: control server failed to bind 127.0.0.1: {e}");
|
||||
eprintln!("solidcraft: control server failed to bind 127.0.0.1: {e}");
|
||||
return None;
|
||||
}
|
||||
};
|
||||
@@ -117,21 +115,19 @@ fn listen_on_listener(listener: TcpListener, token: String, tx: Sender<ControlRe
|
||||
match control_auth::token_path(port) {
|
||||
Some(path) => match control_auth::write_token_file(&path, &token) {
|
||||
Ok(()) => {
|
||||
solidcraft_engine::note!("solidcraft: control server listening on 127.0.0.1:{port} (token in {})", path.display());
|
||||
eprintln!("solidcraft: control server listening on 127.0.0.1:{port} (token in {})", path.display());
|
||||
if let Ok(mut files) = TOKEN_FILES.lock() {
|
||||
files.push((path, token.clone()));
|
||||
}
|
||||
}
|
||||
Err(e) => solidcraft_engine::note!(
|
||||
Err(e) => eprintln!(
|
||||
"solidcraft: control server listening on 127.0.0.1:{port}, but its token file {} could not be written ({e}); \
|
||||
clients need SOLIDCRAFT_CONTROL_TOKEN",
|
||||
path.display()
|
||||
),
|
||||
},
|
||||
None => {
|
||||
solidcraft_engine::note!(
|
||||
"solidcraft: control server listening on 127.0.0.1:{port}; no data folder for its token, clients need SOLIDCRAFT_CONTROL_TOKEN"
|
||||
)
|
||||
eprintln!("solidcraft: control server listening on 127.0.0.1:{port}; no data folder for its token, clients need SOLIDCRAFT_CONTROL_TOKEN")
|
||||
}
|
||||
}
|
||||
let token = std::sync::Arc::new(token);
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
//! Is the file chooser that opens the desktop's own? (#10159)
|
||||
//!
|
||||
//! `rfd` opens the file chooser through the XDG desktop portal; without the portal it falls
|
||||
//! back to zenity, a window that does not belong to the app and that the app never mentions.
|
||||
//! The desktop's own chooser is the portal one, so the app has to know when it is not the one
|
||||
//! that opens, and say so in the session log.
|
||||
//!
|
||||
//! The session bus is asked once, on a thread of its own: a bus that answers late must not
|
||||
//! hold the window. What it answered is kept, and read when a file is opened.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
|
||||
/// The portal's bus name: the desktop's own file chooser lives behind it.
|
||||
const PORTAL: &str = "org.freedesktop.portal.Desktop";
|
||||
|
||||
/// The bus has not answered yet.
|
||||
const UNKNOWN: u8 = 0;
|
||||
/// There is no session bus to reach the portal on.
|
||||
const NO_SESSION_BUS: u8 = 1;
|
||||
/// The bus is there but the portal does not answer on it.
|
||||
const NO_PORTAL_SERVICE: u8 = 2;
|
||||
/// The bus is there and the portal answers on it.
|
||||
const PORTAL_THERE: u8 = 3;
|
||||
|
||||
/// What the session bus said about the portal.
|
||||
static BUS: AtomicU8 = AtomicU8::new(UNKNOWN);
|
||||
/// A file chooser is opening: whether it is the desktop's own is to be said.
|
||||
static OPENING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Ask the session bus, on a thread of its own, whether the portal is there.
|
||||
pub fn start(ctx: &egui::Context) {
|
||||
let thread_ctx = ctx.clone();
|
||||
let probe = move || {
|
||||
let answer = match session_bus() {
|
||||
None => NO_SESSION_BUS,
|
||||
Some(connection) => match portal_on(&connection) {
|
||||
true => PORTAL_THERE,
|
||||
false => NO_PORTAL_SERVICE,
|
||||
},
|
||||
};
|
||||
BUS.store(answer, Ordering::Relaxed);
|
||||
// The answer is in: a file chooser waiting for it can be told now.
|
||||
thread_ctx.request_repaint();
|
||||
};
|
||||
if let Err(e) = std::thread::Builder::new().name("desktop-chooser".into()).spawn(probe) {
|
||||
log::warn!("file chooser: no thread to ask the session bus: {e}");
|
||||
// Nothing will answer: the desktop's own chooser cannot be told apart from the start.
|
||||
BUS.store(PORTAL_THERE, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// The session bus, when there is one to talk to.
|
||||
fn session_bus() -> Option<zbus::blocking::Connection> {
|
||||
zbus::blocking::Connection::session().ok()
|
||||
}
|
||||
|
||||
/// Whether the portal answers on the bus: it is either running on it or waiting to be started.
|
||||
fn portal_on(connection: &zbus::blocking::Connection) -> bool {
|
||||
let Ok(bus) = zbus::blocking::fdo::DBusProxy::new(connection) else { return false };
|
||||
let named = |names: Vec<zbus::names::OwnedBusName>| names.iter().any(|name| name.as_str() == PORTAL);
|
||||
// A portal that is running is on the bus; one that is not running yet is still there when
|
||||
// the bus can start it, which is what the portal's service file is for.
|
||||
match (bus.list_names().map(named), bus.list_activatable_names().map(named)) {
|
||||
(Ok(running), Ok(startable)) => running || startable,
|
||||
(Ok(running), Err(_)) => running,
|
||||
(Err(_), Ok(startable)) => startable,
|
||||
// The bus does not answer: nothing answers on it.
|
||||
(Err(_), Err(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A file chooser is opening: ask for a frame, so that the reason it is not the desktop's own
|
||||
/// is said (#10159).
|
||||
pub fn note_opening(ctx: &egui::Context) {
|
||||
OPENING.store(true, Ordering::Relaxed);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
/// The reason the file chooser that is opening is not the desktop's own, if it is not.
|
||||
///
|
||||
/// The decision is the one the test states: without a session bus there is no bus to reach the
|
||||
/// portal on; with a bus but no portal on it, `rfd` falls back to zenity; and under XWayland the
|
||||
/// window is an X11 one while the session is a Wayland one, which the portal refuses to parent
|
||||
/// its chooser to.
|
||||
pub fn take_reason() -> Option<&'static str> {
|
||||
if !OPENING.load(Ordering::Relaxed) {
|
||||
return None;
|
||||
}
|
||||
let (dbus_session, portal_service) = match BUS.load(Ordering::Relaxed) {
|
||||
NO_SESSION_BUS => (false, false),
|
||||
NO_PORTAL_SERVICE => (true, false),
|
||||
PORTAL_THERE => (true, true),
|
||||
// The bus has not answered yet: wait for it, and say nothing yet.
|
||||
_ => return None,
|
||||
};
|
||||
OPENING.store(false, Ordering::Relaxed);
|
||||
let wayland_display = std::env::var_os("WAYLAND_DISPLAY").is_some();
|
||||
let x11_display = super::window_system_now() == Some("x11");
|
||||
super::desktop_chooser_fallback(dbus_session, portal_service, wayland_display, x11_display)
|
||||
}
|
||||
@@ -112,7 +112,7 @@ pub fn restart(software: bool, lost_at: u64, reason: &str) {
|
||||
pub fn end() -> ! {
|
||||
let watchdog = std::thread::Builder::new().name("solidcraft-exit".into()).spawn(|| {
|
||||
std::thread::sleep(END_GRACE);
|
||||
solidcraft_engine::note!("SolidCraft graphics: still closing after the device was lost; aborting");
|
||||
eprintln!("SolidCraft graphics: still closing after the device was lost; aborting");
|
||||
std::process::abort();
|
||||
});
|
||||
if let Err(e) = watchdog {
|
||||
|
||||
@@ -23,8 +23,6 @@ mod app_icon;
|
||||
mod come_forward;
|
||||
mod control_server;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod desktop_chooser;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod desktop_theme;
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
mod desktop_transparency;
|
||||
@@ -79,7 +77,7 @@ 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 {
|
||||
solidcraft_engine::note!("startup {:>6.0} ms {step}", start.elapsed().as_secs_f64() * 1000.0);
|
||||
eprintln!("startup {:>6.0} ms {step}", start.elapsed().as_secs_f64() * 1000.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -244,12 +242,6 @@ impl eframe::App for App {
|
||||
menu.tick(&mut self.solve, ctx);
|
||||
}
|
||||
self.solve.logic(ctx);
|
||||
// The file chooser that opened is not the desktop's own: the user has to be told (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(reason) = desktop_chooser::take_reason() {
|
||||
let text = self.solve.tr(reason).to_string();
|
||||
self.solve.set_status(text, false);
|
||||
}
|
||||
self.dialog_folder.design(self.solve.session.path.as_deref());
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
if !self.solve.file_picker_busy() {
|
||||
@@ -282,20 +274,13 @@ impl eframe::App for App {
|
||||
let now = std::time::Instant::now();
|
||||
if self.prefs_writer.due(ui.input(|i| i.events.iter().any(changes_settings)), now)
|
||||
&& let Some(storage) = frame.storage_mut()
|
||||
&& let Ok(prefs) = self.prefs_for_storage(storage)
|
||||
&& let Some(wait) = self.prefs_writer.store(storage, prefs, now)
|
||||
&& let Some(wait) = self.prefs_writer.store(storage, self.solve.prefs(), now)
|
||||
{
|
||||
ui.ctx().request_repaint_after(wait);
|
||||
}
|
||||
}
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
let prefs = match self.prefs_for_storage(storage) {
|
||||
Ok(prefs) => prefs,
|
||||
Err(reason) => {
|
||||
log::warn!("Settings were not written: {reason}");
|
||||
return;
|
||||
}
|
||||
};
|
||||
let prefs = self.solve.prefs();
|
||||
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, prefs.clone());
|
||||
self.prefs_writer.written = Some(prefs);
|
||||
}
|
||||
@@ -310,8 +295,6 @@ impl eframe::App for App {
|
||||
log::warn!("a picture was still being drawn as the app closed");
|
||||
}
|
||||
solidcraft_ui_egui::recovery_ui::close(&mut self.solve);
|
||||
// A GPU path tracer setup still running did not stop the app (#9236).
|
||||
solidcraft_ui_egui::render_workspace::close(&mut self.solve);
|
||||
solidcraft_ui_egui::open_elsewhere::close(&mut self.solve);
|
||||
// Before a restart (`Drop for App`) starts the next instance, which would otherwise get
|
||||
// nothing from the 3D mouse driver (`solidcraft_3dx_client::stop`).
|
||||
@@ -368,14 +351,6 @@ struct LostWatch {
|
||||
}
|
||||
|
||||
impl App {
|
||||
fn prefs_for_storage(&self, storage: &dyn eframe::Storage) -> Result<String, String> {
|
||||
self.solve.try_prefs().or_else(|reason| {
|
||||
log::warn!("Panel layout changes were not saved: {reason}");
|
||||
let previous = solidcraft_ui_egui::prefs::stored(|key| storage.get_string(key));
|
||||
self.solve.prefs_with_stored_panel_layout(previous.as_deref())
|
||||
})
|
||||
}
|
||||
|
||||
/// The graphics device was lost (#4172): the window can't draw again, so close, keeping
|
||||
/// unsaved changes for recovery as a forced quit does (the window can't ask about them), and
|
||||
/// start again once closed ([`device_lost::restart`]).
|
||||
@@ -383,7 +358,7 @@ impl App {
|
||||
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");
|
||||
solidcraft_engine::note!("SolidCraft graphics: the device was lost ({reason}); starting 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());
|
||||
gpu.reason = reason;
|
||||
@@ -496,19 +471,12 @@ fn fullscreen_content_insets(raw: &mut egui::RawInput, title_height: f32, zoom:
|
||||
|
||||
fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services {
|
||||
let parent = rfd::AsyncFileDialog::new().set_parent(cc);
|
||||
// The file chooser that opens may not be the desktop's own: the user is told when it does
|
||||
// not (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
let chooser_ctx = cc.egui_ctx.clone();
|
||||
let open_parent = parent.clone();
|
||||
let program_parent = parent.clone();
|
||||
let file_parent = parent.clone();
|
||||
let (open_folder, file_folder, save_folder) = (folder.clone(), folder.clone(), folder.clone());
|
||||
Services {
|
||||
pick_open: Some(Box::new(move |first: &[&str]| {
|
||||
// The chooser that opens may not be the desktop's own (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
desktop_chooser::note_opening(&chooser_ctx);
|
||||
// The file type the command reads comes first, so the picker opens on it (#1801).
|
||||
let mut dialog = open_folder.start(open_parent.clone());
|
||||
if !first.is_empty() {
|
||||
@@ -817,28 +785,6 @@ fn graphics() -> Option<serde_json::Value> {
|
||||
graphics_path().and_then(|p| std::fs::read_to_string(p).ok()).and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
|
||||
}
|
||||
|
||||
fn restore_preferences(app: &mut SolveApp, stored_ui: Option<&str>, saved_graphics: Option<&serde_json::Value>, safe_mode: bool) {
|
||||
if let Some(p) = stored_ui.filter(|_| !safe_mode) {
|
||||
app.load_prefs(p);
|
||||
}
|
||||
// Even safe mode edits the real graphics.json. Keep its choices in Preferences so an
|
||||
// anti-aliasing change cannot erase them; safe mode's renderer still uses its own defaults.
|
||||
restore_graphics_preferences(app, saved_graphics);
|
||||
}
|
||||
|
||||
/// Restore startup choices after loading the separately saved UI preferences.
|
||||
fn restore_graphics_preferences(app: &mut SolveApp, saved: Option<&serde_json::Value>) {
|
||||
app.preferences.graphics_adapter = saved.and_then(|v| v.get("adapter").and_then(serde_json::Value::as_str)).unwrap_or_default().to_string();
|
||||
// graphics.json chooses the window before app.ron is loaded. Reflect that saved choice,
|
||||
// not the current session's window system (which an environment override may change).
|
||||
app.preferences.window_system = saved
|
||||
.and_then(|v| v.get("window_system").and_then(serde_json::Value::as_str))
|
||||
.map(str::trim)
|
||||
.filter(|choice| matches!(*choice, "wayland" | "x11"))
|
||||
.unwrap_or("auto")
|
||||
.to_string();
|
||||
}
|
||||
|
||||
/// The graphics backend preference (`graphics.json`'s `backend`: `auto`, `dx12`, `vulkan`, `gl`).
|
||||
fn backend_preference() -> Option<String> {
|
||||
graphics().and_then(|v| v.get("backend").and_then(serde_json::Value::as_str).map(str::to_string))
|
||||
@@ -1008,7 +954,7 @@ fn main() -> eframe::Result {
|
||||
if let Some(value) = max_file_size {
|
||||
let applied = startup::max_file_size(value.as_deref()).and_then(|bytes| solidcraft_engine::io::limits::set_override(Some(bytes)));
|
||||
if let Err(error) = applied {
|
||||
solidcraft_engine::note!("SolidCraft: {error}");
|
||||
eprintln!("SolidCraft: {error}");
|
||||
return Err(eframe::Error::AppCreation(std::io::Error::other(error).into()));
|
||||
}
|
||||
}
|
||||
@@ -1040,7 +986,7 @@ fn main() -> eframe::Result {
|
||||
let fallback_note = std::env::var(startup::FALLBACK_NOTE_ENV).ok().map(|s| s.chars().take(4096).collect::<String>());
|
||||
if let Some(note) = &fallback_note {
|
||||
log::warn!("{note}");
|
||||
solidcraft_engine::note!("SolidCraft: {note}");
|
||||
eprintln!("SolidCraft: {note}");
|
||||
notes.push(note.clone());
|
||||
}
|
||||
let saved_samples = if safe_mode { None } else { msaa() };
|
||||
@@ -1057,8 +1003,6 @@ fn main() -> eframe::Result {
|
||||
solidcraft_engine::crash::install(dir, build)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
// The recovery folder also holds the reopen guard marker (#5064); read once, reused below.
|
||||
let recovery_dir = solidcraft_engine::recovery::default_dir();
|
||||
// A crash in native code (a graphics driver) says where it happened: the exception or signal
|
||||
// and the module on Windows and macOS, and on every system the startup step the app was in.
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -1141,10 +1085,6 @@ fn main() -> eframe::Result {
|
||||
solidcraft_ui_egui::fonts::load_system_fonts_in_background(&cc.egui_ctx);
|
||||
mark("fonts");
|
||||
let dialog_folder = DialogFolder::default();
|
||||
// The file chooser that opens may not be the desktop's own: the session bus is asked
|
||||
// now, so the answer is there when a file is opened (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
desktop_chooser::start(&cc.egui_ctx);
|
||||
let mut app = SolveApp::new(Session::default(), services(cc, &dialog_folder));
|
||||
app.apply_off_thread = Some(solidcraft_ui_egui::dialogs::APPLY_AT_ONCE);
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
@@ -1153,20 +1093,21 @@ fn main() -> eframe::Result {
|
||||
// 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) {
|
||||
solidcraft_engine::note!("SolidCraft window: no class icon; the taskbar may show a generic one");
|
||||
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 {
|
||||
solidcraft_engine::note!("SolidCraft window: no native resize border; egui edge zones instead");
|
||||
eprintln!("SolidCraft window: no native resize border; egui edge zones instead");
|
||||
}
|
||||
}
|
||||
app.integrated_titlebar = cfg!(target_os = "macos");
|
||||
app.cpu_meshing = safe_mode;
|
||||
app.graphics_fallback = fallback_note.clone();
|
||||
let stored_ui = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k)));
|
||||
restore_preferences(&mut app, stored_ui.as_deref(), graphics().as_ref(), safe_mode);
|
||||
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);
|
||||
}
|
||||
if let Some(path) = &theme_file
|
||||
&& let Err(e) = solidcraft_ui_egui::prefs::follow_theme_file_for_run(&mut app, path)
|
||||
{
|
||||
@@ -1179,6 +1120,10 @@ fn main() -> eframe::Result {
|
||||
lost_at: None,
|
||||
reason: String::new(),
|
||||
});
|
||||
if !safe_mode {
|
||||
app.preferences.graphics_adapter =
|
||||
graphics().and_then(|v| v.get("adapter").and_then(serde_json::Value::as_str).map(str::to_string)).unwrap_or_default();
|
||||
}
|
||||
if let Some(rs) = &cc.wgpu_render_state {
|
||||
app.services.graphics_adapters = rs
|
||||
.available_adapters
|
||||
@@ -1190,10 +1135,9 @@ fn main() -> eframe::Result {
|
||||
.collect();
|
||||
let info = rs.adapter.get_info();
|
||||
if !app.preferences.graphics_adapter.is_empty() && !info.name.eq_ignore_ascii_case(app.preferences.graphics_adapter.as_str()) {
|
||||
solidcraft_engine::note!(
|
||||
eprintln!(
|
||||
"SolidCraft graphics: preferred adapter {:?} was not used; using {:?} instead",
|
||||
app.preferences.graphics_adapter,
|
||||
info.name
|
||||
app.preferences.graphics_adapter, info.name
|
||||
);
|
||||
}
|
||||
let samples = scene_msaa_request(saved_samples);
|
||||
@@ -1201,7 +1145,7 @@ fn main() -> eframe::Result {
|
||||
eframe::egui_wgpu::supported_msaa_samples(&rs.adapter, &rs.device, rs.target_format, depth)
|
||||
});
|
||||
let scene_samples = scene_samples(info.backend, u32::from(samples), &supported);
|
||||
solidcraft_engine::note!("SolidCraft graphics: {:?}, MSAA {scene_samples}x (requested {}x)", rs.adapter.get_info().backend, samples);
|
||||
eprintln!("SolidCraft graphics: {:?}, MSAA {scene_samples}x (requested {}x)", rs.adapter.get_info().backend, samples);
|
||||
solidcraft_engine::crash::phase("viewport pipelines (shader compile)");
|
||||
let scene_samples = app.set_wgpu_split(rs, depth_bits, STENCIL_BITS, scene_samples);
|
||||
app.msaa_supported = supported;
|
||||
@@ -1252,25 +1196,8 @@ fn main() -> eframe::Result {
|
||||
};
|
||||
// No design given: the designs open at the last quit, when the preference asks (#5542).
|
||||
// Not when driven over the control channel, whose clients expect a fresh design.
|
||||
// A design that stopped the last session while it was being reopened is skipped this
|
||||
// time, so one bad file cannot brick every launch (#5064).
|
||||
app.reopen_guard_dir = recovery_dir.clone();
|
||||
let files = if files.is_empty() && !sample && control_port.is_none() {
|
||||
let (reopen, skipped) = solidcraft_ui_egui::documents::reopen_plan(&app, recovery_dir.as_deref());
|
||||
if !skipped.is_empty() {
|
||||
let names: Vec<String> = skipped
|
||||
.iter()
|
||||
.map(|p| std::path::Path::new(p).file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_else(|| p.clone()))
|
||||
.collect();
|
||||
notes.push(format!(
|
||||
"The last session stopped while reopening {}. It was not reopened this time; open it yourself if you need it.",
|
||||
names.join(", ")
|
||||
));
|
||||
}
|
||||
reopen
|
||||
} else {
|
||||
files
|
||||
};
|
||||
let files =
|
||||
if files.is_empty() && !sample && control_port.is_none() { solidcraft_ui_egui::documents::designs_to_reopen(&app) } else { files };
|
||||
for f in &files {
|
||||
app.open_path_async(f);
|
||||
}
|
||||
@@ -1286,9 +1213,9 @@ fn main() -> eframe::Result {
|
||||
app.home.show(rev);
|
||||
}
|
||||
// Autosave, and offer what crashed sessions left behind.
|
||||
if let Some(dir) = recovery_dir.as_ref() {
|
||||
if let Some(dir) = solidcraft_engine::recovery::default_dir() {
|
||||
let every = if app.autosave_minutes > 0.0 { app.autosave_minutes * 60.0 } else { f64::MAX / 4.0 };
|
||||
solidcraft_ui_egui::recovery_ui::start(&mut app, dir, std::time::Duration::from_secs_f64(every.min(1e9)));
|
||||
solidcraft_ui_egui::recovery_ui::start(&mut app, &dir, std::time::Duration::from_secs_f64(every.min(1e9)));
|
||||
// Safe mode keeps autosaving but offers nothing: the entries wait for a normal start.
|
||||
if safe_mode {
|
||||
app.recoverable.clear();
|
||||
@@ -1362,7 +1289,7 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
|
||||
let retry = std::env::var(startup::RETRY_BACKEND_ENV).ok();
|
||||
let preference = if cpu { preference } else { retry.as_deref().or(preference) };
|
||||
if let Some(backends) = startup::choose_backends(cfg!(target_os = "windows"), env_set, cpu, preference, startup::has_hardware_adapter) {
|
||||
solidcraft_engine::note!("SolidCraft graphics: starting {backends:?}");
|
||||
eprintln!("SolidCraft graphics: starting {backends:?}");
|
||||
setup.instance_descriptor.backends = backends;
|
||||
}
|
||||
let intel_mac = cfg!(all(target_os = "macos", target_arch = "x86_64"));
|
||||
@@ -1388,7 +1315,7 @@ fn wgpu_options(cpu: bool, preference: Option<&str>) -> eframe::egui_wgpu::WgpuC
|
||||
/// event loop or a failed device; each retry advances through the APIs, then software once.
|
||||
fn no_window(error: eframe::Error, tried_cpu: bool, failed: eframe::wgpu::Backends, settings: Option<&std::path::Path>) -> eframe::Result {
|
||||
log::error!("the window could not open: {error}");
|
||||
solidcraft_engine::note!("SolidCraft: the window could not open: {error}");
|
||||
eprintln!("SolidCraft: the window could not open: {error}");
|
||||
// This failure was handled, so neither the marker nor the journal should call it a crash.
|
||||
if let Some(dir) = settings {
|
||||
startup::started(dir);
|
||||
@@ -1401,7 +1328,7 @@ fn no_window(error: eframe::Error, tried_cpu: bool, failed: eframe::wgpu::Backen
|
||||
let previous = std::env::var(startup::FALLBACK_NOTE_ENV).unwrap_or_default();
|
||||
let note = startup::fallback_note(&previous, failed, next, &error.to_string());
|
||||
log::warn!("{note}");
|
||||
solidcraft_engine::note!("SolidCraft: {note}");
|
||||
eprintln!("SolidCraft: {note}");
|
||||
let mut command = std::process::Command::new(exe);
|
||||
command.args(std::env::args_os().skip(1)).env(startup::FALLBACK_NOTE_ENV, note);
|
||||
if next == "cpu" {
|
||||
@@ -1476,47 +1403,6 @@ fn prefer_x11(display: bool, wayland_display: bool, desktop: Option<&str>, wayla
|
||||
display && !(niri && wayland_display)
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
const CHOOSER_NO_SESSION_BUS: &str = "file.open.portal_fallback.no_session_bus";
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
const CHOOSER_NO_PORTAL_SERVICE: &str = "file.open.portal_fallback.no_portal_service";
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
const CHOOSER_XWAYLAND: &str = "file.open.portal_fallback.xwayland";
|
||||
|
||||
/// Whether the chooser that opens is NOT the desktop's own, and why.
|
||||
///
|
||||
/// `rfd` opens the file chooser through the XDG desktop portal; when the portal is not there it
|
||||
/// falls back to zenity, which opens a window that does not belong to SolidCraft and that the
|
||||
/// application never mentions. The application has to know that: this decision is what the warning
|
||||
/// is built on.
|
||||
///
|
||||
/// * `dbus_session`: the session bus is there (`DBUS_SESSION_BUS_ADDRESS`).
|
||||
/// * `portal_service`: the file chooser portal answers on it.
|
||||
/// * `wayland_display`: a Wayland session runs (`WAYLAND_DISPLAY`).
|
||||
/// * `x11_display`: the window is an X11 one (`DISPLAY`, or XWayland).
|
||||
///
|
||||
/// `None` means the desktop's own chooser is the one that opens and there is nothing to say;
|
||||
/// `Some` carries the reason to report.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
fn desktop_chooser_fallback(dbus_session: bool, portal_service: bool, wayland_display: bool, x11_display: bool) -> Option<&'static str> {
|
||||
// Without a session bus there is no bus to reach the portal on, so the chooser is not the
|
||||
// desktop's own whatever else is there.
|
||||
if !dbus_session {
|
||||
return Some(CHOOSER_NO_SESSION_BUS);
|
||||
}
|
||||
// The bus is there but the portal does not answer on it: `rfd` falls back to zenity.
|
||||
if !portal_service {
|
||||
return Some(CHOOSER_NO_PORTAL_SERVICE);
|
||||
}
|
||||
// Under XWayland the window is an X11 one while the session is a Wayland one, and the portal
|
||||
// refuses to parent its chooser to an X11 window: `rfd` falls back to zenity.
|
||||
if wayland_display && x11_display {
|
||||
return Some(CHOOSER_XWAYLAND);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix, not(target_os = "macos")))]
|
||||
mod backend_tests {
|
||||
use super::prefer_x11;
|
||||
@@ -1552,30 +1438,6 @@ mod backend_tests {
|
||||
assert!(prefer_x11(true, true, Some("niri"), over(Some("x11"))));
|
||||
}
|
||||
|
||||
/// The file chooser must say when it is NOT the desktop's own: `rfd` falls back to zenity
|
||||
/// when the XDG portal is not there, and the user gets a foreign window with no explanation.
|
||||
#[test]
|
||||
fn the_file_picker_says_when_it_is_not_the_desktops_own_chooser() {
|
||||
use super::desktop_chooser_fallback;
|
||||
// The normal case: the portal is there, the desktop's own chooser opens, nothing to say.
|
||||
assert_eq!(desktop_chooser_fallback(true, true, true, false), None);
|
||||
// No session bus: the portal can not be reached at all, so `rfd` falls back to zenity.
|
||||
assert!(
|
||||
desktop_chooser_fallback(false, true, true, false).is_some(),
|
||||
"without a D-Bus session bus the file chooser is not the desktop's own: the user has to be told"
|
||||
);
|
||||
// The bus is there but the portal service is not: same fallback, same warning.
|
||||
assert!(
|
||||
desktop_chooser_fallback(true, false, true, false).is_some(),
|
||||
"without the desktop portal service the file chooser is not the desktop's own: the user has to be told"
|
||||
);
|
||||
// XWayland: the parent window is an X11 one and the portal refuses it, so `rfd` falls back.
|
||||
assert!(
|
||||
desktop_chooser_fallback(true, true, true, true).is_some(),
|
||||
"under XWayland the portal rejects the parent window: the user has to be told"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_desktops_title_bar_only_when_asked_for() {
|
||||
use super::titlebar_choice;
|
||||
@@ -1807,7 +1669,7 @@ mod viewport_tests {
|
||||
viewport.fullscreen = Some(fullscreen);
|
||||
viewport.native_pixels_per_point = Some(retina);
|
||||
super::fullscreen_content_insets(&mut raw, 32.0, zoom);
|
||||
ctx.run_ui(raw, |ui| super::content_ui(app, ui)).textures_delta.clear();
|
||||
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] {
|
||||
@@ -1853,88 +1715,6 @@ mod viewport_tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod msaa_tests {
|
||||
/// Safe mode uses default UI settings but edits the real graphics.json for the next start.
|
||||
#[test]
|
||||
fn safe_mode_preserves_graphics_choices_when_changing_antialiasing() {
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_ui_egui::scenario::Harness;
|
||||
|
||||
for choice in ["wayland", "x11"] {
|
||||
let mut h = Harness::new();
|
||||
let path = h.dir.join("graphics.json");
|
||||
let saved = json!({"window_system": choice, "adapter": "Selected GPU", "backend": "vulkan", "msaa": 8});
|
||||
std::fs::write(&path, saved.to_string()).unwrap();
|
||||
h.app.services.graphics_path = Some(path.to_string_lossy().into_owned());
|
||||
let defaults = h.app.preferences.clone();
|
||||
let stored_ui = json!({"preferences": {"window_system": "auto", "graphics_adapter": "Stale GPU", "msaa": 2,
|
||||
"display_quality": "fine"}})
|
||||
.to_string();
|
||||
super::restore_preferences(&mut h.app, Some(&stored_ui), Some(&saved), true);
|
||||
|
||||
assert_eq!(h.app.preferences.window_system, choice);
|
||||
assert_eq!(h.app.preferences.graphics_adapter, "Selected GPU");
|
||||
assert_eq!(h.app.preferences.msaa, defaults.msaa, "safe mode ignores saved anti-aliasing");
|
||||
assert_eq!(h.app.preferences.display_quality, defaults.display_quality, "safe mode ignores saved UI settings");
|
||||
assert_eq!(serde_json::from_str::<Value>(&std::fs::read_to_string(&path).unwrap()).unwrap(), saved, "startup does not write settings");
|
||||
|
||||
assert_eq!(h.call("ui.prefs", json!({"open": "Graphics"}))["ok"], true);
|
||||
assert_eq!(h.call("ui.prefs", json!({"msaa": 1}))["ok"], true);
|
||||
assert_eq!(h.call("ui.prefs", json!({"button": "ok"}))["ok"], true);
|
||||
let after: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
assert_eq!(after, json!({"window_system": choice, "adapter": "Selected GPU", "backend": "vulkan", "msaa": 1}));
|
||||
|
||||
let mut normal = Harness::new();
|
||||
super::restore_preferences(&mut normal.app, Some(&stored_ui), Some(&after), false);
|
||||
assert_eq!(normal.app.preferences.window_system, choice);
|
||||
assert_eq!(normal.app.preferences.graphics_adapter, "Selected GPU");
|
||||
assert_eq!(normal.app.preferences.display_quality, "fine", "normal startup still restores UI settings");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_invalid_startup_window_system_uses_automatic() {
|
||||
use serde_json::json;
|
||||
|
||||
for saved in [None, Some(json!(null)), Some(json!({})), Some(json!({"window_system": 7})), Some(json!({"window_system": "unknown"}))] {
|
||||
let mut app = super::SolveApp::new(super::Session::default(), super::Services::default());
|
||||
app.preferences.window_system = "wayland".into();
|
||||
super::restore_graphics_preferences(&mut app, saved.as_ref());
|
||||
assert_eq!(app.preferences.window_system, "auto", "{saved:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A manual graphics.json choice wins over stale app.ron preferences and survives an
|
||||
/// unrelated graphics edit (#10116), including on a fresh launch with no saved UI state.
|
||||
#[test]
|
||||
fn startup_window_system_survives_changing_antialiasing() {
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_ui_egui::scenario::Harness;
|
||||
|
||||
for choice in ["wayland", "x11", "auto", " wayland ", " x11 "] {
|
||||
for stale in [None, Some("auto"), Some("wayland"), Some("x11")] {
|
||||
let mut h = Harness::new();
|
||||
if let Some(stale) = stale {
|
||||
h.app.load_prefs(&json!({"preferences": {"window_system": stale}}).to_string());
|
||||
}
|
||||
let path = h.dir.join("graphics.json");
|
||||
std::fs::write(&path, json!({"window_system": choice, "backend": "vulkan", "adapter": "Selected GPU", "msaa": 4}).to_string())
|
||||
.unwrap();
|
||||
h.app.services.graphics_path = Some(path.to_string_lossy().into_owned());
|
||||
let saved: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
super::restore_graphics_preferences(&mut h.app, Some(&saved));
|
||||
|
||||
let result = h.call("ui.prefs", json!({"msaa": 2}));
|
||||
assert_eq!(result["ok"], true, "{result}");
|
||||
let after: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
assert_eq!(after["window_system"], choice.trim(), "saved choice {choice:?}, stale UI choice {stale:?}");
|
||||
assert_eq!(result["result"]["window_system"], choice.trim());
|
||||
assert_eq!(after["msaa"], 2);
|
||||
assert_eq!(after["backend"], "vulkan");
|
||||
assert_eq!(after["adapter"], "Selected GPU");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_launch_adopts_the_previous_release_settings() {
|
||||
let d = std::env::temp_dir().join(format!("solidcraft-adopt-settings-{}", std::process::id()));
|
||||
|
||||
@@ -434,7 +434,7 @@ mod tests {
|
||||
let mut app = SolveApp::new(solidcraft_engine::Session::default(), solidcraft_ui_egui::Services::default());
|
||||
app.home.open = false;
|
||||
let ctx = Context::default();
|
||||
ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |_| {}).textures_delta.clear();
|
||||
let _ = ctx.run_ui(egui::RawInput { focused: true, ..Default::default() }, |_| {});
|
||||
let mut bridge = NavBridge { shared: Arc::new(Mutex::new(Shared::default())) };
|
||||
let queue = |bridge: &NavBridge| {
|
||||
let mut shared = bridge.shared.lock().unwrap();
|
||||
|
||||
@@ -1914,7 +1914,6 @@ mod tests {
|
||||
|
||||
fn wheel() -> egui::Event {
|
||||
egui::Event::MouseWheel {
|
||||
source: egui::MouseWheelSource::Unknown,
|
||||
unit: egui::MouseWheelUnit::Line,
|
||||
delta: egui::vec2(0.0, -1.0),
|
||||
modifiers: Default::default(),
|
||||
|
||||
@@ -40,10 +40,10 @@ fn connect_to(path: &Path) -> Option<UnixStream> {
|
||||
|
||||
/// The axes of one record, or `None` for a record that is not motion.
|
||||
///
|
||||
/// The daemon reports x to the right, y up and z away from the user; our raw HID frame is x
|
||||
/// right, y towards the user and z down. Swap and negate y/z and ry/rz to match that frame.
|
||||
/// With nothing set in `spnavcfg` the two readers give the same motion; what the user swaps
|
||||
/// or inverts there then carries over.
|
||||
/// The daemon's frame is right-handed with x to the right, y away from the user and z up; ours is
|
||||
/// the device's raw one, x right, y towards the user and z down, so y and z (and the rotations
|
||||
/// about them) change sign. With nothing set in `spnavcfg` the two give the same motion; what
|
||||
/// the user swaps or inverts there then carries over.
|
||||
fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
|
||||
let mut data = [0_i32; 8];
|
||||
for (slot, bytes) in data.iter_mut().zip(record.as_chunks::<4>().0) {
|
||||
@@ -51,8 +51,8 @@ fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
|
||||
}
|
||||
let [kind, x, y, z, rx, ry, rz, _period] = data;
|
||||
(kind == 0).then(|| Axes {
|
||||
translation: [unit(x), unit(-i64::from(z)), unit(-i64::from(y))],
|
||||
rotation: [unit(rx), unit(-i64::from(rz)), unit(-i64::from(ry))],
|
||||
translation: [unit(x), unit(-i64::from(y)), unit(-i64::from(z))],
|
||||
rotation: [unit(rx), unit(-i64::from(ry)), unit(-i64::from(rz))],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -149,28 +149,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn motion_records_become_axes_in_the_devices_raw_frame() {
|
||||
// Each daemon axis reaches only its matching raw HID axis, in both directions.
|
||||
for (daemon_axis, raw_axis, sign) in [(0, 0, 1), (1, 2, -1), (2, 1, -1), (3, 3, 1), (4, 5, -1), (5, 4, -1)] {
|
||||
for value in [-500, 500] {
|
||||
let mut data = [0; 8];
|
||||
data[daemon_axis + 1] = value;
|
||||
let mut expected = [0; 6];
|
||||
expected[raw_axis] = sign * (value * 350 / 500) as i16;
|
||||
assert_eq!(decode(&record(data)), Some(Axes::from(expected)), "daemon axis {daemon_axis}, value {value}");
|
||||
}
|
||||
}
|
||||
// Full deflection of the daemon's x, y and z is the device's full scale, and y and z flip.
|
||||
assert_eq!(decode(&record([0, 500, 500, 500, 0, 0, 0, 8])), Some(Axes { translation: [350, -350, -350], rotation: [0; 3] }));
|
||||
// Rotations follow translations: right-handed in both frames, so ry and rz flip as y and z do.
|
||||
assert_eq!(decode(&record([0, 0, 0, 0, 250, 250, 250, 8])), Some(Axes { translation: [0; 3], rotation: [175, -175, -175] }));
|
||||
// Rest is a motion record of zeros.
|
||||
assert_eq!(decode(&record([0, 0, 0, 0, 0, 0, 0, 1200])), Some(Axes::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_spnavcfg_swaps_reaches_the_view() {
|
||||
// A push away is daemon z (raw y); swapping Y/Z makes it a lift (raw z).
|
||||
// The same swap changes a sideways tilt (daemon rz, raw ry) into a twist (raw rz).
|
||||
let before = decode(&record([0, 0, 0, 500, 0, 0, 250, 8]));
|
||||
let after = decode(&record([0, 0, 500, 0, 0, 250, 0, 8]));
|
||||
assert_eq!(before, Some(Axes { translation: [0, -350, 0], rotation: [0, -175, 0] }));
|
||||
assert_eq!(after, Some(Axes { translation: [0, 0, -350], rotation: [0, 0, -175] }));
|
||||
// The daemon sends a push away as y. With Y and Z swapped in spnavcfg it sends the same push
|
||||
// as z, and the view gets a lift (raw z) instead of the zoom (raw y).
|
||||
let before = decode(&record([0, 0, 500, 0, 0, 0, 0, 8])).map(|axes| axes.translation);
|
||||
let after = decode(&record([0, 0, 0, 500, 0, 0, 0, 8])).map(|axes| axes.translation);
|
||||
assert_eq!(before, Some([0, -350, 0]));
|
||||
assert_eq!(after, Some([0, 0, -350]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -185,7 +179,7 @@ mod tests {
|
||||
// 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]));
|
||||
assert_eq!(extreme.map(|axes| axes.translation[0]), Some(i16::MIN));
|
||||
assert_eq!(extreme.map(|axes| axes.translation), Some([i16::MIN, i16::MIN, i16::MAX]));
|
||||
assert_eq!(extreme.map(|axes| axes.translation[1]), Some(i16::MAX));
|
||||
assert_eq!(extreme.map(|axes| axes.rotation[0]), Some(i16::MAX));
|
||||
}
|
||||
|
||||
|
||||
@@ -311,18 +311,13 @@ pub const SYSTEM_VERSION_COMPAT_ENV: &str = "SYSTEM_VERSION_COMPAT";
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const OCLP_NVRAM_KEY: &str = "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:OCLP-Version";
|
||||
|
||||
/// Where macOS keeps its `nvram` tool.
|
||||
#[cfg(target_os = "macos")]
|
||||
const NVRAM_TOOL: &str = "/usr/sbin/nvram";
|
||||
|
||||
/// `true` if the OCLP NVRAM key is present on this system.
|
||||
///
|
||||
/// Runs `/usr/sbin/nvram <key>` and treats exit 0 as present. The full path keeps a `PATH` that
|
||||
/// lacks `/usr/sbin`, or lists a look-alike first, from changing the answer. Any error (missing
|
||||
/// binary, permission denied, key absent) returns `false`: this is a best-effort probe.
|
||||
/// Runs `nvram <key>` and treats exit 0 as present. Any error (missing binary,
|
||||
/// permission denied, key absent) returns `false` — this is a best-effort probe.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn oclp_nvram_present() -> bool {
|
||||
std::process::Command::new(NVRAM_TOOL).arg(OCLP_NVRAM_KEY).output().map(|o| o.status.success()).unwrap_or(false)
|
||||
std::process::Command::new("nvram").arg(OCLP_NVRAM_KEY).output().map(|o| o.status.success()).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether to re-launch with `SYSTEM_VERSION_COMPAT=1`: it is not set yet and OCLP shows through
|
||||
@@ -339,8 +334,8 @@ fn compat_wanted(compat_set: bool, nvram: impl FnOnce() -> bool) -> bool {
|
||||
///
|
||||
/// `SYSTEM_VERSION_COMPAT=1` must be present **before** the process starts: setting it
|
||||
/// with `std::env::set_var` inside `main` is too late because the macOS frameworks have
|
||||
/// already queried the OS version by then, so the program starts itself again with the variable
|
||||
/// set and waits for that child.
|
||||
/// already queried the OS version by then. Re-launching is the only safe approach that
|
||||
/// works without `unsafe` (a true `execve` replacement would require it).
|
||||
///
|
||||
/// Returns `Ok(false)` immediately when:
|
||||
/// - `SYSTEM_VERSION_COMPAT` is already set (re-launched process, no loop), or
|
||||
@@ -357,7 +352,7 @@ pub fn relaunch_with_version_compat() -> Result<bool, String> {
|
||||
return Ok(false);
|
||||
}
|
||||
log::info!("startup: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
|
||||
solidcraft_engine::note!("SolidCraft: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
|
||||
eprintln!("SolidCraft: OCLP detected; re-launching with {}=1", SYSTEM_VERSION_COMPAT_ENV);
|
||||
let exe = std::env::current_exe().map_err(|e| format!("could not find own executable: {e}"))?;
|
||||
let status = std::process::Command::new(&exe)
|
||||
.args(std::env::args_os().skip(1))
|
||||
|
||||
@@ -17,13 +17,12 @@ assets/
|
||||
ui/design/tools/ # authoring bases, checks and included review app
|
||||
ui/source/ # Rust viewport glyphs, markers and navigation
|
||||
branding/web/source/og.png # shared social preview card
|
||||
branding/logo/ # print/web logo files; guide in branding/README.md
|
||||
styles/ui/palettes.rs # light/dark defaults
|
||||
styles/web/loading.css # web loading/canvas styles
|
||||
appearances/library.rs # built-in visual presets
|
||||
materials/library.rs # physical defaults and colours
|
||||
shaders/viewport.wgsl # original viewport shader
|
||||
fonts/biz-udp-gothic/ # unmodified font and OFL licence
|
||||
fonts/<family>/ # unmodified fonts and OFL licences
|
||||
locales/{en,de,ja,ko}.json # shared offline catalogs
|
||||
platforms/{macos,linux,windows}/ # bundle, freedesktop and WiX templates
|
||||
platforms/macos/dmg/ # disk image window: layout, dmgbuild settings, background art
|
||||
@@ -80,16 +79,14 @@ The CLI still supports arbitrary PNG/ICO exports with `solidcraft-cli icon`.
|
||||
| Linux, Nix, deb/rpm, tar, Flatpak, Snap | Copy canonical hicolor images; icon name stays `io.tangencylabs.solidcraft` |
|
||||
| AppImage | Uses the Linux 256 px image and `.DirIcon` alias |
|
||||
| FreeBSD | Shares Linux hicolor images and freedesktop metadata |
|
||||
| Web | HTML/Trunk and `manifest.webmanifest` stay in `apps/solidcraft-web/`; copies Japanese font/licence to `fonts/`, uses the collected CSS, and copies the Linux 32/256/512 px images to `icons/` for the favicon and installed app |
|
||||
| Web | HTML/Trunk and `manifest.webmanifest` stay in `apps/solidcraft-web/`; copies the Japanese font/licence to `fonts/` and the Noto Sans Arabic, KR, SC and TC fonts with their licences to `fonts/<family>/` (fetched when a drawing's text needs them), uses the collected CSS, and copies the Linux 32/256/512 px images to `icons/` for the favicon and installed app |
|
||||
| Docs and link previews | `xtask book` copies the shared card to the existing `/solidcraft/og.png` public path |
|
||||
|
||||
## Code and provenance
|
||||
|
||||
Resources remain compiled by their owning layer through existing APIs:
|
||||
`ui-egui::icons::paint_icon` and `Tokens` at L5, model presets in L2 `doc`, and sketch
|
||||
fonts at L1. The sketch crate owns its built-in Liberation Sans font and OFL
|
||||
licence under `crates/sketch/assets/fonts/liberation-sans/`, so Cargo source
|
||||
packages and vendored builds include both files. State, projection, interaction, commands and GPU bindings stay in
|
||||
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.
|
||||
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# SolidCraft brand kit
|
||||
|
||||
Logo files for stickers, shirts, the web and social posts. Everything in `logo/` is drawn
|
||||
from the app icon (`assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg`),
|
||||
which is also the last frame of the loading animation (`assets/styles/web/loading-logo.svg`).
|
||||
The SVGs are plain paths: no scripts, fonts, animation or external links, so any printer can use them.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Use it for |
|
||||
|---|---|
|
||||
| `logo/solidcraft-mark.svg` | The mark in full colour. First choice for stickers, print and the web. |
|
||||
| `logo/solidcraft-mark-mono-black.svg` | One-colour print on light material: screen-printed shirts, stamps, engraving, vinyl. |
|
||||
| `logo/solidcraft-mark-mono-white.svg` | One-colour print on dark material, such as a white print on a dark shirt. |
|
||||
| `logo/solidcraft-mark-512.png`, `-1024.png`, `-2048.png` | Transparent square PNGs for places that don't take SVG: avatars, social posts, slides, print shops that ask for a bitmap (send the 2048). |
|
||||
| `logo/solidcraft-wordmark.svg` | The name on its own, outlined to paths. |
|
||||
| `logo/solidcraft-lockup-horizontal.svg` | Mark and name side by side: headers, banners, shirt fronts, wide stickers. |
|
||||
| `web/source/og.png` | The existing link preview card for the docs site. |
|
||||
|
||||
The gaps between the faces are part of the logo. In the colour and one-colour files they are
|
||||
transparent, so the material shows through. For a die-cut sticker, cut around the outer shape
|
||||
and let the backing colour (white is best) fill the gaps.
|
||||
|
||||
## Colours
|
||||
|
||||
The mark's five faces each use a two-stop gradient from the icon source:
|
||||
|
||||
| Face | From | To |
|
||||
|---|---|---|
|
||||
| Top | `#51adff` | `#2b83f1` |
|
||||
| Left | `#258df5` | `#1461da` |
|
||||
| Right | `#1b66cf` | `#124596` |
|
||||
| Lower step (top) | `#459fff` | `#2379e7` |
|
||||
| Upper step (inner side) | `#1d63c6` | `#15499e` |
|
||||
|
||||
Other colours, all already used in the project:
|
||||
|
||||
| Colour | Hex | Where it comes from |
|
||||
|---|---|---|
|
||||
| Wordmark blue | `#15499e` | loading logo `--word` |
|
||||
| Interface accent | `#2678da` | light theme `accent` token (`assets/styles/ui/palettes.rs`) |
|
||||
| Light background | `#e8ebf0` | web loading screen (`assets/styles/web/loading.css`) |
|
||||
| Dark background | `#1f232b` | dark theme `file_background` token |
|
||||
| Black / white | `#000000` / `#ffffff` | one-colour versions only |
|
||||
|
||||
If a printer can't do gradients, use the one-colour files, or fill every face with `#2678da`.
|
||||
|
||||
## Clear space and size
|
||||
|
||||
- Keep empty space around the mark of at least one fifth of its height on every side. Treat
|
||||
the lockup the same way, measured from the mark.
|
||||
- Smallest mark: 16 px tall on screen, 12 mm tall in print. Below that the gaps fill in.
|
||||
- Smallest lockup: 120 px wide on screen, 40 mm wide in print.
|
||||
|
||||
## Don'ts
|
||||
|
||||
- Don't recolour it outside the colours above.
|
||||
- Don't stretch, squash, rotate, skew or crop it. Scale it evenly.
|
||||
- Don't add shadows, outlines, glows, bevels or other effects.
|
||||
- Don't fill or close the gaps between the faces, or move the faces apart.
|
||||
- Don't rearrange the lockup, retype the name in another font, or put the mark on a busy
|
||||
background where it loses contrast.
|
||||
|
||||
## Name and logo
|
||||
|
||||
The SolidCraft code and these files are licensed MIT OR Apache-2.0 (see the [README](../../README.md#licence)),
|
||||
so you may copy and change them. The name SolidCraft and this logo identify the SolidCraft
|
||||
project. Use them to refer to the project, for example in an article, a talk, or a sticker for a
|
||||
meetup. If you publish a fork or a modified build, give it its own name and logo so people
|
||||
don't mistake it for SolidCraft.
|
||||
|
||||
The wordmark is set in IBM Plex Sans Bold (SIL Open Font License 1.1, the interface font in
|
||||
`assets/fonts/ibm-plex-sans/`) and outlined, so you don't need the font to print it.
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 2748.71 904.436" width="2749" height="904" role="img"><title>SolidCraft</title><defs><linearGradient id="sc1" x1="619.14" y1="179.734" x2="694.687" y2="396.761" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d63c6"/><stop offset="1" stop-color="#15499e"/></linearGradient><linearGradient id="sc2" x1="619.14" y1="413.244" x2="790.839" y2="605.547" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#459fff"/><stop offset="1" stop-color="#2379e7"/></linearGradient><linearGradient id="sc3" x1="632.876" y1="399.508" x2="783.971" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b66cf"/><stop offset="1" stop-color="#124596"/></linearGradient><linearGradient id="sc4" x1="207.063" y1="303.357" x2="426.837" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#258df5"/><stop offset="1" stop-color="#1461da"/></linearGradient><linearGradient id="sc5" x1="385.63" y1="53.364" x2="522.989" y2="510.769" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#51adff"/><stop offset="1" stop-color="#2b83f1"/></linearGradient></defs><g><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z" fill="url(#sc1)"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z" fill="url(#sc2)"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z" fill="url(#sc3)"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z" fill="url(#sc4)"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z" fill="url(#sc5)"/></g><path fill="#15499e" transform="translate(1092.78 632.20) scale(0.36281)" d="M303 12Q212 12 147.5 -20Q83 -52 38 -101L138 -202Q173 -162 217.5 -142Q262 -122 313 -122Q369 -122 396 -145Q423 -168 423 -206Q423 -225 416.5 -240.5Q410 -256 392.5 -266.5Q375 -277 342 -281L273 -290Q200 -300 152.5 -326.5Q105 -353 82 -396.5Q59 -440 59 -498Q59 -561 90.5 -608.5Q122 -656 180.5 -683Q239 -710 321 -710Q400 -710 460 -684.5Q520 -659 563 -612L462 -510Q437 -539 401.5 -557.5Q366 -576 312 -576Q261 -576 236 -558Q211 -540 211 -508Q211 -484 219.5 -469Q228 -454 246.5 -446Q265 -438 294 -433L363 -422Q435 -411 482 -385Q529 -359 552 -317Q575 -275 575 -215Q575 -148 543.5 -97Q512 -46 451 -17Q390 12 303 12ZM926 12Q849 12 794 -21.5Q739 -55 709.5 -117Q680 -179 680 -263Q680 -348 709.5 -409Q739 -470 794 -503.5Q849 -537 926 -537Q1003 -537 1058 -503.5Q1113 -470 1142.5 -409Q1172 -348 1172 -263Q1172 -179 1142.5 -117Q1113 -55 1058 -21.5Q1003 12 926 12ZM926 -105Q970 -105 994 -132.5Q1018 -160 1018 -209V-316Q1018 -366 994 -393Q970 -420 926 -420Q883 -420 858.5 -393Q834 -366 834 -316V-209Q834 -160 858.5 -132.5Q883 -105 926 -105ZM1511 0H1444Q1368 0 1332.5 -37Q1297 -74 1297 -144V-740H1445V-115H1511ZM1694 -581Q1649 -581 1628.5 -602Q1608 -623 1608 -654V-676Q1608 -708 1628.5 -728.5Q1649 -749 1694 -749Q1739 -749 1759.5 -728.5Q1780 -708 1780 -676V-654Q1780 -623 1759.5 -602Q1739 -581 1694 -581ZM1620 0V-525H1768V0ZM2248 0V-88H2243Q2232 -45 2192.5 -16.5Q2153 12 2102 12Q2036 12 1991 -19.5Q1946 -51 1923 -112.5Q1900 -174 1900 -263Q1900 -352 1923 -413Q1946 -474 1991 -505.5Q2036 -537 2102 -537Q2153 -537 2192.5 -509Q2232 -481 2243 -437H2248V-740H2396V0ZM2151 -108Q2179 -108 2201 -117Q2223 -126 2235.5 -143.5Q2248 -161 2248 -185V-340Q2248 -364 2235.5 -381.5Q2223 -399 2201 -408Q2179 -417 2151 -417Q2109 -417 2081.5 -387.5Q2054 -358 2054 -308V-217Q2054 -168 2081.5 -138Q2109 -108 2151 -108ZM2844 12Q2750 12 2680.5 -28Q2611 -68 2573 -147Q2535 -226 2535 -344Q2535 -462 2573 -543.5Q2611 -625 2680.5 -667.5Q2750 -710 2844 -710Q2940 -710 3003.5 -670Q3067 -630 3105 -546L2974 -478Q2961 -522 2930.5 -548.5Q2900 -575 2844 -575Q2777 -575 2736.5 -531.5Q2696 -488 2696 -405V-293Q2696 -211 2736.5 -167Q2777 -123 2844 -123Q2900 -123 2933.5 -153.5Q2967 -184 2983 -227L3107 -155Q3068 -76 3004 -32Q2940 12 2844 12ZM3373 0H3225V-525H3373V-411H3378Q3384 -440 3400 -466Q3416 -492 3443.5 -508.5Q3471 -525 3512 -525H3538V-387H3501Q3458 -387 3429.5 -379.5Q3401 -372 3387 -355Q3373 -338 3373 -307ZM4105 0H4023Q3996 0 3972.5 -13.5Q3949 -27 3935 -53.5Q3921 -80 3921 -117V-130L3953 -92H3917Q3905 -41 3864 -14.5Q3823 12 3763 12Q3684 12 3642 -30.5Q3600 -73 3600 -141Q3600 -197 3627.5 -233Q3655 -269 3705.5 -287Q3756 -305 3825 -305H3908V-338Q3908 -376 3888 -398.5Q3868 -421 3821 -421Q3777 -421 3751 -402Q3725 -383 3708 -359L3620 -437Q3652 -484 3701 -510.5Q3750 -537 3832 -537Q3943 -537 3999.5 -487.5Q4056 -438 4056 -345V-115H4105ZM3908 -221H3836Q3793 -221 3771 -206Q3749 -191 3749 -162V-147Q3749 -119 3767 -105Q3785 -91 3818 -91Q3843 -91 3863 -98Q3883 -105 3895.5 -120Q3908 -135 3908 -159ZM4250 0V-410H4176V-525H4250V-587Q4250 -663 4290.5 -701.5Q4331 -740 4407 -740H4503V-625H4398V-525H4503V-410H4398V0ZM4877 0H4789Q4713 0 4673 -39Q4633 -78 4633 -153V-410H4559V-525H4596Q4626 -525 4637 -539.5Q4648 -554 4648 -581V-667H4781V-525H4885V-410H4781V-115H4877Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 32 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><g fill="#000000"><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 612 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><g fill="#ffffff"><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 612 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><defs><linearGradient id="sc1" x1="619.14" y1="179.734" x2="694.687" y2="396.761" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d63c6"/><stop offset="1" stop-color="#15499e"/></linearGradient><linearGradient id="sc2" x1="619.14" y1="413.244" x2="790.839" y2="605.547" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#459fff"/><stop offset="1" stop-color="#2379e7"/></linearGradient><linearGradient id="sc3" x1="632.876" y1="399.508" x2="783.971" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b66cf"/><stop offset="1" stop-color="#124596"/></linearGradient><linearGradient id="sc4" x1="207.063" y1="303.357" x2="426.837" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#258df5"/><stop offset="1" stop-color="#1461da"/></linearGradient><linearGradient id="sc5" x1="385.63" y1="53.364" x2="522.989" y2="510.769" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#51adff"/><stop offset="1" stop-color="#2b83f1"/></linearGradient></defs><g><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z" fill="url(#sc1)"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z" fill="url(#sc2)"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z" fill="url(#sc3)"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z" fill="url(#sc4)"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z" fill="url(#sc5)"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="38.0 -749.0 4847.0 761.0" width="4847" height="761" role="img"><title>SolidCraft</title><path fill="#15499e" d="M303 12Q212 12 147.5 -20Q83 -52 38 -101L138 -202Q173 -162 217.5 -142Q262 -122 313 -122Q369 -122 396 -145Q423 -168 423 -206Q423 -225 416.5 -240.5Q410 -256 392.5 -266.5Q375 -277 342 -281L273 -290Q200 -300 152.5 -326.5Q105 -353 82 -396.5Q59 -440 59 -498Q59 -561 90.5 -608.5Q122 -656 180.5 -683Q239 -710 321 -710Q400 -710 460 -684.5Q520 -659 563 -612L462 -510Q437 -539 401.5 -557.5Q366 -576 312 -576Q261 -576 236 -558Q211 -540 211 -508Q211 -484 219.5 -469Q228 -454 246.5 -446Q265 -438 294 -433L363 -422Q435 -411 482 -385Q529 -359 552 -317Q575 -275 575 -215Q575 -148 543.5 -97Q512 -46 451 -17Q390 12 303 12ZM926 12Q849 12 794 -21.5Q739 -55 709.5 -117Q680 -179 680 -263Q680 -348 709.5 -409Q739 -470 794 -503.5Q849 -537 926 -537Q1003 -537 1058 -503.5Q1113 -470 1142.5 -409Q1172 -348 1172 -263Q1172 -179 1142.5 -117Q1113 -55 1058 -21.5Q1003 12 926 12ZM926 -105Q970 -105 994 -132.5Q1018 -160 1018 -209V-316Q1018 -366 994 -393Q970 -420 926 -420Q883 -420 858.5 -393Q834 -366 834 -316V-209Q834 -160 858.5 -132.5Q883 -105 926 -105ZM1511 0H1444Q1368 0 1332.5 -37Q1297 -74 1297 -144V-740H1445V-115H1511ZM1694 -581Q1649 -581 1628.5 -602Q1608 -623 1608 -654V-676Q1608 -708 1628.5 -728.5Q1649 -749 1694 -749Q1739 -749 1759.5 -728.5Q1780 -708 1780 -676V-654Q1780 -623 1759.5 -602Q1739 -581 1694 -581ZM1620 0V-525H1768V0ZM2248 0V-88H2243Q2232 -45 2192.5 -16.5Q2153 12 2102 12Q2036 12 1991 -19.5Q1946 -51 1923 -112.5Q1900 -174 1900 -263Q1900 -352 1923 -413Q1946 -474 1991 -505.5Q2036 -537 2102 -537Q2153 -537 2192.5 -509Q2232 -481 2243 -437H2248V-740H2396V0ZM2151 -108Q2179 -108 2201 -117Q2223 -126 2235.5 -143.5Q2248 -161 2248 -185V-340Q2248 -364 2235.5 -381.5Q2223 -399 2201 -408Q2179 -417 2151 -417Q2109 -417 2081.5 -387.5Q2054 -358 2054 -308V-217Q2054 -168 2081.5 -138Q2109 -108 2151 -108ZM2844 12Q2750 12 2680.5 -28Q2611 -68 2573 -147Q2535 -226 2535 -344Q2535 -462 2573 -543.5Q2611 -625 2680.5 -667.5Q2750 -710 2844 -710Q2940 -710 3003.5 -670Q3067 -630 3105 -546L2974 -478Q2961 -522 2930.5 -548.5Q2900 -575 2844 -575Q2777 -575 2736.5 -531.5Q2696 -488 2696 -405V-293Q2696 -211 2736.5 -167Q2777 -123 2844 -123Q2900 -123 2933.5 -153.5Q2967 -184 2983 -227L3107 -155Q3068 -76 3004 -32Q2940 12 2844 12ZM3373 0H3225V-525H3373V-411H3378Q3384 -440 3400 -466Q3416 -492 3443.5 -508.5Q3471 -525 3512 -525H3538V-387H3501Q3458 -387 3429.5 -379.5Q3401 -372 3387 -355Q3373 -338 3373 -307ZM4105 0H4023Q3996 0 3972.5 -13.5Q3949 -27 3935 -53.5Q3921 -80 3921 -117V-130L3953 -92H3917Q3905 -41 3864 -14.5Q3823 12 3763 12Q3684 12 3642 -30.5Q3600 -73 3600 -141Q3600 -197 3627.5 -233Q3655 -269 3705.5 -287Q3756 -305 3825 -305H3908V-338Q3908 -376 3888 -398.5Q3868 -421 3821 -421Q3777 -421 3751 -402Q3725 -383 3708 -359L3620 -437Q3652 -484 3701 -510.5Q3750 -537 3832 -537Q3943 -537 3999.5 -487.5Q4056 -438 4056 -345V-115H4105ZM3908 -221H3836Q3793 -221 3771 -206Q3749 -191 3749 -162V-147Q3749 -119 3767 -105Q3785 -91 3818 -91Q3843 -91 3863 -98Q3883 -105 3895.5 -120Q3908 -135 3908 -159ZM4250 0V-410H4176V-525H4250V-587Q4250 -663 4290.5 -701.5Q4331 -740 4407 -740H4503V-625H4398V-525H4503V-410H4398V0ZM4877 0H4789Q4713 0 4673 -39Q4633 -78 4633 -153V-410H4559V-525H4596Q4626 -525 4637 -539.5Q4648 -554 4648 -581V-667H4781V-525H4885V-410H4781V-115H4877Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -0,0 +1,93 @@
|
||||
Copyright 2022 The Noto Project Authors (https://github.com/notofonts/arabic)
|
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1.
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
https://openfontlicense.org
|
||||
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font creation
|
||||
efforts of academic and linguistic communities, and to provide a free and
|
||||
open framework in which fonts may be shared and improved in partnership
|
||||
with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply
|
||||
to any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software components as
|
||||
distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting,
|
||||
or substituting -- in part or in whole -- any of the components of the
|
||||
Original Version, by changing formats or by porting the Font Software to a
|
||||
new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify,
|
||||
redistribute, and sell modified and unmodified copies of the Font
|
||||
Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components,
|
||||
in Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the corresponding
|
||||
Copyright Holder. This restriction only applies to the primary font name as
|
||||
presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created
|
||||
using the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,92 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,92 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -0,0 +1,92 @@
|
||||
This Font Software is licensed under the SIL Open Font License,
|
||||
Version 1.1.
|
||||
|
||||
This license is copied below, and is also available with a FAQ at:
|
||||
http://scripts.sil.org/OFL
|
||||
|
||||
-----------------------------------------------------------
|
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
|
||||
-----------------------------------------------------------
|
||||
|
||||
PREAMBLE
|
||||
The goals of the Open Font License (OFL) are to stimulate worldwide
|
||||
development of collaborative font projects, to support the font
|
||||
creation efforts of academic and linguistic communities, and to
|
||||
provide a free and open framework in which fonts may be shared and
|
||||
improved in partnership with others.
|
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and
|
||||
redistributed freely as long as they are not sold by themselves. The
|
||||
fonts, including any derivative works, can be bundled, embedded,
|
||||
redistributed and/or sold with any software provided that any reserved
|
||||
names are not used by derivative works. The fonts and derivatives,
|
||||
however, cannot be released under any other type of license. The
|
||||
requirement for fonts to remain under this license does not apply to
|
||||
any document created using the fonts or their derivatives.
|
||||
|
||||
DEFINITIONS
|
||||
"Font Software" refers to the set of files released by the Copyright
|
||||
Holder(s) under this license and clearly marked as such. This may
|
||||
include source files, build scripts and documentation.
|
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the
|
||||
copyright statement(s).
|
||||
|
||||
"Original Version" refers to the collection of Font Software
|
||||
components as distributed by the Copyright Holder(s).
|
||||
|
||||
"Modified Version" refers to any derivative made by adding to,
|
||||
deleting, or substituting -- in part or in whole -- any of the
|
||||
components of the Original Version, by changing formats or by porting
|
||||
the Font Software to a new environment.
|
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical
|
||||
writer or other person who contributed to the Font Software.
|
||||
|
||||
PERMISSION & CONDITIONS
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of the Font Software, to use, study, copy, merge, embed,
|
||||
modify, redistribute, and sell modified and unmodified copies of the
|
||||
Font Software, subject to the following conditions:
|
||||
|
||||
1) Neither the Font Software nor any of its individual components, in
|
||||
Original or Modified Versions, may be sold by itself.
|
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled,
|
||||
redistributed and/or sold with any software, provided that each copy
|
||||
contains the above copyright notice and this license. These can be
|
||||
included either as stand-alone text files, human-readable headers or
|
||||
in the appropriate machine-readable metadata fields within text or
|
||||
binary files as long as those fields can be easily viewed by the user.
|
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font
|
||||
Name(s) unless explicit written permission is granted by the
|
||||
corresponding Copyright Holder. This restriction only applies to the
|
||||
primary font name as presented to the users.
|
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
|
||||
Software shall not be used to promote, endorse or advertise any
|
||||
Modified Version, except to acknowledge the contribution(s) of the
|
||||
Copyright Holder(s) and the Author(s) or with their explicit written
|
||||
permission.
|
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole,
|
||||
must be distributed entirely under this license, and must not be
|
||||
distributed under any other license. The requirement for fonts to
|
||||
remain under this license does not apply to any document created using
|
||||
the Font Software.
|
||||
|
||||
TERMINATION
|
||||
This license becomes null and void if any of the above conditions are
|
||||
not met.
|
||||
|
||||
DISCLAIMER
|
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
|
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
|
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
|
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
|
||||
OTHER DEALINGS IN THE FONT SOFTWARE.
|
||||
@@ -60,6 +60,7 @@ circle_ttt false
|
||||
close false
|
||||
coil false
|
||||
colored_components false
|
||||
colored_components_off false
|
||||
combine false
|
||||
compare false
|
||||
component false
|
||||
@@ -160,7 +161,6 @@ min_radius false
|
||||
mirror false
|
||||
motion_link false
|
||||
move false
|
||||
nav_settings false
|
||||
new false
|
||||
normal false
|
||||
offset false
|
||||
@@ -178,6 +178,7 @@ pattern_rect false
|
||||
perforate false
|
||||
perspective false
|
||||
pipe false
|
||||
placeholder true
|
||||
plane false
|
||||
plane_along_path false
|
||||
plane_angle false
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Disable component colors</title>
|
||||
<path d="M9.235 17.706L15.059 20.882L15.059 48.941L9.235 44.926Z" fill="#328bd3"/>
|
||||
<path d="M35.176 9.235L15.059 20.882L15.059 48.941L36.765 35.139Z" fill="#236ead"/>
|
||||
<path d="M29.046 5L35.176 8.176L15.196 19.718L9.066 16.541Z" fill="#b9ddf8"/>
|
||||
<path d="M29.046 5.529L35.176 8.706L35.176 36.235L15.059 49.471L9.235 45.765L9.066 17.071Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.066 17.6L15.059 20.882L34.647 9.235" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.059 20.882L15.059 47.067" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M32.222 18.595L44.949 25.944L44.949 51.122L32.222 43.774Z" fill="#328bd3"/>
|
||||
<path d="M54.934 20.173L44.949 25.944L44.949 51.122L54.934 45.352Z" fill="#236ead"/>
|
||||
<path d="M42.059 12.412L54.934 20.173L44.949 25.944L32 17.706Z" fill="#b9ddf8"/>
|
||||
<path d="M41.159 11.766L54.934 20.173L54.934 45.352L44.949 51.122L31.164 42.715L31.164 17.536Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M32.222 18.595L44.949 25.944L54.934 20.173" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M44.949 25.944L44.949 51.122" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.299 39.062L31.015 46.411L31.015 59L18.299 51.652Z" fill="#328bd3"/>
|
||||
<path d="M41.921 40.121L31.015 46.411L31.015 59L41.921 52.711Z" fill="#236ead"/>
|
||||
<path d="M29.194 32.773L41.921 40.121L31.015 46.411L18.299 39.062Z" fill="#b9ddf8"/>
|
||||
<path d="M29.194 32.773L41.921 40.121L41.921 52.711L31.015 59L18.299 51.652L18.299 39.062Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.299 39.062L31.015 46.411L41.921 40.121" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M31.015 46.411L31.015 59" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Align drawing annotations</title>
|
||||
<path d="M12 6L12 58" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 12L51.5 12A2.5 2.5 0 0 1 54 14.5L54 17.5A2.5 2.5 0 0 1 51.5 20L22.5 20A2.5 2.5 0 0 1 20 17.5L20 14.5A2.5 2.5 0 0 1 22.5 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 28L40.5 28A2.5 2.5 0 0 1 43 30.5L43 33.5A2.5 2.5 0 0 1 40.5 36L22.5 36A2.5 2.5 0 0 1 20 33.5L20 30.5A2.5 2.5 0 0 1 22.5 28Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 44L47.5 44A2.5 2.5 0 0 1 50 46.5L50 49.5A2.5 2.5 0 0 1 47.5 52L22.5 52A2.5 2.5 0 0 1 20 49.5L20 46.5A2.5 2.5 0 0 1 22.5 44Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 962 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Align drawing views</title>
|
||||
<path d="M10.5 24L23.5 24A2.5 2.5 0 0 1 26 26.5L26 43.5A2.5 2.5 0 0 1 23.5 46L10.5 46A2.5 2.5 0 0 1 8 43.5L8 26.5A2.5 2.5 0 0 1 10.5 24Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M39.5 12L52.5 12A2.5 2.5 0 0 1 55 14.5L55 43.5A2.5 2.5 0 0 1 52.5 46L39.5 46A2.5 2.5 0 0 1 37 43.5L37 14.5A2.5 2.5 0 0 1 39.5 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6 52L58 52" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M17 29L17 40M46 18L46 39" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 846 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing balloon</title>
|
||||
<path d="M9.5 37L29.5 37A2.5 2.5 0 0 1 32 39.5L32 52.5A2.5 2.5 0 0 1 29.5 55L9.5 55A2.5 2.5 0 0 1 7 52.5L7 39.5A2.5 2.5 0 0 1 9.5 37Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 45L35 30L40 25" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 50L19 37L28 45Z" fill="#236ead"/>
|
||||
<circle cx="45" cy="17" r="12" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 651 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Base drawing view</title>
|
||||
<path d="M9.5 10L54.5 10A2.5 2.5 0 0 1 57 12.5L57 51.5A2.5 2.5 0 0 1 54.5 54L9.5 54A2.5 2.5 0 0 1 7 51.5L7 12.5A2.5 2.5 0 0 1 9.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M17.5 19L42.5 19A2.5 2.5 0 0 1 45 21.5L45 39.5A2.5 2.5 0 0 1 42.5 42L17.5 42A2.5 2.5 0 0 1 15 39.5L15 21.5A2.5 2.5 0 0 1 17.5 19Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="30" cy="30.5" r="6" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M47 46L53 46M50 43L50 49" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 851 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing centre mark</title>
|
||||
<circle cx="32" cy="32" r="18" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 6L32 21M32 27L32 37M32 43L32 58M6 32L21 32M27 32L37 32M43 32L58 32" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 420 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Check drawing</title>
|
||||
<path d="M14.5 7L49.5 7A2.5 2.5 0 0 1 52 9.5L52 54.5A2.5 2.5 0 0 1 49.5 57L14.5 57A2.5 2.5 0 0 1 12 54.5L12 9.5A2.5 2.5 0 0 1 14.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21 19L42 19M21 29L35 29" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27 44L37 53L57 30" fill="none" stroke="#ffffff" stroke-width="6.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27 44L37 53L57 30" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 730 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing datum</title>
|
||||
<path d="M6 53L58 53" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 52L24 38L40 38Z" fill="#236ead"/>
|
||||
<path d="M32 38L32 27" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21.5 7L42.5 7A2.5 2.5 0 0 1 45 9.5L45 24.5A2.5 2.5 0 0 1 42.5 27L21.5 27A2.5 2.5 0 0 1 19 24.5L19 9.5A2.5 2.5 0 0 1 21.5 7Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 631 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Detail drawing view</title>
|
||||
<path d="M9.5 11L30.5 11A2.5 2.5 0 0 1 33 13.5L33 45.5A2.5 2.5 0 0 1 30.5 48L9.5 48A2.5 2.5 0 0 1 7 45.5L7 13.5A2.5 2.5 0 0 1 9.5 11Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M13 18L23 18L23 33L29 33" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="23" cy="30" r="9" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M31 35L39 41" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47" cy="46" r="12" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M40 39L46 39L46 48L53 48" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1000 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing dimension</title>
|
||||
<path d="M10 10L10 55M54 10L54 55" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 14L54 14L54 36L10 36Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 49L44 49" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M11 49L23 43L23 55Z" fill="#236ead"/>
|
||||
<path d="M53 49L41 55L41 43Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 598 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Edit drawing table</title>
|
||||
<path d="M10.5 10L53.5 10A2.5 2.5 0 0 1 56 12.5L56 51.5A2.5 2.5 0 0 1 53.5 54L10.5 54A2.5 2.5 0 0 1 8 51.5L8 12.5A2.5 2.5 0 0 1 10.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L54 12L54 23L10 23Z" fill="#b8c3cf"/>
|
||||
<path d="M8 24L56 24M8 39L56 39M23 24L23 54" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M25 26L54 26L54 37L25 37Z" fill="#b9ddf8"/>
|
||||
<path d="M32 32L47 32M42 27L47 32L42 37" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 741 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Hole callout</title>
|
||||
<circle cx="18" cy="43" r="13" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="18" cy="43" r="6" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27 35L39 22L57 22" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23 40L27 28L34 35Z" fill="#236ead"/>
|
||||
<circle cx="44" cy="10" r="4.5" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M39 16L49 4" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 794 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Insert drawing image</title>
|
||||
<path d="M9.5 11L54.5 11A2.5 2.5 0 0 1 57 13.5L57 50.5A2.5 2.5 0 0 1 54.5 53L9.5 53A2.5 2.5 0 0 1 7 50.5L7 13.5A2.5 2.5 0 0 1 9.5 11Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="43" cy="23" r="5" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 45L24 27L36 41L42 34L54 47Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 622 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing leader</title>
|
||||
<path d="M8.5 34L33.5 34A2.5 2.5 0 0 1 36 36.5L36 51.5A2.5 2.5 0 0 1 33.5 54L8.5 54A2.5 2.5 0 0 1 6 51.5L6 36.5A2.5 2.5 0 0 1 8.5 34Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18 45L39 18L56 18" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 49L19 35L26 41Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 516 B |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing parts list</title>
|
||||
<path d="M10.5 10L53.5 10A2.5 2.5 0 0 1 56 12.5L56 51.5A2.5 2.5 0 0 1 53.5 54L10.5 54A2.5 2.5 0 0 1 8 51.5L8 12.5A2.5 2.5 0 0 1 10.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L54 12L54 23L10 23Z" fill="#b8c3cf"/>
|
||||
<path d="M8 24L56 24M8 39L56 39M23 24L23 54" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15.5" cy="31.5" r="3.5" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15.5" cy="46.5" r="3.5" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 31L48 31M30 46L48 46" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 958 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Print drawing</title>
|
||||
<path d="M18.5 7L45.5 7A2.5 2.5 0 0 1 48 9.5L48 29.5A2.5 2.5 0 0 1 45.5 32L18.5 32A2.5 2.5 0 0 1 16 29.5L16 9.5A2.5 2.5 0 0 1 18.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M8.5 24L55.5 24A2.5 2.5 0 0 1 58 26.5L58 49.5A2.5 2.5 0 0 1 55.5 52L8.5 52A2.5 2.5 0 0 1 6 49.5L6 26.5A2.5 2.5 0 0 1 8.5 24Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="48" cy="32" r="2.5" fill="#236ead" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.5 37L45.5 37A2.5 2.5 0 0 1 48 39.5L48 55.5A2.5 2.5 0 0 1 45.5 58L18.5 58A2.5 2.5 0 0 1 16 55.5L16 39.5A2.5 2.5 0 0 1 18.5 37Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23 44L41 44M23 51L36 51" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Projected drawing view</title>
|
||||
<path d="M9.5 33L31.5 33A2.5 2.5 0 0 1 34 35.5L34 52.5A2.5 2.5 0 0 1 31.5 55L9.5 55A2.5 2.5 0 0 1 7 52.5L7 35.5A2.5 2.5 0 0 1 9.5 33Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="20.5" cy="44" r="5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9.5 8L31.5 8A2.5 2.5 0 0 1 34 10.5L34 19.5A2.5 2.5 0 0 1 31.5 22L9.5 22A2.5 2.5 0 0 1 7 19.5L7 10.5A2.5 2.5 0 0 1 9.5 8Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9 25L9 30M32 25L32 30M38 44L54 44" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="3 5"/>
|
||||
<path d="M44 28L44 13" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M44 7L48 14L40 14Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Browse drawing resources</title>
|
||||
<path d="M15 7L51 7A3 3 0 0 1 54 10L54 48A3 3 0 0 1 51 51L15 51A3 3 0 0 1 12 48L12 10A3 3 0 0 1 15 7Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L46 12A3 3 0 0 1 49 15L49 53A3 3 0 0 1 46 56L10 56A3 3 0 0 1 7 53L7 15A3 3 0 0 1 10 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M14 20L42 20L42 47L14 47Z" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M14 38L42 38M28 38L28 47" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22 27L34 27L34 36L22 36Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 933 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Add drawing revision</title>
|
||||
<path d="M14.5 7L49.5 7A2.5 2.5 0 0 1 52 9.5L52 54.5A2.5 2.5 0 0 1 49.5 57L14.5 57A2.5 2.5 0 0 1 12 54.5L12 9.5A2.5 2.5 0 0 1 14.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M20 19L43 19M20 29L37 29" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M43 33L58 57L28 57Z" fill="#ffffff" stroke="#ffffff" stroke-width="4.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M43 33L58 57L28 57Z" fill="#b9ddf8" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 745 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Drawing revision table</title>
|
||||
<path d="M10.5 10L53.5 10A2.5 2.5 0 0 1 56 12.5L56 51.5A2.5 2.5 0 0 1 53.5 54L10.5 54A2.5 2.5 0 0 1 8 51.5L8 12.5A2.5 2.5 0 0 1 10.5 10Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 12L54 12L54 23L10 23Z" fill="#b8c3cf"/>
|
||||
<path d="M8 24L56 24M8 39L56 39M23 24L23 54" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 27L10 35L20 35ZM15 42L10 50L20 50Z" fill="#236ead"/>
|
||||
<path d="M31 31L49 31M31 46L49 46" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 752 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Section drawing view</title>
|
||||
<path d="M8 15L56 15L56 50L37 50L37 27L27 27L27 50L8 50Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 38L26 22M10 25L20 15M10 50L24 36M39 35L54 20M39 48L54 33M49 50L56 43" fill="none" stroke="#236ead" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 450 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Space drawing annotations</title>
|
||||
<path d="M28.5 9L53.5 9A2.5 2.5 0 0 1 56 11.5L56 14.5A2.5 2.5 0 0 1 53.5 17L28.5 17A2.5 2.5 0 0 1 26 14.5L26 11.5A2.5 2.5 0 0 1 28.5 9Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28.5 28L53.5 28A2.5 2.5 0 0 1 56 30.5L56 33.5A2.5 2.5 0 0 1 53.5 36L28.5 36A2.5 2.5 0 0 1 26 33.5L26 30.5A2.5 2.5 0 0 1 28.5 28Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28.5 47L53.5 47A2.5 2.5 0 0 1 56 49.5L56 52.5A2.5 2.5 0 0 1 53.5 55L28.5 55A2.5 2.5 0 0 1 26 52.5L26 49.5A2.5 2.5 0 0 1 28.5 47Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 19L15 25M15 38L15 44" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15 14L10 21L20 21ZM15 30L10 23L20 23ZM15 33L10 40L20 40ZM15 49L10 42L20 42Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Update drawing</title>
|
||||
<path d="M14.5 7L49.5 7A2.5 2.5 0 0 1 52 9.5L52 54.5A2.5 2.5 0 0 1 49.5 57L14.5 57A2.5 2.5 0 0 1 12 54.5L12 9.5A2.5 2.5 0 0 1 14.5 7Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.5 16L41.5 16A2.5 2.5 0 0 1 44 18.5L44 28.5A2.5 2.5 0 0 1 41.5 31L22.5 31A2.5 2.5 0 0 1 20 28.5L20 18.5A2.5 2.5 0 0 1 22.5 16Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M38 35A11 11 0 1 0 45 45" fill="none" stroke="#ffffff" stroke-width="6.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M38 35A11 11 0 1 0 45 45" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M44 40L32 37L40 28Z" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 900 B |
@@ -79,6 +79,17 @@ pub(crate) fn snap_hint(painter: &egui::Painter, c: Pos2, kind: SnapKind, col: C
|
||||
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",
|
||||
@@ -89,7 +100,7 @@ pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
|
||||
"Smooth" => "c_smooth",
|
||||
"Equal" => "c_equal",
|
||||
"Concentric" => "c_concentric",
|
||||
"MidPoint" | "PointAlong" => "c_midpoint",
|
||||
"MidPoint" => "c_midpoint",
|
||||
"Symmetry" => "c_symmetry",
|
||||
"Fix" => "c_fix",
|
||||
_ => return None,
|
||||
|
||||
@@ -55,7 +55,8 @@ pub(crate) fn face_text(painter: &egui::Painter, face: &Facet, ink: Color32, fon
|
||||
painter.ctx().add_plugin(LabelAtlas);
|
||||
let text = Shape::Text(egui::epaint::TextShape::new(Pos2::ZERO, galley, ink));
|
||||
let atlas = painter.ctx().fonts(|f| f.font_image_size());
|
||||
let primitives = painter.ctx().tessellate(vec![egui::epaint::ClippedShape::new(Rect::EVERYTHING, text)], painter.ctx().pixels_per_point());
|
||||
let primitives =
|
||||
painter.ctx().tessellate(vec![egui::epaint::ClippedShape { clip_rect: Rect::EVERYTHING, shape: text }], painter.ctx().pixels_per_point());
|
||||
for primitive in primitives {
|
||||
if let egui::epaint::Primitive::Mesh(mut mesh) = primitive.primitive {
|
||||
mesh.texture_id = LABEL_PIXELS;
|
||||
|
||||
@@ -957,6 +957,8 @@
|
||||
"cmd.inspect_accessibility": "Analiza dostupnosti",
|
||||
"cmd.inspect_center_of_mass": "Centar mase",
|
||||
"cmd.inspect_clearance": "Provjera zazora",
|
||||
"cmd.inspect_colored_components": "Omogući boje komponenti",
|
||||
"cmd.inspect_colored_components_disable": "Onemogući boje komponenti",
|
||||
"cmd.inspect_compare": "Uporedi tijela",
|
||||
"cmd.inspect_compare_picture": "Uporedi sliku",
|
||||
"cmd.inspect_continuity": "Analiza kontinuiteta",
|
||||
@@ -1621,7 +1623,7 @@
|
||||
"help.sketch.insert_svg": "Učitava putanje SVG crteža u skicu, skalirane, zakrenute i postavljene gdje zadate. Koristite ga da logotip ili vektorski obris pretvorite u krive koje možete ekstrudirati.",
|
||||
"help.sketch.inspect": "Otvara izvještaj o otvorenoj skici: koliko je stepeni slobode preostalo i je li potpuno ograničena. Koristite ga da saznate šta se još pomjera prije nego što završite skicu.",
|
||||
"help.sketch.intersect": "Dodaje krive ili tačke gdje tijela, lica, ivice ili druge skice sijeku ravan skice. Ostaju povezane i prate model kada se promijeni.",
|
||||
"help.sketch.intersection_curve": "Pravi 3D krive tamo gdje se dva tijela, lica ili krive drugih skica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
|
||||
"help.sketch.intersection_curve": "Pravi 3D krive tamo gdje se dva tijela ili lica sastaju, povezane s njima. Koristite ih kao putanje ili vodilice koje prate model.",
|
||||
"help.sketch.isoparametric_curve": "Pravi 3D krivu u smjeru U ili V lica kroz tačku koju izaberete, povezanu s licem. Koristite je kao putanju ili liniju razdvajanja koja prati površ.",
|
||||
"help.sketch.line": "Crta povezane linije tačku po tačku, kao otvoreni lanac ili zatvoren obris. To je osnovni alat za većinu profila.",
|
||||
"help.sketch.line.midpoint": "Crta liniju od njene središnje tačke do jednog kraja, a drugi kraj se preslikava. Koristite je za linije centrirane na tački, npr. simetričnu ivicu.",
|
||||
|
||||
@@ -957,6 +957,8 @@
|
||||
"cmd.inspect_accessibility": "Tilgængelighedsanalyse",
|
||||
"cmd.inspect_center_of_mass": "Massecentrum",
|
||||
"cmd.inspect_clearance": "Frigangskontrol",
|
||||
"cmd.inspect_colored_components": "Aktivér komponentfarver",
|
||||
"cmd.inspect_colored_components_disable": "Deaktivér komponentfarver",
|
||||
"cmd.inspect_compare": "Sammenlign emner",
|
||||
"cmd.inspect_compare_picture": "Sammenlign billede",
|
||||
"cmd.inspect_continuity": "Kontinuitetsanalyse",
|
||||
@@ -1621,7 +1623,7 @@
|
||||
"help.sketch.insert_svg": "Læser stierne i en SVG-tegning ind i en skitse, skaleret, drejet og placeret, som du angiver. Brug den til at gøre et logo eller en vektorkontur til kurver, du kan ekstrudere.",
|
||||
"help.sketch.inspect": "Åbner en rapport om den åbne skitse: hvor mange frihedsgrader der er tilbage, og om den er fuldt bundet. Brug den til at finde ud af, hvad der stadig kan bevæge sig, før du afslutter skitsen.",
|
||||
"help.sketch.intersect": "Tilføjer de kurver eller punkter, hvor emner, flader, kanter eller andre skitser krydser skitseplanet. De forbliver linkede og følger modellen, når den ændres.",
|
||||
"help.sketch.intersection_curve": "Opretter 3D-kurver, hvor to emner, flader eller kurver fra andre skitser mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
|
||||
"help.sketch.intersection_curve": "Opretter 3D-kurver, hvor to emner eller flader mødes, linket til dem. Brug dem som baner eller styrekurver, der følger modellen.",
|
||||
"help.sketch.isoparametric_curve": "Opretter en 3D-kurve langs en flades U- eller V-retning gennem et punkt, du vælger, linket til fladen. Brug den som bane eller delelinje, der følger overfladen.",
|
||||
"help.sketch.line": "Tegner forbundne linjer punkt for punkt som en åben kæde eller en lukket kontur. Det er det grundlæggende værktøj til de fleste profiler.",
|
||||
"help.sketch.line.midpoint": "Tegner en linje fra dens midtpunkt ud til den ene ende, og den anden ende spejles. Brug den til linjer centreret om et punkt, f.eks. en symmetrisk kant.",
|
||||
@@ -3807,8 +3809,6 @@
|
||||
"sample.plate.name": "Eksempelplade",
|
||||
"sample.vents.about": "Slidser i et rektangulært mønster: Rækker, kolonner og deling er parametre",
|
||||
"sample.vents.name": "Ventilationsplade",
|
||||
"scale.per_axis": "Per akse",
|
||||
"scale.uniform": "Ensartet",
|
||||
"search.command_keys": "Enter kør Shift+Enter kør og hold åben Esc luk",
|
||||
"search.commands": "Kommandoer",
|
||||
"search.design": "I dette design",
|
||||
|
||||
@@ -264,8 +264,8 @@
|
||||
"Flat size": "Abwicklungsgröße",
|
||||
"Flat size X": "Abwicklungsmaß X",
|
||||
"Flat size Y": "Abwicklungsmaß Y",
|
||||
"Follow camera (Look At sketches)": "Kamera folgen (senkrecht auf Skizzen)",
|
||||
"Follow the camera (Look At sketches)": "Der Kamera folgen (senkrecht auf Skizzen)",
|
||||
"Follow camera (Look At sketches)": "Kamera folgen (Skizzen ansehen)",
|
||||
"Follow the camera (Look At sketches)": "Der Kamera folgen (Skizzen ansehen)",
|
||||
"Font": "Schriftart",
|
||||
"Free Move": "Frei verschieben",
|
||||
"From": "Von",
|
||||
@@ -568,7 +568,6 @@
|
||||
"Select edges": "Kanten auswählen",
|
||||
"Select points of the sketch being edited": "Punkte der bearbeiteten Skizze auswählen",
|
||||
"Select the centre the pattern turns around first": "Zuerst den Mittelpunkt auswählen, um den sich das Muster dreht",
|
||||
"Select the curves to divide first": "Zuerst die zu teilenden Kurven auswählen",
|
||||
"Select the curves to offset first": "Zuerst die zu versetzenden Kurven auswählen",
|
||||
"Select the mirror line": "Die Spiegellinie auswählen",
|
||||
"Select the sketch curves or points to mirror first": "Zuerst die zu spiegelnden Skizzenkurven oder -punkte auswählen",
|
||||
@@ -629,8 +628,6 @@
|
||||
"Suppress Selected Folders": "Ausgewählte Ordner unterdrücken",
|
||||
"Suppressed": "Unterdrückt",
|
||||
"Surface": "Flächenkörper",
|
||||
"Surface or face": "Flächenkörper oder Fläche",
|
||||
"Surface or faces": "Flächenkörper oder Flächen",
|
||||
"Surface, or faces to turn over": "Fläche oder umzudrehende Flächen",
|
||||
"Surfaces": "Flächen",
|
||||
"Swap Ends": "Enden vertauschen",
|
||||
@@ -644,7 +641,6 @@
|
||||
"Target": "Ziel",
|
||||
"Target Body": "Zielkörper",
|
||||
"Target Face (optional)": "Zielfläche (optional)",
|
||||
"Target edges": "Zielkanten",
|
||||
"Tear": "Reißen",
|
||||
"Technical details": "Technische Details",
|
||||
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "Die gestrichelte Skizze ist eine gespeicherte Ebenenführung. Wählen Sie eine neue Fläche, um die Anbindung zu reparieren.",
|
||||
@@ -652,7 +648,6 @@
|
||||
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "Die Richtung muss eine Achse oder eine gerade Skizzenkurve in der Skizzenebene sein",
|
||||
"The mirror line cannot also be mirrored": "Die Spiegellinie kann nicht gleichzeitig gespiegelt werden",
|
||||
"The mirror line must be a straight curve of this sketch, not an origin axis": "Die Spiegellinie muss eine gerade Kurve dieser Skizze sein, keine Ursprungsachse",
|
||||
"The origin axis is perpendicular to the sketch plane, so it has no line in the plane": "Die Ursprungsachse steht senkrecht auf der Skizzenebene und hat daher keine Linie in ihr",
|
||||
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Das gespeicherte Autorvorkommen fehlt. Wählen Sie vor dem erneuten Anbinden eine gültige Autorplatzierung.",
|
||||
"Then": "Then",
|
||||
"These features will be deleted:": "Diese Merkmale werden gelöscht:",
|
||||
@@ -694,7 +689,6 @@
|
||||
"Unsuppress Selected Folders": "Unterdrückung der ausgewählten Ordner aufheben",
|
||||
"Updating the preview": "Vorschau wird aktualisiert",
|
||||
"Use active author placement": "Aktive Autorplatzierung verwenden",
|
||||
"Use axis direction only": "Nur Achsenrichtung verwenden",
|
||||
"Use fixed plane": "Feste Ebene verwenden",
|
||||
"Use saved fixed plane": "Gespeicherte feste Ebene verwenden",
|
||||
"Use selected face as fixed plane": "Ausgewählte Fläche als feste Ebene verwenden",
|
||||
@@ -745,9 +739,6 @@
|
||||
"a picked edge's body is gone": "der Körper einer gewählten Kante existiert nicht mehr",
|
||||
"add a key for a joint first": "zuerst einen Schlüssel für ein Gelenk hinzufügen",
|
||||
"ai.connected_toast": "SolidCraft ist jetzt in {clients} verfügbar. Starten Sie diese neu, um es zu nutzen (Einstellungen > Assistent).",
|
||||
"ai.http.allow": "Unsicheres HTTP erlauben",
|
||||
"ai.http.confirm": "Für diesen Endpunkt erlauben",
|
||||
"ai.http.warning": "Der API-Schlüssel und die Unterhaltung werden unverschlüsselt gesendet. Jeder mit Zugriff auf das Netzwerk kann sie lesen oder verändern. Verwenden Sie dies nur in einem vertrauenswürdigen Netzwerk.",
|
||||
"ai.prefs.allow_control": "KI-Agenten verbinden lassen",
|
||||
"ai.prefs.allow_control_note": "Ein lokaler Kanal (MCP). Nichts verlässt diesen Computer.\n\nÖffnet einen Steuerkanal nur auf diesem Computer (127.0.0.1, Port 27878 oder der nächste freie). Ein verbindendes Programm muss zusätzlich ein geheimes Token vorweisen, das nur Ihr Benutzerkonto lesen kann und bei jedem Start neu erzeugt wird. Wirkt ab dem nächsten Start von SolidCraft. Aus: Es wartet nichts auf Verbindungen, außer Sie starten SolidCraft mit --control.",
|
||||
"ai.prefs.availability": "Verfügbarkeit",
|
||||
@@ -842,6 +833,8 @@
|
||||
"cmd.appearance_pick_up": "Aussehen aufnehmen",
|
||||
"cmd.appearance_replace": "Erscheinungsbild ersetzen",
|
||||
"cmd.assembly_bom": "Stückliste",
|
||||
"cmd.assembly_bom_item_set": "Positionsnummer ändern",
|
||||
"cmd.assembly_bom_renumber": "Positionen neu nummerieren",
|
||||
"cmd.assembly_mass_properties": "Masseeigenschaften",
|
||||
"cmd.body.print_face": "Auflagefläche",
|
||||
"cmd.body_rename": "Körper umbenennen",
|
||||
@@ -969,6 +962,8 @@
|
||||
"cmd.inspect_accessibility": "Zugänglichkeitsanalyse",
|
||||
"cmd.inspect_center_of_mass": "Massenschwerpunkt",
|
||||
"cmd.inspect_clearance": "Freiraum",
|
||||
"cmd.inspect_colored_components": "Komponentenfarben einschalten",
|
||||
"cmd.inspect_colored_components_disable": "Komponentenfarben ausschalten",
|
||||
"cmd.inspect_compare": "Körper vergleichen",
|
||||
"cmd.inspect_continuity": "Kontinuitätsprüfung",
|
||||
"cmd.inspect_curvature_comb": "Krümmungskamm-Analyse",
|
||||
@@ -1031,13 +1026,13 @@
|
||||
"cmd.motion_trace_edit": "Bewegungsbahn bearbeiten",
|
||||
"cmd.motion_trace_keep": "Bewegungsbahn behalten",
|
||||
"cmd.occurrence_align": "Instanz ausrichten",
|
||||
"cmd.occurrence_bom_type": "Stücklistentyp",
|
||||
"cmd.occurrence_copy": "Einfügen (Instanz)",
|
||||
"cmd.occurrence_drag": "Instanz ziehen",
|
||||
"cmd.occurrence_ground": "Festsetzen",
|
||||
"cmd.occurrence_move": "Instanz verschieben",
|
||||
"cmd.occurrence_properties": "Physikalische Eigenschaften der Instanz",
|
||||
"cmd.occurrence_reparent": "In andere Komponente verschieben",
|
||||
"cmd.panel.action": "Panelaktion ausführen",
|
||||
"cmd.panel.activate": "Panel-Registerkarte aktivieren",
|
||||
"cmd.panel.close": "Panel schließen",
|
||||
"cmd.panel.collapse": "Panel-Bereich einklappen",
|
||||
@@ -1081,15 +1076,9 @@
|
||||
"cmd.pointcloud_edit": "Punktwolke bearbeiten",
|
||||
"cmd.pointcloud_insert": "Punktwolke einfügen",
|
||||
"cmd.pointcloud_list": "Punktwolken",
|
||||
"cmd.pointcloud_reload": "Punktwolke neu laden",
|
||||
"cmd.render.coat.bind": "Klarlack speichern",
|
||||
"cmd.render.coat.unbind": "Klarlack entfernen",
|
||||
"cmd.render.compare.divider": "Render-Vergleichstrenner verschieben",
|
||||
"cmd.render.result.discard": "Renderergebnis verwerfen",
|
||||
"cmd.render.result.select": "Renderergebnis auswählen",
|
||||
"cmd.render.surface.import": "Oberflächentexturen importieren",
|
||||
"cmd.render.texture.list": "Eingebettete Oberflächenbilder auflisten",
|
||||
"cmd.render.texture.remove": "Eingebettetes Oberflächenbild entfernen",
|
||||
"cmd.render_aging_describe": "Mehrschichtige Oberflächenalterung",
|
||||
"cmd.render_backends": "Render-Backends",
|
||||
"cmd.render_batch": "Alle Aufnahmen rendern",
|
||||
@@ -1232,7 +1221,6 @@
|
||||
"cmd.sketch_options": "Skizzenpalettenoptionen",
|
||||
"cmd.sketch_paste": "Skizzenelemente einfügen",
|
||||
"cmd.sketch_point3d": "3D-Punkt",
|
||||
"cmd.sketch_point_divide": "Kurve teilen",
|
||||
"cmd.sketch_polygon_circumscribed": "Umgeschriebenes Polygon",
|
||||
"cmd.sketch_polygon_edge": "Polygon über eine Kante",
|
||||
"cmd.sketch_polygon_inscribed": "Eingeschriebenes Polygon",
|
||||
@@ -1306,11 +1294,9 @@
|
||||
"cmd.timeline_stop": "Verlauf anhalten",
|
||||
"cmd.timeline_suppress": "Merkmal unterdrücken",
|
||||
"cmd.ui.assistant": "Assistenten ein-/ausblenden",
|
||||
"cmd.view.navigator": "Eigener Ansichtsnavigator",
|
||||
"cmd.view.render_browser": "Render-Browser",
|
||||
"cmd.view.render_dock": "Render-Panel verschieben",
|
||||
"cmd.view.render_focus": "Render-Fokusansicht",
|
||||
"cmd.view.render_live": "Live-Rendering pausieren oder fortsetzen",
|
||||
"cmd.view.status_details": "Statusdetails des Ansichtsfensters",
|
||||
"cmd.view.status_items": "Ansichtsstatus-Elemente",
|
||||
"cmd.view_ambient_occlusion": "Umgebungsverdeckung",
|
||||
@@ -1340,7 +1326,6 @@
|
||||
"cmd.view_shots_strip": "Aufnahmenleiste",
|
||||
"cmd.view_spin": "Ansicht kontinuierlich drehen",
|
||||
"cmd.view_tool": "Navigationswerkzeug",
|
||||
"cmd.view_tool_zoom": "Durch Ziehen zoomen",
|
||||
"cmd.view_top": "Draufsicht",
|
||||
"cmd.view_visual_style": "Darstellungsstil",
|
||||
"color.blue": "Blau",
|
||||
@@ -1407,7 +1392,6 @@
|
||||
"dialog.computing": "Wird berechnet… {seconds} s",
|
||||
"dialog.could_not_be_checked": "nicht prüfbar",
|
||||
"dialog.custom_axis": "↖ Eigene Achse",
|
||||
"dialog.cylinder_face_tip": "Die Fläche oder der Eckpunkt des Modells, auf dem der Zylinder steht: hier klicken, dann in der Ansicht darauf klicken. Leer gelassen platzieren ihn X, Y und Z.",
|
||||
"dialog.dimension_insert_hint": "Doppelklicken Sie auf eine andere steuernde Bemaßung, um ihren Namen einzufügen",
|
||||
"dialog.dimension_name": "Bemaßung: {name}",
|
||||
"dialog.dimension_step_down": "Um {step} verringern",
|
||||
@@ -1416,8 +1400,6 @@
|
||||
"dialog.edit_title": "{title} bearbeiten",
|
||||
"dialog.no_shown_body": "Kein sichtbarer Körper zum Ändern vorhanden. Blenden Sie einen Körper ein oder wählen Sie „Neuer Körper“.",
|
||||
"dialog.pattern_sketch_not_object": "Körper oder Funktionen wählen: Eine Skizze ist keines von beiden. Ihre Kurven mit Muster oder Spiegeln in der Skizze wiederholen oder sie zuerst extrudieren.",
|
||||
"dialog.primitive_point_tip": "Ein Punkt, der X, Y und Z füllt: hier klicken, dann einen Eckpunkt, eine Kante oder Fläche des Modells, einen Punkt einer Skizze oder den Ursprung anklicken.",
|
||||
"dialog.reference_edge_tip": "Eine gerade Kante der gewählten Fläche, von der aus der Abstand darunter gemessen wird: hier klicken, dann die Kante anklicken.",
|
||||
"dialog.select": "↖ Auswählen",
|
||||
"dialog.selected_count": "↖ {count} ausgewählt",
|
||||
"dialog.selection_constraint": "Bedingung {id}",
|
||||
@@ -1460,16 +1442,10 @@
|
||||
"each rail's curves must be in one sketch": "die Kurven jeder Führungskurve müssen in derselben Skizze liegen",
|
||||
"feature computation ended without a result": "die Berechnung des Merkmals wurde ohne Ergebnis beendet",
|
||||
"file.open.drawing_filter": "Designs, CAD-Dateien, Netze und Zeichnungen",
|
||||
"file.open.portal_fallback.no_portal_service": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Der Dateiauswahl-Dienst des Desktops ist nicht verfügbar.",
|
||||
"file.open.portal_fallback.no_session_bus": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Es gibt keinen D-Bus-Sitzungsbus.",
|
||||
"file.open.portal_fallback.xwayland": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Unter XWayland lehnt das Portal das Elternfenster ab.",
|
||||
"file_load.built": "Bisher erstellt: {built} von {total} Merkmalen.",
|
||||
"file_load.converting": "Volumenkörper {done} von {total} wird konvertiert",
|
||||
"file_load.inserted": "{name} eingefügt",
|
||||
"file_load.inserting": "Geometrie wird eingefügt",
|
||||
"file_load.link_item": "{name} ({done} von {total})",
|
||||
"file_load.opened": "{name} geöffnet",
|
||||
"file_load.opened_in_tab": "{name} in neuem Tab geöffnet",
|
||||
"file_load.opening": "Design wird geöffnet",
|
||||
"file_load.preparing_thumbnail": "Vorschaubild wird vorbereitet...",
|
||||
"file_load.preparing_view": "Ansicht wird vorbereitet...",
|
||||
@@ -1478,7 +1454,6 @@
|
||||
"file_load.reading_percent": "Datei wird gelesen... {percent} %",
|
||||
"file_load.rebuilding": "Merkmal {done} von {total} wird neu aufgebaut",
|
||||
"file_load.recovering": "Design wird wiederhergestellt",
|
||||
"file_load.reloading_cloud": "Punktwolke wird neu geladen",
|
||||
"file_load.step_build": "Modell wird aufgebaut",
|
||||
"file_load.step_feature": "Merkmal {done} von {total}",
|
||||
"file_load.step_part": "Teil {done} von {total}",
|
||||
@@ -1706,7 +1681,7 @@
|
||||
"help.sketch.insert_svg": "Liest die Pfade einer SVG-Zeichnung in eine Skizze ein, skaliert, gedreht und an der gewünschten Stelle. Damit machen Sie aus einem Logo oder einer Vektorkontur Kurven, die sich extrudieren lassen.",
|
||||
"help.sketch.inspect": "Öffnet einen Bericht zur offenen Skizze: wie viele Freiheitsgrade übrig sind und ob sie vollständig bestimmt ist. Damit sehen Sie, was sich noch bewegt, bevor Sie die Skizze abschließen.",
|
||||
"help.sketch.intersect": "Fügt die Kurven oder Punkte hinzu, an denen Körper, Flächen, Kanten oder andere Skizzen die Skizzenebene schneiden. Sie bleiben verknüpft und folgen Änderungen am Modell.",
|
||||
"help.sketch.intersection_curve": "Erzeugt 3D-Kurven, wo sich zwei Körper, Flächen oder Kurven anderer Skizzen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
|
||||
"help.sketch.intersection_curve": "Erzeugt 3D-Kurven, wo sich zwei Körper oder Flächen treffen, mit diesen verknüpft. Als Pfade oder Leitkurven, die dem Modell folgen.",
|
||||
"help.sketch.isoparametric_curve": "Erzeugt eine 3D-Kurve in U- oder V-Richtung einer Fläche durch einen gewählten Punkt, mit der Fläche verknüpft. Als Pfad oder Trennlinie, die der Fläche folgt.",
|
||||
"help.sketch.line": "Zeichnet verbundene Linien Punkt für Punkt, als offene Kette oder geschlossene Kontur. Das Grundwerkzeug für die meisten Profile.",
|
||||
"help.sketch.line.midpoint": "Zeichnet eine Linie von ihrem Mittelpunkt zu einem Ende, das andere Ende gespiegelt. Für Linien, die auf einem Punkt mittig liegen, etwa eine symmetrische Kante.",
|
||||
@@ -1718,7 +1693,6 @@
|
||||
"help.sketch.pattern.circular": "Kopiert Skizzengeometrie um einen Mittelpunkt, über einen vollen Kreis oder einen festen Winkel. Für Lochkreise, Speichen und Zähne.",
|
||||
"help.sketch.pattern.rectangular": "Kopiert Skizzengeometrie in Reihen und Spalten mit festen Abständen. Für Raster aus Bohrungen, Schlitzen oder Lüftungsöffnungen.",
|
||||
"help.sketch.point": "Setzt einen Skizzenpunkt. Als Bezug für Maße, Bedingungen, Bohrungsmitten oder spätere Elemente.",
|
||||
"help.sketch.point.divide": "Setzt Punkte, die die gewählten Kurven in gleich lange Abschnitte teilen. Die Punkte bleiben auf jeder Kurve und behalten ihren Abstand, wenn sie sich ändert.",
|
||||
"help.sketch.point3d": "Setzt bei eingeschalteter 3D-Skizze einen Punkt im Raum. Als Bezug für 3D-Kurven und Pfade.",
|
||||
"help.sketch.polygon.circumscribed": "Zeichnet ein regelmäßiges Polygon um einen Kreis, bemessen vom Mittelpunkt zur Mitte einer Seite. Wenn die Schlüsselweite bekannt ist, etwa bei einer Sechskantmutter.",
|
||||
"help.sketch.polygon.edge": "Zeichnet ein regelmäßiges Polygon aus den beiden Enden einer Seite. Wenn Länge und Lage einer Seite bekannt sind.",
|
||||
@@ -2046,8 +2020,6 @@
|
||||
"nav.blender.help": "Mittlere Maustaste dreht, Umschalt+Mitte verschiebt, Strg+Mitte zoomt; rechtes Ziehen lässt die Kamera unverändert",
|
||||
"nav.creo.help": "Ziehen mit der mittleren Maustaste dreht, Umschalt+Mitte verschiebt, Strg+Mitte zoomt beim vertikalen Ziehen (nach unten zum Vergrößern); das Mausrad zoomt",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.drag_keeps_mode.help": "Ein Ziehen mit der mittleren oder rechten Taste dreht, verschiebt oder zoomt weiter wie begonnen, bis die Taste losgelassen wird, egal welche Tasten dabei gedrückt oder losgelassen werden. Aus: Die gehaltenen Tasten entscheiden in jedem Moment des Ziehens.",
|
||||
"nav.drag_keeps_mode.label": "Ziehen behält die Navigation bei, mit der es begann",
|
||||
"nav.free_orbit.help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollneigung bei. Das Einrasten mit Umschalt ist deaktiviert.",
|
||||
"nav.free_orbit.label": "Freie Drehung",
|
||||
"nav.free_orbit.snap_help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollausrichtung bei. Halten Sie beim Ziehen zum Drehen die Umschalttaste gedrückt, um Blickrichtung und Rollausrichtung einrasten zu lassen.",
|
||||
@@ -2147,7 +2119,7 @@
|
||||
"navbar.ground_shadow": "Bodenschatten",
|
||||
"navbar.home": "Start",
|
||||
"navbar.later": "Später",
|
||||
"navbar.look_at": "Senkrecht auf",
|
||||
"navbar.look_at": "Ansehen",
|
||||
"navbar.more": "Mehr",
|
||||
"navbar.orbit": "Drehen",
|
||||
"navbar.orthographic": "Orthografisch",
|
||||
@@ -2167,7 +2139,6 @@
|
||||
"navbar.value_tip": "{name} · {value}",
|
||||
"navbar.visual_style": "Darstellung",
|
||||
"navbar.zoom": "Zoomen",
|
||||
"navbar.zoom_hold_tip": "Zoomen (Tastenkürzel gedrückt halten)",
|
||||
"needs two joints": "benötigt zwei Gelenke",
|
||||
"network.allow_once": "Einmalig erlauben",
|
||||
"network.always_allow": "Diesen Server immer erlauben",
|
||||
@@ -2306,8 +2277,6 @@
|
||||
"prefs.browser_eye_left": "Auge neben dem Aufklapppfeil",
|
||||
"prefs.browser_eye_left_note": "Browserzeilen zeigen ihr Auge zum Ein- und Ausblenden wie bisher neben dem Aufklapppfeil statt in einer Spalte am rechten Zeilenende.",
|
||||
"prefs.browser_reveal_on_hover": "Beim Überfahren einblenden",
|
||||
"prefs.browser_rows_start_closed": "Browser-Zeilen beginnen geschlossen",
|
||||
"prefs.browser_rows_start_closed_note": "Platzierungen und Ordner im Browser beginnen geschlossen, sodass ein Entwurf mit vielen Komponenten seine obersten Zeilen zeigt. Jede öffnet sich mit ihrem Pfeil, „Alle ausklappen“ oder „Im Browser suchen“, das nur die Zeilen bis zu seinem Körper öffnet und die von der letzten Suche geöffneten wieder schließt. Ein in der 3D-Ansicht gewählter Körper lässt den Browser, wie er ist. Ist es aus, beginnen die Ordner geöffnet, Platzierungen so, wie „Komponenten zu Beginn aufgeklappt“ es festlegt, und eine Auswahl in der 3D-Ansicht öffnet den Browser bis zu ihrem Körper, wie bisher.",
|
||||
"prefs.browser_show_note": "Angezeigt lässt den Browser auf dem Bildschirm. Beim Überfahren einblenden blendet ihn aus, bis der Zeiger über seinen Platz oder die schmale Lasche an seinem linken Rand kommt; er bleibt offen, solange der Zeiger darin ist oder ein Menü daraus offen ist.",
|
||||
"prefs.browser_shown": "Angezeigt",
|
||||
"prefs.built_in": "Integriert",
|
||||
@@ -2318,7 +2287,6 @@
|
||||
"prefs.carousel_previous": "Zurück",
|
||||
"prefs.choose_font": "Schriftdatei auswählen…",
|
||||
"prefs.click": "Klicken",
|
||||
"prefs.command_dialog_values": "Befehlsdialoge",
|
||||
"prefs.contact_shading": "Weiche Kontaktschattierung",
|
||||
"prefs.contact_shading_note": "Erzeugt weiche Schatten an Stellen, an denen sich Bauteile nahekommen.",
|
||||
"prefs.context_menu": "Kontextmenü",
|
||||
@@ -2362,11 +2330,10 @@
|
||||
"prefs.dimension_values_view": "In der Ansicht",
|
||||
"prefs.dimension_values_window": "In einem Fenster",
|
||||
"prefs.display": "Anzeige",
|
||||
"prefs.drawings": "Zeichnungen",
|
||||
"prefs.edges": "Kanten und Schattierung",
|
||||
"prefs.extend": "Erweitern",
|
||||
"prefs.extra_navigation": "Mit Strg und gedrückter linker Maustaste auf einer freien Fläche drehen. Leertaste gedrückt halten, um die Ansicht zu verschieben.",
|
||||
"prefs.extrude_arrow_face_stops": "Extrusionspfeil hält an Flächen",
|
||||
"prefs.extrude_arrow_face_stops_note": "Beim Ziehen des Abstandspfeils von Extrusion hält er an ebenen Flächen parallel zum Profil, etwa an der Rückseite eines Blocks, in den geschnitten wird, und hebt die Fläche hervor. Aus: Der Pfeil bewegt sich nur in runden Schritten.",
|
||||
"prefs.extrude_view_isometric": "Beim Öffnen von Extrudieren isometrisch ausrichten",
|
||||
"prefs.extrude_view_return": "Beim Schließen von Extrudieren zur vorigen Ansicht zurück",
|
||||
"prefs.follows_os": "Wie im Betriebssystem",
|
||||
@@ -2416,15 +2383,9 @@
|
||||
"prefs.graphics_page.surfaces_edges": "Flächen und Kanten",
|
||||
"prefs.graphics_page.view_bar": "Auch in der Ansichtsleiste",
|
||||
"prefs.graphics_presets_note": "Mit den Profilen werden die unten aufgeführten Optionen gemeinsam eingestellt.",
|
||||
"prefs.ground_shadow_follows_model": "Bodenschatten folgt dem Modell",
|
||||
"prefs.ground_shadow_follows_model_help": "Den Schatten unter dem niedrigsten sichtbaren Körper platzieren. Wenn deaktiviert, bleibt der Schatten angehobener Modelle auf der Bodenebene durch den Ursprung.",
|
||||
"prefs.hide_constraint_glyphs_zoomed_out": "Bindungssymbole bei starkem Herauszoomen ausblenden",
|
||||
"prefs.hide_constraint_glyphs_zoomed_out_note": "Bindungssymbole verschwinden, wenn die Skizze so weit herausgezoomt ist, dass sie sie überladen würden. Aus: sie behalten bei jedem Zoom eine feste Bildschirmgröße.",
|
||||
"prefs.history": "Verlauf",
|
||||
"prefs.history_bar_groups": "Verlaufsleiste in Gruppen falten",
|
||||
"prefs.history_bar_groups_note": "In einem langen Verlauf wird eine Folge von Features einer Komponente zu einem Chip gefaltet, und Merkmalsordner beginnen gefaltet. Eine eingefügte STEP-Baugruppe kommt in einen Group-Ordner. Aus zeigt jedes Feature, öffnet Ordner und legt keinen Ordner an.",
|
||||
"prefs.history_group_joints": "Gelenke im Verlauf gruppieren",
|
||||
"prefs.history_group_joints_note": "Zwei oder mehr Gelenke erscheinen als eine Schaltfläche mit ihrer Anzahl; ein Klick zeigt jedes einzeln. Aus zeigt jedes Gelenk einzeln.",
|
||||
"prefs.history_origin_bodies": "Ursprung und Körper oben zeigen",
|
||||
"prefs.history_origin_bodies_note": "Der Verlauf links oder unter dem Browser beginnt mit dem Körper-Ordner der aktiven Komponente und dem Ursprung, mit Augen und Menüs wie im Browser. Der Verlauf unten bleibt unverändert.",
|
||||
"prefs.history_play_button": "Schaltfläche „Verlauf abspielen“ in den Verlaufssteuerelementen",
|
||||
@@ -2457,8 +2418,6 @@
|
||||
"prefs.layout_page.card": "Karte",
|
||||
"prefs.layout_page.column": "Spalte",
|
||||
"prefs.layout_page.hover": "Bei Mauszeiger",
|
||||
"prefs.layout_page.hover_panel_menus": "Bereichsmenüs beim Darüberfahren wechseln",
|
||||
"prefs.layout_page.hover_panel_menus_help": "Klicken Sie auf einen Bereichsnamen, um das Menü zu öffnen. Bewegen Sie den Zeiger auf einen anderen Bereichsnamen, um das Menü zu wechseln. Außerhalb des Menübands und des geöffneten Menüs schließt es sich.",
|
||||
"prefs.layout_page.icons_labels": "Symbole + Beschriftungen",
|
||||
"prefs.layout_page.ok_cancel": "OK und Abbrechen",
|
||||
"prefs.layout_page.on_hover": "Bei Mauszeiger",
|
||||
@@ -2497,8 +2456,6 @@
|
||||
"prefs.mouse": "Maus und Trackpad",
|
||||
"prefs.mouse_heading": "Maus",
|
||||
"prefs.mouse_mapping": "Mausbelegung",
|
||||
"prefs.move_drag_highlights": "Hervorheben, was unter dem Zeiger liegt, während die Pfeile von Verschieben eine Komponente ziehen",
|
||||
"prefs.move_drag_highlights_tip": "Aus: Solange die Pfeile von Verschieben eine Komponente ziehen, wird nichts unter dem Zeiger hervorgehoben; so bleiben große Baugruppen flüssig. Mit einem Schnitt oder einer Analyse wird es trotzdem hervorgehoben, und ein entlang seiner Gelenke gezogenes Teil hebt nie etwas hervor.",
|
||||
"prefs.navigation": "Navigation",
|
||||
"prefs.navigation.axes_signed": "Achsen",
|
||||
"prefs.navigation.cube_compact": "Würfel",
|
||||
@@ -2522,7 +2479,6 @@
|
||||
"prefs.origin_plane_names": "Namen der Ursprungsebenen",
|
||||
"prefs.origin_plane_names_note": "Namen in Browser, Dialogen und Listen. {top} ist XY, {front} ist XZ und {right} ist YZ, mit Z nach oben. Befehle, Skripte und gespeicherte Konstruktionen behalten XY, XZ und YZ.",
|
||||
"prefs.overall_speed": "Gesamtgeschwindigkeit",
|
||||
"prefs.panel_layout.recovery": "Gespeicherte Panel-Layouts konnten nicht wiederhergestellt werden. Die gespeicherten Daten bleiben erhalten; Klassisch bleibt verfügbar.",
|
||||
"prefs.parameter_list_rows": "Zeilen der Parameterliste",
|
||||
"prefs.parameter_list_rows_tip": "Maximale Anzahl der Parameter, die Pfeil abwärts in einem leeren Ausdrucksfeld auflistet.",
|
||||
"prefs.persistent_ribbon_pressed": "Aktive Menübandbefehle hervorheben",
|
||||
@@ -2564,10 +2520,6 @@
|
||||
"prefs.quality_smoother": "Stärker geglättet",
|
||||
"prefs.quick_measure": "Schnellmessung in der Ecke der Ansicht",
|
||||
"prefs.quick_measure_note": "Zeigt unten rechts in der Ansicht die Größe der Auswahl an: bei einer Kante die Länge, bei einer kreisförmigen Kante zusätzlich Durchmesser (Ø) und Radius (R), bei einer Fläche den Flächeninhalt, bei einem Punkt die Position, bei zwei Elementen Abstand und Winkel sowie bei mehreren Kanten oder Flächen die Gesamtlänge bzw. Gesamtfläche. Ein Klick kopiert den Messwert.",
|
||||
"prefs.remember_dialog_values": "Befehlsdialogwerte merken",
|
||||
"prefs.remember_dialog_values.help": "Standardmäßig aus. Wenn aktiviert, werden nach erfolgreicher Ausführung eines Befehls seine sicheren Werte und Optionen beim nächsten Öffnen desselben Befehls wiederverwendet, bis SolidCraft geschlossen wird.",
|
||||
"prefs.render_legacy_image_defaults": "Bisherige Render-Bildvorgaben verwenden",
|
||||
"prefs.render_legacy_image_defaults_help": "Den bisherigen manuellen Ablauf beibehalten: neue Setups mit CPU-Raster, feste Abtastung beim CPU-Raytracing und manuell geöffnete Ergebnisse. Beim Wechsel zu Render die Ansicht neu einpassen, Aufnahmen ohne Wiederherstellung ihrer Kamera wechseln und die bisherige Bildausrichtung und Navigation verwenden. Automatische Live-Bildrückmeldungen deaktivieren. Gespeicherte Setups bleiben unverändert.",
|
||||
"prefs.render_previous_geometry_budget": "Bisheriges Geometriebudget für die Render-Vorschau verwenden",
|
||||
"prefs.render_previous_geometry_budget_help": "Stellt die bisherige feste Obergrenze für die Geometrie wieder her (512 MiB auf 64-Bit-Desktop-Systemen, andernfalls 128 MiB) anstelle des geräteabhängigen Budgets.",
|
||||
"prefs.reports_saved": "{n} gespeichert",
|
||||
@@ -2583,8 +2535,6 @@
|
||||
"prefs.search": "Einstellungen suchen",
|
||||
"prefs.search_empty": "Keine passenden Einstellungen",
|
||||
"prefs.select": "Auswählen",
|
||||
"prefs.select_hidden_edges": "Verdeckte Kanten in der schattierten Ansicht mit verdeckten Kanten auswählen",
|
||||
"prefs.select_hidden_edges_help": "Kanten durch Flächen auswählen, wenn die schattierte Ansicht mit verdeckten Kanten aktiv ist, auch zum Verrunden. Aus behält das Auswahlverhalten von v0.30.1 bei. Ausgeblendete Körper bleiben ausgeschlossen.",
|
||||
"prefs.select_one_item": "Ein Element auswählen",
|
||||
"prefs.select_other_hidden": "Verborgene Geometrie mit „Andere auswählen“ auswählen",
|
||||
"prefs.select_other_hidden_help": "Ein: Verborgene Körper, Skizzen und verdeckte Kanten nahe dem Zeiger einbeziehen. Aus: Die ursprüngliche Liste sichtbarer Geometrie direkt unter dem Zeiger verwenden.",
|
||||
@@ -2609,8 +2559,6 @@
|
||||
"prefs.sketch.click_selects": "Klick wählt",
|
||||
"prefs.sketch.conflict_colour": "Konfliktfarbe",
|
||||
"prefs.sketch.drawing": "Zeichnen",
|
||||
"prefs.sketch.ellipse_axes": "Ellipsen mit ihren Achsen zeichnen",
|
||||
"prefs.sketch.ellipse_axes_note": "Haupt- und Nebenachse als Konstruktionslinien, damit Halbachsen und Achsen bemaßt werden können. Aus: nur die Ellipse, und D darauf ergibt ihren Nebenradius, wohin der Wert auch gezogen wird, gezeichnet und mit R beschriftet wie bisher.",
|
||||
"prefs.sketch.exit_keep": "Beibehalten",
|
||||
"prefs.sketch.exit_previous": "Zur Ansicht davor zurückkehren",
|
||||
"prefs.sketch.exit_unless_rotated": "Außer bei Drehung",
|
||||
@@ -2644,9 +2592,8 @@
|
||||
"prefs.sketch_exit_view_note": "Legt fest, zu welcher Ansicht nach „Skizze beenden“, „Abbrechen“ oder zweimaligem Drücken der Esc-Taste gewechselt wird.",
|
||||
"prefs.sketch_face_activates_part": "Skizzieren auf der Fläche eines Teils aktiviert dieses Teil",
|
||||
"prefs.sketch_face_activates_part_note": "Eine Skizze, die auf einer Fläche am Körper einer anderen Komponente begonnen wird, aktiviert zuerst diese Komponente, sodass die Skizze zu ihr gehört und im Browser bei diesem Teil bleibt. Das Teil bleibt danach aktiv. Aus: Die Skizze gehört zur aktiven Komponente.",
|
||||
"prefs.sketch_mirror_auto_line": "Vorausgewählte Konstruktionslinie zum Spiegeln verwenden",
|
||||
"prefs.sketch_open_look_at": "Beim Öffnen einer Skizze auf sie ausrichten",
|
||||
"prefs.sketch_open_look_at_note": "Aus: Beim Erstellen oder Bearbeiten einer Skizze bleibt die Ansicht, wie sie ist. „Senkrecht auf“ richtet die Ansicht bei Bedarf auf die Skizze aus.",
|
||||
"prefs.sketch_open_look_at_note": "Aus: Beim Erstellen oder Bearbeiten einer Skizze bleibt die Ansicht, wie sie ist. „Ansehen“ richtet die Ansicht bei Bedarf auf die Skizze aus.",
|
||||
"prefs.sketch_recent_snap": "Automatisch an zuletzt verwendete Mittelpunkte fangen",
|
||||
"prefs.sketch_recent_snap_note": "Fängt den Zeiger beim Zeichnen dort ein, wo sich die Hilfslinien der beiden zuletzt verwendeten Kreismittelpunkte oder Kantenmitten kreuzen. Aus: die Hilfslinien bleiben sichtbar und der Zeiger bewegt sich nicht.",
|
||||
"prefs.sketch_selection": "Skizzen und Auswahl",
|
||||
@@ -2696,8 +2643,6 @@
|
||||
"prefs.structure.work": "Arbeit",
|
||||
"prefs.supersampling_brief": "Rendert die Ansicht größer und verkleinert sie dann: sehr scharf, aber rechenintensiver.",
|
||||
"prefs.tab_accent_note": "Die Akzentfarbe richtet sich nach dem aktiven Tab.",
|
||||
"prefs.tab_double_click_renames": "Entwurfsregisterkarte per Doppelklick umbenennen",
|
||||
"prefs.tab_double_click_renames_note": "Geben Sie den neuen Namen ein und drücken Sie Enter oder klicken Sie woanders hin, um ihn zu übernehmen, oder Esc zum Abbrechen. Aus: Ein Doppelklick auf eine Registerkarte maximiert das Fenster, wie auf dem leeren Teil der Leiste.",
|
||||
"prefs.theme_file": "Designdatei",
|
||||
"prefs.theme_file_or_paste": "Datei oder Einfügen",
|
||||
"prefs.theme_follow": "Einer Designdatei folgen",
|
||||
@@ -2794,15 +2739,12 @@
|
||||
"refused.join": "Konnte nicht mit {body} vereinigt werden.",
|
||||
"refused.repair": "Teil reparieren",
|
||||
"refused.show_face": "Fläche zeigen",
|
||||
"render.adaptive.pilot_progress": "Adaptive Pilotphase · {current}/{target} Durchgänge",
|
||||
"render.adaptive.preview_help": "Pilotbilder bestimmen die Verteilung und sind vom Endbild ausgeschlossen. Produktionsiterationen nutzen ein eigenes Budget pro Pixel; die Iterationszahl ist keine einheitliche Samplezahl pro Pixel.",
|
||||
"render.adaptive.production_progress": "Adaptive Produktion · {current}/{target} Iterationen",
|
||||
"render.adaptive_absolute": "Absolute RGB-Fehleruntergrenze",
|
||||
"render.adaptive_coverage": "Zielwert für den Abdeckungsfehler",
|
||||
"render.adaptive_enable": "Samples mithilfe unabhängiger Pilot-Samples zuteilen",
|
||||
"render.adaptive_help": "Unabhängige Pilotsamples werden verworfen und bestimmen dann mindestens 16 Produktionssamples pro Pixel bis zum Wert Samples pro Pixel. CPU-Ressourcengrenzen umfassen beide Phasen. RGB- und Abdeckungsvarianz steuern die Verteilung; Pixel am Limit können verrauscht bleiben. Der Abschlussbericht zählt die tatsächlichen Samples.",
|
||||
"render.adaptive_help": "Nur GPU. Unabhängige Pilot-Samples werden verworfen. Anschließend werden pro Pixel 16 oder mehr Produktions-Samples zugeteilt, bis zu den Samples pro Pixel. Die Zuteilung richtet sich nach der RGB- und Abdeckungsvarianz; seltene Pfade können übersehen werden, und Pixel, die ihr Limit erreicht haben, können verrauscht bleiben. Der Abschlussbericht zählt den tatsächlichen Aufwand. Keine zertifizierte Garantie für ein bestimmtes Rauschniveau oder eine bestimmte Renderzeit.",
|
||||
"render.adaptive_minimum": "Minimale Produktions-Samples",
|
||||
"render.adaptive_pending": "Adaptive Verteilung mit unabhängigen Pilotsamples ist für CPU und GPU verfügbar. Bedingte Fresnel-Schichten erfordern die GPU. Die statistische Verteilung bestätigt keine Konvergenz.",
|
||||
"render.adaptive_pending": "Die adaptive Zuteilung mit unabhängigen GPU-Pilot-Samples und bedingte Fresnel-Schichten sind verfügbar. Zertifizierte Konvergenz, Garantien für seltene Ereignisse und adaptiver Lichttransport auf der CPU bleiben nicht verfügbar.",
|
||||
"render.adaptive_pilots": "Pilot-Samples pro Pixel",
|
||||
"render.adaptive_relative": "Zielwert für den relativen RGB-Fehler",
|
||||
"render.add_ground": "Add fitted ground to setup",
|
||||
@@ -2825,7 +2767,6 @@
|
||||
"render.aging_metallic": "Metallanteil der Schicht",
|
||||
"render.aging_origin": "Feldursprung · Weltkoordinaten (mm)",
|
||||
"render.aging_patina": "Kupfergrüne Patina",
|
||||
"render.aging_raytrace_help": "Alterung wird beim CPU-Raytracing und GPU-Pathtracing angewendet. Raster bleibt eine schematische Darstellung.",
|
||||
"render.aging_refresh_targets": "Aktuelle Körper prüfen",
|
||||
"render.aging_roughness": "Rauheit der Schicht",
|
||||
"render.aging_rust": "Oxidiertes Eisen / Rost",
|
||||
@@ -2846,7 +2787,6 @@
|
||||
"render.appearance_editor.closed": "Geschlossener Körper",
|
||||
"render.appearance_editor.coat": "Klarlack",
|
||||
"render.appearance_editor.cpu_reason": "Benötigt CPU-Strahlverfolgung.",
|
||||
"render.appearance_editor.discard_saved_edit": "Ungespeicherte Optikänderungen verwerfen",
|
||||
"render.appearance_editor.glass": "Glas und Absorption",
|
||||
"render.appearance_editor.glass_enable": "Physikalisches Glas für diesen Körper verwenden",
|
||||
"render.appearance_editor.glass_note": "Geschlossenes Glas verwendet geometrische Abstände. Es kann nicht mit Klarlack oder Texturen auf demselben Körper kombiniert werden.",
|
||||
@@ -2865,7 +2805,7 @@
|
||||
"render.appearance_editor.replace": "Gespeicherte Optik dieses Körpers ersetzen",
|
||||
"render.appearance_editor.revalidate": "Diesen Körper erneut prüfen",
|
||||
"render.appearance_editor.save": "Diese Optik speichern",
|
||||
"render.appearance_editor.save_reason": "Zum Speichern Glas an einem aktuellen CPU- oder GPU-Körper aktivieren. Für Klarlack „Klarlack in Aufnahme speichern“ verwenden. Lokale Bilddateien bleiben temporär.",
|
||||
"render.appearance_editor.save_reason": "Zum Speichern wird physikalisches Glas auf einem aktuellen GPU-Körper benötigt. Klarlack und lokale Bildtexturen bleiben vorübergehend.",
|
||||
"render.appearance_editor.saved": "Mit dieser Aufnahme gespeichert",
|
||||
"render.appearance_editor.saved_state": "Diese Optik ist mit dieser Aufnahme gespeichert.",
|
||||
"render.appearance_editor.scatter_reason": "Innere Streuung benötigt einen geschlossenen Körper.",
|
||||
@@ -2911,13 +2851,9 @@
|
||||
"render.base_metallic": "Metallanteil",
|
||||
"render.base_opaque_only": "Dieser Editor lässt Körper undurchsichtig. Für optische Transmission verwenden Sie die gesonderten Glaseinstellungen.",
|
||||
"render.batch_all_shots": "Alle Ansichten ({count})",
|
||||
"render.batch_current_settings_help": "Verwendet die aktuellen Bildeinstellungen mit jeder gespeicherten Aufnahmekamera, eine Aufnahme nach der anderen.",
|
||||
"render.batch_empty": "Vor dem Rendern aller Ansichten eine Ansicht speichern.",
|
||||
"render.batch_limit": "Es können höchstens 32 Ansichten gerendert werden.",
|
||||
"render.batch_owner_limit": "Es können höchstens 32 Dokument-Bildfolgen vorgehalten werden. Schließen Sie zuerst eine vorhandene Folge ab oder brechen Sie sie ab.",
|
||||
"render.batch_preparing": "Nächste Aufnahme wird vorbereitet",
|
||||
"render.batch_status": "{running} läuft · {waiting} warten",
|
||||
"render.batch_waiting_worker": "Warten auf die Rendering-Vorbereitung",
|
||||
"render.bloom_enable": "Darstellungs-Bloom",
|
||||
"render.bloom_help": "Kreativer Ausgabeglanz aus linearen Glanzlichtern mit drei normierten Durchläufen. Radius in Ausgabepixeln; Addition vor sRGB. Keine Linsen-, Volumen- oder Konvergenzsimulation. Das rohe EXR bleibt unverändert.",
|
||||
"render.bloom_opaque": "Bloom benötigt eine deckende Ausgabe. Transparentes PNG wird abgelehnt, nicht still abgeflacht.",
|
||||
@@ -2947,16 +2883,6 @@
|
||||
"render.category_paint": "Lack",
|
||||
"render.category_plastic": "Kunststoff",
|
||||
"render.category_wood": "Holz",
|
||||
"render.caustics_depth": "Kaustik-Pfadtiefe",
|
||||
"render.caustics_enable": "Glaskaustiken durch Punkt- und Richtungslichter",
|
||||
"render.caustics_help": "Die Photonenzahl bestimmt Rauschen und Aufwand. Der Sammelradius tauscht scharfe Details gegen eine glattere Schätzung. CPU benötigt indirekte Beleuchtung.",
|
||||
"render.caustics_path_required": "CPU-Kaustiken benötigen indirekte Beleuchtung.",
|
||||
"render.caustics_photons": "Photonen",
|
||||
"render.caustics_radius": "Sammelradius",
|
||||
"render.check_glass_cpu": "Die CPU unterstützt geprüfte ebene Glasscheiben und achsenparallele Boxen. Allgemeine geschlossene Netze, Verschachtelung und innere Streuung erfordern indirekte Beleuchtung. Durch geschlossenes Glas übertragene Schatten bleiben nicht unterstützt.",
|
||||
"render.check_glass_gpu": "Die GPU unterstützt geprüfte getrennte Glasmedien oder ausdrücklich aktivierte geprüfte Verschachtelung. Dafür müssen die äußeren Körper konvex und die inneren streng enthalten sein; Grenzen dürfen sich nicht berühren.",
|
||||
"render.check_media_remaining": "Dichtegitter, Displacement, vollständige spektrale Integration und fortgeführte Polarisation bleiben nicht verfügbar.",
|
||||
"render.check_media_scope": "Aktive innere Streuung benötigt ein geschlossenes Glasmedium ohne gerade Schattenübertragung und ohne Rauschreduzierung. Die GPU benötigt außerdem eine Fresnel-Schicht. Aktive äußere Atmosphäre kann nicht mit Glas oder Oberflächen-Rauschreduzierung kombiniert werden.",
|
||||
"render.check_opaque": "Transparenz: nur undurchsichtige Oberflächen; Hintergrund-Alpha wird unterstützt.",
|
||||
"render.check_sorted_alpha": "Transparenz: Näherung mit sortiertem Alpha.",
|
||||
"render.check_transmission": "Transparenz: physikalische Transmission.",
|
||||
@@ -2975,7 +2901,7 @@
|
||||
"render.children.copy_request": "Anfrage-JSON kopieren",
|
||||
"render.children.coverage_error": "Abdeckungsfehler",
|
||||
"render.children.cpu": "CPU-Raytracing",
|
||||
"render.children.denoise": "Bildrauschen reduzieren",
|
||||
"render.children.denoise": "Rauschen im fertigen Bild reduzieren",
|
||||
"render.children.depth": "Pfadtiefe",
|
||||
"render.children.distortion": "Verzeichnung",
|
||||
"render.children.edge_samples": "Kanten-Samples",
|
||||
@@ -3038,14 +2964,6 @@
|
||||
"render.choose_look": "Zuerst einen Look in der Bibliothek wählen",
|
||||
"render.chromatic_offset": "Chromatischer Kantenversatz",
|
||||
"render.clear_stage": "Remove saved stage",
|
||||
"render.coat_discard": "Klarlackänderung verwerfen",
|
||||
"render.coat_pending_edit": "Diese Klarlackänderung ist ungespeichert. Mit der Aufnahme speichern oder verwerfen.",
|
||||
"render.coat_remove": "Gespeicherten Klarlack entfernen",
|
||||
"render.coat_remove_note": "Entfernt den Klarlack dieses Ziels aus der Aufnahme. Geometrie, Aussehen und physikalische Materialien bleiben unverändert.",
|
||||
"render.coat_remove_title": "Gespeicherten Klarlack entfernen?",
|
||||
"render.coat_revalidate_saved": "Der gespeicherte Klarlack wurde geändert. Diese Änderung verwerfen und das Ziel vor der nächsten Bearbeitung erneut öffnen.",
|
||||
"render.coat_save": "Klarlack in Aufnahme speichern",
|
||||
"render.coat_saved_help": "Klarlack fügt undurchsichtigen Flächen eine glänzende Schicht hinzu. Mit der Aufnahme speichern. Glas kann nicht dasselbe Ziel verwenden. Auf der CPU dürfen auch benutzerdefinierte Reflexions- und Anisotropieüberschreibungen dieses Ziel nicht verwenden.",
|
||||
"render.compare_divider": "Vergleichstrenner",
|
||||
"render.compare_live": "Live",
|
||||
"render.compare_live_preview": "Live-Vorschau",
|
||||
@@ -3079,18 +2997,10 @@
|
||||
"render.cpu_budget_triangles": "Dreiecksobergrenze",
|
||||
"render.cpu_budget_workstation": "Workstation (komplexe Szenen)",
|
||||
"render.cpu_captured_hdr": "Für dieses Bild erfasstes HDR",
|
||||
"render.cpu_celestial_adopt": "CPU-Raytracing mit indirekter Beleuchtung · Pfadtiefe {depth} · {spp} SPP · verfolgte Schatten · Seed {seed} · 1 Primärsample",
|
||||
"render.cpu_celestial_adopt_label": "CPU-Raytracing mit indirekter Beleuchtung",
|
||||
"render.cpu_celestial_path_required": "Endliche Sonne und Verlaufshimmel verwenden indirekte CPU-Beleuchtung.",
|
||||
"render.cpu_celestial_switch": "Wähle CPU-Raytracing mit indirekter Beleuchtung, um diese Sonne oder diesen Himmel beizubehalten.",
|
||||
"render.cpu_celestial_unportable": "Behalte GPU-Pathtracing für die aktuellen Transmissions-Sample-Einstellungen bei.",
|
||||
"render.cpu_check_capture_help": "Prüfen und Starten verifizieren dieselbe Datei-Prüfsumme oder verwenden das eingebettete HDR. Jedes Bild behält seine erfasste Quelle.",
|
||||
"render.cpu_check_changed": "Die Einstellungen oder das Modell haben sich nach dieser Prüfung geändert. Prüfen Sie erneut, bevor Sie rendern.",
|
||||
"render.cpu_constant_direct_help": "Ohne indirekte Beleuchtung kann diese konstante Quelle in Reflexionen und durch Glas sichtbar sein. Sie beleuchtet keine undurchsichtigen Flächen. Schalte indirekte Beleuchtung ein, um sie als Szenenlicht zu nutzen.",
|
||||
"render.cpu_constant_enable_indirect_plan": "Pfadtiefe {depth}; progressives Sampling mit {spp} Samples/Pixel (Seed {seed}); ein Primärstrahl pro Sample; Raytracing-Schatten. Bildgröße, CPU-Budget und andere Einstellungen bleiben erhalten.",
|
||||
"render.cpu_constant_help": "Konstante Strahldichte ist für reflektierte oder durchgelassene Strahlen sichtbar. Sie liefert keine direkte diffuse Beleuchtung.",
|
||||
"render.cpu_custom_directions": "Vorgabe durch eigene Richtungslichter ersetzen",
|
||||
"render.cpu_denoise_help": "Verringert das Rauschen bei jeder Bildaktualisierung. Feine Details und Übergänge der Tiefenschärfe können weicher werden. Die EXR-Rohdaten bleiben unverändert. Nachträgliche Anpassungen eines fertigen Bildes verwenden dessen Rohbild und wenden die Rauschreduzierung nicht erneut an.",
|
||||
"render.cpu_direction": "Richtung zum Licht (X, Y, Z)",
|
||||
"render.cpu_directional": "Richtungslicht",
|
||||
"render.cpu_directional_add": "Richtungslicht hinzufügen",
|
||||
@@ -3102,11 +3012,8 @@
|
||||
"render.cpu_environment_samples": "HDR-Beleuchtungs-Samples",
|
||||
"render.cpu_exposure_help": "Die CPU-Belichtung muss zwischen −16 und 16 EV liegen. Eine GPU-Belichtung außerhalb dieses Bereichs bleibt erhalten, bis Sie sie ändern.",
|
||||
"render.cpu_fixed": "Einzelbild",
|
||||
"render.cpu_fixed_retained": "Feste Abtastung beibehalten",
|
||||
"render.cpu_glass_enable_path": "Indirekte Beleuchtung einschalten",
|
||||
"render.cpu_hdr_help": "Radiance-HDR-Datei (.hdr), Panorama im Format 2:1, bis zu 2048 × 1024 Pixel und 16 MiB. Für die CPU müssen die sRGB/D65-Primärfarben ausdrücklich in der Datei angegeben sein. Dateien ohne Angabe der Primärfarben sowie EXR-Dateien werden abgelehnt.",
|
||||
"render.cpu_hdr_reflection_help": "Raue HDR-Reflexionen erfordern vollständig metallische Körper- und Flächenoberflächen. Die Prüfung meldet nicht unterstützte Kombinationen.",
|
||||
"render.cpu_indirect_method_required": "Wähle CPU-Raytracing, um diesen Modus für indirekte Beleuchtung einzuschalten.",
|
||||
"render.cpu_key_rim": "Haupt- und Konturlicht",
|
||||
"render.cpu_last_check": "Letzte CPU-Prüfung",
|
||||
"render.cpu_light_help": "Bis zu acht Richtungslichter. Eine leere eigene Liste schaltet sie aus; die Wahl einer Vorgabe stellt deren Lichter wieder her. Farben sind lineares RGB, und die Intensität ist ein relativer Wert, keine Leistungsangabe in Watt.",
|
||||
@@ -3118,32 +3025,18 @@
|
||||
"render.cpu_lighting": "Beleuchtungsvorgabe",
|
||||
"render.cpu_lights": "CPU-Lichter",
|
||||
"render.cpu_linear_color": "Lineare Farbe (R, G, B)",
|
||||
"render.cpu_maps_saved_help": "CPU-Renderings können gespeicherte Karten undurchsichtiger Flächen verwenden: Oberfläche wählen und die Karten in der Aufnahme speichern. Mit indirekter Beleuchtung darf ein anderer Körper Glas verwenden; der Körper mit Karten darf kein Glas sein. Ausgewählte Reflexionen, Anisotropie und prozedurale Effekte ausschalten. Temporäre Kartendateipfade verwenden die GPU.",
|
||||
"render.cpu_material_certification": "Die CPU-Prüfung validiert unterstützte Glasgeometrie vor dem Rendern. Geschlossenes Glas verwendet geometrische Abstände.",
|
||||
"render.cpu_neutral": "Neutral",
|
||||
"render.cpu_opaque_hint": "CPU-Raytracing verwendet einen undurchsichtigen Hintergrund.",
|
||||
"render.cpu_opaque_required": "CPU-Raytracing benötigt einen undurchsichtigen Hintergrund. Entfernen Sie das Häkchen bei „Transparenter Hintergrund“, um fortzufahren.",
|
||||
"render.cpu_options_pending": "Dieses CPU-Bedienfeld kann Emission, Oberflächeneffekte, Glas oder Anisotropie noch nicht bearbeiten. Entfernen Sie diese API-Optionen, bevor Sie es verwenden.",
|
||||
"render.cpu_path_depth": "Pfadtiefe",
|
||||
"render.cpu_path_help": "Berechnet Licht, das zwischen undurchsichtigen Flächen und durch unterstützte Glasformen läuft. Erfordert progressives Rendern und Raytracing-Schatten. Die Pfadtiefe zählt beleuchtete Flächen, Glasgrenzen und Streuereignisse ab dem ersten Treffer (1–16). Tiefe 1 liefert direkte Beleuchtung auf undurchsichtigen Flächen; höhere Werte erlauben mehr Reflexionen oder Glasdurchgänge und können länger dauern. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
|
||||
"render.cpu_path_lighting": "Indirekte Beleuchtung",
|
||||
"render.cpu_path_optics_reason": "Die Einstellungen bleiben erhalten. Für eigene Reflexionen oder Anisotropie die indirekte Beleuchtung ausschalten.",
|
||||
"render.cpu_point": "Punktlicht",
|
||||
"render.cpu_point_add": "Punktlicht hinzufügen",
|
||||
"render.cpu_point_help": "Bis zu acht Punktlichter, zusätzlich zur gewählten Richtungsbeleuchtung. Abstände in mm; der Mindestabstand darf den Referenzabstand nicht überschreiten.",
|
||||
"render.cpu_primary_samples": "Kanten-Samples pro Pixel",
|
||||
"render.cpu_progressive": "CPU progressiv",
|
||||
"render.cpu_progressive_adopt": "Progressive Abtastung verwenden",
|
||||
"render.cpu_progressive_adopt_help": "Die progressive Abtastung verwendet ein Kamerasample pro Durchlauf. Bildgröße, Lichtquellen und Reflexionseinstellungen bleiben unverändert.",
|
||||
"render.cpu_progressive_always": "Progressive Abtastung ist immer aktiv",
|
||||
"render.cpu_progressive_help": "Neue CPU-Kamerasamples sammeln und jeden vollständigen Durchgang zeigen. Ausgewählte Reflexionen verwenden einen Versuch pro Kamerasample; zertifiziertes Glas und planare Anisotropie behalten ihre bisherigen Grenzen. Adaptive Pilot- und Produktionssamples teilen sich das CPU-Profilbudget.",
|
||||
"render.cpu_progressive_help": "Den vorhandenen CPU-Integrator in Durchgängen mit je einem Primär-Sample akkumulieren. Ausgewählte Reflexionen erfordern ein Sample; Flächenlichter, Dielektrika und Anisotropie erfordern „Einzelbild“.",
|
||||
"render.cpu_progressive_limits": "Höchstens 1.048.576 Pixel und 16.777.216 Auswertungen von Pixel × Sample. Das Ändern der Samples passt die Bildgröße nie automatisch an.",
|
||||
"render.cpu_progressive_reflection_adopt": "Setzen Sie die Reflexionsversuche ausgewählter Körper auf 1, bevor Sie die progressive Abtastung übernehmen.",
|
||||
"render.cpu_progressive_seed": "Startwert für das CPU-Sampling",
|
||||
"render.cpu_progressive_spp": "Samples pro Pixel",
|
||||
"render.cpu_quality_budget_help": "Ändert Größe, Obergrenze der Produktionssamples, Startwert und CPU-Ressourcenprofil. Kamera, Objektiv, Beleuchtung, GI-Tiefe und Effekte bleiben erhalten.",
|
||||
"render.cpu_quality_progressive_required": "Aktiviere unten progressive Samples, bevor du ein CPU-Budget auswählst.",
|
||||
"render.cpu_quality_summary": "{width} × {height} · Sample-Grenze {samples} · Startwert {seed} · {profile}",
|
||||
"render.cpu_reflection_add": "Raue Reflexion hinzufügen",
|
||||
"render.cpu_reflection_depth": "Reflexionstiefe (1–8)",
|
||||
"render.cpu_reflection_empty": "Keine Körper ausgewählt: Szenenreflexionen sind aus.",
|
||||
@@ -3157,21 +3050,7 @@
|
||||
"render.cpu_runtime_help": "Numerische Licht- und Umgebungseinstellungen werden mit dem Setup gespeichert. Körperreflexionen und HDR-Dateien bleiben vorübergehend. Vorhandene Werte für Flächenlicht und Spotkegel bleiben erhalten.",
|
||||
"render.cpu_sampling_mode": "CPU-Sampling-Modus",
|
||||
"render.cpu_shadows": "Schatten werfen",
|
||||
"render.cpu_sky_bounds": "Die Himmelsrotation muss endlich innerhalb ±1.000.000 Radiant liegen; die Intensität muss 0–100 sein.",
|
||||
"render.cpu_studio_enable": "Indirekte Beleuchtung für dieses Lichtset aktivieren",
|
||||
"render.cpu_studio_enable_apply": "Indirekte Beleuchtung aktivieren und anwenden",
|
||||
"render.cpu_studio_indirect_help": "Dieses Sonnen-Lichtset benötigt indirekte Beleuchtung mit Tiefe 8, verfolgte Schatten und eine Kamerastichprobe pro Durchlauf. Ist progressives Rendern ausgeschaltet, beginnt es mit 16 Stichproben pro Pixel und Startwert 0. Vorhandene progressive Einstellungen und das Ressourcenprofil bleiben erhalten. Unvereinbare Einstellungen verhindern die Anwendung, ohne sie zu ändern.",
|
||||
"render.cpu_studio_radiance_limit": "Dieses Lichtset hat eine Flächenstrahldichte von {radiance}, über der CPU-Grenze von 64. Die vorgegebene Beleuchtung bleibt erhalten; wählen Sie ein anderes Lichtset oder GPU.",
|
||||
"render.cpu_subsurface_denoise_reason": "Streuung im Glas auf der CPU benötigt ausgeschaltete Rauschreduzierung. Die Einstellungen bleiben erhalten.",
|
||||
"render.cpu_subsurface_help": "Streuung lenkt Licht im Inneren geschlossenen Glases um. Positive Raten benötigen indirekte Beleuchtung ohne Rauschreduzierung. Die Rate zählt Kollisionen pro mm; 0 fügt keine Streuung hinzu. Positive Richtungswerte bevorzugen vorwärts, negative rückwärts.",
|
||||
"render.cpu_summary": "CPU-Raytracing · Reflexionen · HDR · Richtungs- und Punktlichter",
|
||||
"render.cpu_transparent_help": "Transparentes PNG entfernt den Hintergrund und erhält die Kantenabdeckung. Körper- und Flächendeckkraft müssen 100 % bleiben.",
|
||||
"render.cpu_volume_enable_path": "Indirekte Beleuchtung einschalten",
|
||||
"render.cpu_volume_enable_path_plan": "CPU-Raytracing · Pfadtiefe {depth} · {spp} Samples/Pixel · Seed {seed} · Raytracing-Schatten. Atmosphäre und Bildbudget bleiben erhalten.",
|
||||
"render.cpu_volume_help": "CPU-Atmosphäre nutzt indirekte Beleuchtung. Bei Extinktion null bleibt die gespeicherte Box ohne Nebel erhalten.",
|
||||
"render.cpu_volume_method_adopt": "CPU-Raytracing mit indirekter Beleuchtung",
|
||||
"render.cpu_volume_method_incompatible": "Diese Licht-, Sampling- oder Atmosphäreinstellungen lassen sich nicht auf diese Methode übertragen. Die Einstellungen bleiben erhalten.",
|
||||
"render.cpu_volume_path_required": "Indirekte Beleuchtung einschalten, um CPU-Atmosphäre zu nutzen.",
|
||||
"render.cpu_warm_cool": "Warm und kühl",
|
||||
"render.diag_bodies": "Sichtbare Körper",
|
||||
"render.diag_job_details": "Erfasste Auftragsdetails",
|
||||
@@ -3181,7 +3060,6 @@
|
||||
"render.diag_preflight_details": "Details der Vorabprüfung",
|
||||
"render.diag_request": "Aktuelle Anfrage im Wortlaut",
|
||||
"render.diag_triangles": "Dreiecke",
|
||||
"render.directional_sun_limit": "Die GPU unterstützt insgesamt acht gerichtete Lichtquellen, darunter höchstens eine weiche Sonne. Dupliziere ein hartes gerichtetes Licht für eine weitere entfernte Quelle.",
|
||||
"render.discard_continue": "Entwurf verwerfen und fortfahren",
|
||||
"render.draft_confirm_help": "Der aktuelle Render-Entwurf enthält nicht angewendete Änderungen. Speichern Sie sie vor dem Wechsel, verwerfen Sie sie ausdrücklich oder bearbeiten Sie weiter.",
|
||||
"render.draft_confirm_title": "Bearbeitete Rendereinstellungen beibehalten",
|
||||
@@ -3331,7 +3209,7 @@
|
||||
"render.export_display": "Export: Anzeige-PNG",
|
||||
"render.export_image_label": "Bild exportieren",
|
||||
"render.export_linear": "Export: lineares Roh-EXR",
|
||||
"render.exr_pending": "CPU-Raytracing und GPU-Pathtracing können rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
|
||||
"render.exr_pending": "Die GPU kann rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
|
||||
"render.film_grain": "Fotografisches Korn",
|
||||
"render.fit": "Produkt einpassen",
|
||||
"render.fixed_camera": "Kamera",
|
||||
@@ -3369,23 +3247,11 @@
|
||||
"render.frame_tall": "9:16 · Vertikal",
|
||||
"render.frame_thirds": "Drittellinien",
|
||||
"render.frame_wide": "16:9 · Breitbild",
|
||||
"render.glass_abbe": "Abbe-Zahl",
|
||||
"render.glass_dispersion": "Chromatische Dispersion",
|
||||
"render.glass_film": "Dünnschichtinterferenz",
|
||||
"render.glass_film_ior": "Brechungsindex der Schicht",
|
||||
"render.glass_film_thickness": "Schichtdicke",
|
||||
"render.glass_model_enable": "Erweitertes Glasmodell",
|
||||
"render.glass_model_help": "Glasrauheit streut durchgelassene Strahlen. Dispersion und Schichtfarbe verwenden drei Wellenlängenbänder. Raue Scheiben benötigen ausgeschaltete gerade Schattenübertragung.",
|
||||
"render.glass_roughness": "Glasrauheit",
|
||||
"render.gpu_depth": "Maximale Pfadtiefe",
|
||||
"render.gpu_job_shader_wait": "Shader-Vorbereitung kann Minuten dauern",
|
||||
"render.gpu_options_help": "Samples, Tiefe, Startwert und Belichtung werden mit diesem Setup gespeichert. Es wird nur die aktive Bildmethode gespeichert.",
|
||||
"render.gpu_path_crashed": "SolidCraft wurde beim letzten Einrichten auf diesem Grafikadapter beendet, wahrscheinlich im Grafiktreiber, und richtet ihn deshalb nicht von selbst erneut ein. Verwenden Sie stattdessen Raytracing auf der CPU oder versuchen Sie es erneut (nach einem Treiberupdate wird es ebenfalls erneut versucht).",
|
||||
"render.gpu_path_failed": "GPU-Pathtracer nicht verfügbar",
|
||||
"render.gpu_path_first_setup": "Das erste Setup auf dieser GPU kann mehrere Minuten dauern; SolidCraft bleibt währenddessen bedienbar. Spätere Starts verwenden die kompilierten Shader erneut, sofern der Treiber sie behält.",
|
||||
"render.gpu_path_not_started": "Die Einrichtung des GPU-Pfadtracers wurde noch nicht gestartet.",
|
||||
"render.gpu_path_preparing": "GPU-Pathtracer: Shader werden kompiliert…",
|
||||
"render.gpu_path_retry": "Erneut versuchen",
|
||||
"render.gpu_seed": "Zufallsstartwert",
|
||||
"render.gpu_unavailable": "GPU-Pathtracing ist auf diesem Gerät nicht bereit.",
|
||||
"render.ground_apply": "Boden anpassen und speichern",
|
||||
@@ -3579,8 +3445,6 @@
|
||||
"render.light_product_centre": "Produktmitte",
|
||||
"render.light_size": "Größe",
|
||||
"render.light_softness": "Weichheit",
|
||||
"render.light_space_world": "Lichter in Weltkoordinaten",
|
||||
"render.light_space_world_help": "Lichtpositionen und -richtungen verwenden Weltkoordinaten. Beim Umkreisen mit der Kamera bleiben die Lichter an ihrer Position.",
|
||||
"render.light_temperature": "Farbtemperatur",
|
||||
"render.light_temperature_help": "Angenäherte Schwarzkörperfarbe; als lineares RGB gespeichert.",
|
||||
"render.light_type": "Typ",
|
||||
@@ -3637,25 +3501,6 @@
|
||||
"render.linear_help": "Behält ein lineares 32-Bit-Gleitkomma-RGBA-Rohbild vor Entrauschen, Belichtung, Tone-Mapping, Filtern und Hintergrund. Lineares sRGB/D65; Abdeckung vormultipliziert. Zusätzlicher Speicher wird ausdrücklich zugelassen. Keine Komprimierung und keine zusätzlichen Durchgänge.",
|
||||
"render.linear_retain": "Lineares Roh-EXR behalten",
|
||||
"render.linear_unavailable": "Für das gewählte fertige Bild wurde kein EXR behalten. Aktivieren Sie das Behalten vor dem Rendern; ein älteres PNG kann die rohe Strahldichte nicht wiederherstellen.",
|
||||
"render.live.adaptive": "Live · {width} × {height} · {progress}",
|
||||
"render.live.final_changed": "Die Render-Anfrage hat sich beim Anhalten des Live-Renderings geändert. Starten Sie das finale Bild erneut.",
|
||||
"render.live.final_waiting": "Finales Bild vorbereiten · Live-Render-Ressourcen freigeben",
|
||||
"render.live.invalid_size": "Live-Rendering benötigt eine positive Ausgabebreite und -höhe.",
|
||||
"render.live.pause": "Live pausieren",
|
||||
"render.live.paused": "Live · Pausiert",
|
||||
"render.live.paused_outdated": "Live · Pausiert · Veraltet",
|
||||
"render.live.quality_help": "Interaktive Qualität: höchstens 512 Pixel an der längsten Kante. Verwendet den gewählten Renderer und seine unterstützten Effekte. Endauflösung, Einstellungen und Export bleiben getrennt.",
|
||||
"render.live.refused": "Live nicht verfügbar: {reason}",
|
||||
"render.live.resume": "Live fortsetzen",
|
||||
"render.live.samples": "Live · {width} × {height} · {current}/{target} Samples",
|
||||
"render.live.source_current": "{method} · Live",
|
||||
"render.live.source_outdated": "{method} · Vorheriges Bild",
|
||||
"render.live.source_outdated_help": "Zeigt das zuletzt aufgenommene Bild, während das Live-Rendering aktualisiert wird oder nicht verfügbar ist. Es kann von der aktuellen Szene oder den Bildeinstellungen abweichen.",
|
||||
"render.live.source_settings_help": "Ausgabe öffnen, um Renderer und Bildeinstellungen zu bearbeiten.",
|
||||
"render.live.stopped": "Live-Rendering angehalten",
|
||||
"render.live.updating": "Live · Interaktive Qualität (bis 512 px) · Aktualisierung",
|
||||
"render.live.waiting_canvas": "Warten auf die Render-Ansicht",
|
||||
"render.live.worker_stopped": "Der Aufnahmeprozess für das Live-Rendering wurde vor der Rückgabe eines Ergebnisses beendet",
|
||||
"render.look_add": "Farblook hinzufügen",
|
||||
"render.look_applied": "{name} angewendet",
|
||||
"render.look_bleach": "Von Bleach-Bypass inspiriert",
|
||||
@@ -3685,9 +3530,6 @@
|
||||
"render.material_coat_help": "Illustrative dielektrische Beschichtung über dem vorhandenen undurchsichtigen Erscheinungsbild. Gewicht null lässt das Grundmaterial unverändert.",
|
||||
"render.material_color_map": "Grundfarbentextur (sRGB)",
|
||||
"render.material_color_replace": "Basismap ersetzt Körperfarbe",
|
||||
"render.material_cpu_glass_only": "Temporäre Bilddatei-Karten benötigen die GPU. Die CPU kann gespeicherte integrierte Oberflächenkarten verwenden.",
|
||||
"render.material_cpu_optics_limits": "Indirekte Beleuchtung für unterstützte geschlossene Glasformen, geprüfte Verschachtelung und Streuung im Inneren einschalten. Dünne Scheiben können Licht für Schatten durchlassen. Sonst eine ebene Scheibe oder achsenparallele Box verwenden. Berührende Formen und durch geschlossenes Glas übertragene Schatten werden weiterhin nicht unterstützt. Die Werte bleiben erhalten.",
|
||||
"render.material_cpu_saved_runtime_conflict": "Gespeicherte CPU-Optik kann nicht mit temporären optischen Bindungen kombiniert werden. Geprüfte Optik speichern oder die temporäre Bindung entfernen.",
|
||||
"render.material_custom": "Eigene illustrative Optik",
|
||||
"render.material_enable": "Diese GPU-Materialüberschreibungen verwenden",
|
||||
"render.material_filter": "Szenenkörper filtern…",
|
||||
@@ -3708,7 +3550,6 @@
|
||||
"render.material_normal_map": "Normalenbild (OpenGL, +Y)",
|
||||
"render.material_normal_strength": "Normalenstärke (0–1)",
|
||||
"render.material_origin": "Texturursprung X · Y · Z (mm)",
|
||||
"render.material_raytrace_required": "Physikalisches Glas benötigt CPU-Raytracing oder GPU-Pathtracing.",
|
||||
"render.material_refresh": "Szenenkörper aktualisieren",
|
||||
"render.material_refresh_needed": "Die Szene hat sich geändert. Aktualisieren Sie die Körperliste, bevor Sie ein Ziel hinzufügen.",
|
||||
"render.material_remove": "Diese Zuweisung entfernen",
|
||||
@@ -3718,7 +3559,6 @@
|
||||
"render.material_revalidate_needed": "Diese Zuweisung gehört zu einer früheren Szenenrevision. Prüfen Sie die genaue Körperidentität erneut, bevor Sie rendern.",
|
||||
"render.material_roughness": "Rauheit des Klarlacks (0,045–1)",
|
||||
"render.material_roughness_map": "Rauheitsmap (linear)",
|
||||
"render.material_saved_edit_pending": "Diese Optikänderungen sind ungespeichert. Geprüfte Werte speichern, die gespeicherte Zuordnung entfernen oder Änderungen verwerfen. Temporäre Bindungen und nicht unterstützte Effekte können das gespeicherte Ziel nicht überschreiben.",
|
||||
"render.material_shadow": "Gerade Schattenstrahlen durchlassen",
|
||||
"render.material_target_missing": "Ordnungsnummer und Name des Körpers stimmen nicht mehr überein. Entfernen Sie diese Zuweisung und wählen Sie den gewünschten Körper ausdrücklich erneut.",
|
||||
"render.material_thickness": "Dicke der dünnen Schicht (mm)",
|
||||
@@ -3730,7 +3570,7 @@
|
||||
"render.method_cpu": "CPU-Raytracing",
|
||||
"render.method_gpu": "GPU-Pathtracing",
|
||||
"render.method_limit_raster": "Die Rasterung verwendet die feste undurchsichtige Studiovorschau. Wählen Sie ausdrücklich eine Raytracing-Methode für die unterstützten optischen Einstellungen.",
|
||||
"render.method_raster": "CPU-Rasterung (schematisch)",
|
||||
"render.method_raster": "CPU-Rasterung",
|
||||
"render.navigation_help": "Die Kamera bewegt sich wie im Konstruieren (Einstellungen › Navigation). Setup speichern übernimmt die aktuelle Kamera.",
|
||||
"render.nested_enable": "Zertifizierte verschachtelte Dielektrika",
|
||||
"render.nested_help": "Bis zu 16 umschließende Schichten; jedes übergeordnete Element muss konvex und jedes untergeordnete strikt darin enthalten sein. Überschneidungen, sich berührende Flächen, dünne Schichten und die Transmission gerader Schattenstrahlen werden abgelehnt. Der Zustand „Kamera im Inneren“ folgt der zertifizierten Hierarchie. Erzeugt keine Flüssigkeitsoberfläche und verschmilzt keine sich berührenden CAD-Grenzflächen.",
|
||||
@@ -3758,19 +3598,16 @@
|
||||
"render.output_exr_unavailable": "EXR-Ausgabeeinstellungen sind hier nicht verfügbar. Linearer EXR-Export erfordert ein fertiges GPU-Bild mit gespeichertem linearem Original.",
|
||||
"render.output_fit": "Einpassen",
|
||||
"render.output_format": "Format",
|
||||
"render.output_format_status": "PNG enthält das exakte fertige Anzeigebild. Optionales CPU/GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Anzeigefiltern. Der Export verwendet nie den Vorschaucache oder einen anderen Auftrag.",
|
||||
"render.output_format_status": "PNG enthält das exakte fertige Anzeigebild. Optionales GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Filtern. Export ersetzt nie durch Vorschaucache oder anderen Auftrag.",
|
||||
"render.output_grading": "Farbkorrektur",
|
||||
"render.output_image": "Bild",
|
||||
"render.output_largest": "{width} × {height} max.",
|
||||
"render.output_lock_ratio": "Seitenverhältnis sperren",
|
||||
"render.output_master_unavailable": "Zum EXR-Export ein fertiges Bild mit EXR-Rohdaten auswählen. EXR-Rohdaten vor dem Rendern neuer Bilder aktivieren.",
|
||||
"render.output_over_budget": "Das angeforderte Bild überschreitet den gewählten Ressourcenrahmen. Wählen Sie ausdrücklich eine passende Größe oder ein passendes Profil; es erfolgt keine automatische Herabstufung.",
|
||||
"render.output_presentation_limits": "CPU-Bildeffekte und EXR-Ausgabe unterstützen Bilder mit bis zu 8192 Pixeln Breite oder Höhe und insgesamt höchstens 16.777.216 Pixeln.",
|
||||
"render.output_presets_help": "Format, Qualität und Prüfliste befinden sich im Ausgabeeditor.",
|
||||
"render.output_quality": "Qualität",
|
||||
"render.output_quality_waiting": "Qualitätsdefinition steht noch aus",
|
||||
"render.output_ratio_refused": "Die verknüpfte Abmessung überschreitet das Größenlimit dieser Methode. Die vorherige Größe bleibt erhalten; entsperren Sie das Verhältnis oder wählen Sie einen kleineren Wert.",
|
||||
"render.output_raytrace_only": "Bildeffekte erfordern CPU-Raytracing oder GPU-Pathtracing.",
|
||||
"render.output_ready": "Bereit zum Rendern",
|
||||
"render.output_run_check": "Prüfung starten",
|
||||
"render.output_size_limit": "{method}: bis zu {dimension} px pro Seite und {pixels} Pixel. Größte passende Größe: {width} × {height}.",
|
||||
@@ -3825,12 +3662,6 @@
|
||||
"render.post_saturation": "Sättigung",
|
||||
"render.post_title": "Darstellungsfilter",
|
||||
"render.post_vignette": "Vignettierung",
|
||||
"render.preparation_cancelled": "Rendering-Vorbereitung abgebrochen",
|
||||
"render.preparation_checking": "Rendering wird geprüft",
|
||||
"render.preparation_retirement_failed": "Die Bereinigung der Rendering-Vorbereitung ist fehlgeschlagen. Dieses Fenster kann kein weiteres Bild vorbereiten.",
|
||||
"render.preparation_retiring": "Rendering-Vorbereitung wird abgeschlossen",
|
||||
"render.preparation_unavailable": "Rendering-Vorbereitung nicht verfügbar",
|
||||
"render.presentation_banner": "Bildeffekte funktionieren mit CPU- und GPU-Raytracing. Das Öffnen dieser Seite ändert die Methode nicht.",
|
||||
"render.preview": "Integrierte Vorschau",
|
||||
"render.preview_help": "Rasterdarstellung mit gemischter Transparenz. Brechung, HDRI und finales Rendering sind noch nicht verfügbar.",
|
||||
"render.preview_image_settings_help": "Die Live-Vorschau behält eine feste Studiobeleuchtung. Belichtung und Bildbeleuchtung gelten für den Bildauftrag, wenn Sie „Prüfen“ oder „Start“ verwenden.",
|
||||
@@ -3849,15 +3680,11 @@
|
||||
"render.quality_ack": "Die Projektion mit festem Budget und ihre Auslassungen bestätigen",
|
||||
"render.quality_apply": "Dieses feste Sampling-Budget verwenden",
|
||||
"render.quality_balanced": "Ausgewogen",
|
||||
"render.quality_budget_help": "Legt Größe, Samples, Tiefe, Startwert und Rauschreduzierung fest. Andere Bildeinstellungen bleiben erhalten.",
|
||||
"render.quality_custom": "Benutzerdefiniert",
|
||||
"render.quality_denoise_off": "Rauschreduzierung: Aus",
|
||||
"render.quality_denoise_on": "Rauschreduzierung: Ein",
|
||||
"render.quality_draft": "Entwurf",
|
||||
"render.quality_high": "Hoch",
|
||||
"render.quality_projection_help": "Diese Prüfung ändert nur Auflösung, Abtastbudget, Pfadtiefe, Startwert und Rauschreduzierung. Andere Einstellungen bleiben erhalten; das Budget garantiert keine Konvergenz.",
|
||||
"render.quality_projection_help": "Q-Vorgaben enthalten künftige Anforderungen. Diese Aktion projiziert nur ein unterstütztes PNG-Budget mit festen Samples und lässt adaptives Stoppen, rohe lineare Ausgabe und unabhängige Transmissionslimits ausdrücklich aus.",
|
||||
"render.quality_review": "Sampling-Budget prüfen",
|
||||
"render.quality_summary": "{width} × {height} · {samples} Samples · Tiefe {depth} · Startwert {seed} · {denoise}",
|
||||
"render.queue_pending": "In dieser Oberfläche gibt es keine automatische Produktionswarteschlange und keinen Animations-Renderer. Fertige Einzelbilder bleiben separat exportierbar.",
|
||||
"render.radial_assign": "Erscheinungsbild zuweisen",
|
||||
"render.radial_compare": "Vergleichen",
|
||||
@@ -3887,7 +3714,7 @@
|
||||
"render.result_navigation": "Verschieben und Zoomen folgen den Navigationseinstellungen. Leertaste + Ziehen verschiebt.",
|
||||
"render.result_open": "Das fertige Bild in der Hauptansicht öffnen.",
|
||||
"render.result_preview": "Live-Vorschau",
|
||||
"render.result_provisional": "Vorläufiges Bild",
|
||||
"render.result_provisional": "Vorläufiges CPU-Bild",
|
||||
"render.result_provisional_help": "Dieser Zwischenstand ist nicht exportierbar. Der Export behält das letzte fertige Ergebnis; ein Abbruch kehrt zu diesem Ergebnis zurück.",
|
||||
"render.result_reduced_display": "Anzeigevorschau auf die Geräte-/Cachegrenze reduziert. PNG und Export behalten die volle Auflösung; native 100%-Ansicht ist hier nicht verfügbar.",
|
||||
"render.result_retained": "Das zuletzt fertiggestellte Bild wird angezeigt; ein neueres Rendering hat es noch nicht ersetzt.",
|
||||
@@ -3976,11 +3803,9 @@
|
||||
"render.shot_frame_subject": "Motiv einpassen",
|
||||
"render.shot_invalid_camera": "Kamerawerte müssen endlich und im gültigen Bereich sein.",
|
||||
"render.shot_lens": "Objektiv",
|
||||
"render.shot_lens_cpu_progressive_reason": "Vor der CPU-Schärfentiefe unter Sampling die progressive Abtastung übernehmen.",
|
||||
"render.shot_lens_enable_reason": "Schärfentiefe aktivieren, um Blende und Fokusabstand zu bearbeiten.",
|
||||
"render.shot_lens_gpu_reason": "Schärfentiefe benötigt GPU-Pfadverfolgung.",
|
||||
"render.shot_lens_perspective_reason": "Schärfentiefe benötigt perspektivische Projektion.",
|
||||
"render.shot_lens_raytrace_reason": "Schärfentiefe ist für CPU-Raytracing und GPU-Pathtracing verfügbar.",
|
||||
"render.shot_look_selection": "Auswahl anvisieren",
|
||||
"render.shot_manager": "Gespeicherte Aufnahmen",
|
||||
"render.shot_new_name": "Neuer Aufnahmename",
|
||||
@@ -4014,12 +3839,7 @@
|
||||
"render.sky_horizon": "Horizont · lineares RGB",
|
||||
"render.sky_zenith": "Zenit · lineares RGB",
|
||||
"render.soft_focus": "Weichzeichnung",
|
||||
"render.spot_axis": "Abstrahlrichtung",
|
||||
"render.spot_cone": "Spotkegel",
|
||||
"render.spot_gpu_help": "Die Winkel werden von der Abstrahlrichtung aus gemessen. Zwischen innerem und äußerem Winkel wird das Licht schwächer. Sehr schmale Übergänge erfordern CPU-Rendering.",
|
||||
"render.spot_help": "Die Winkel werden von der Abstrahlrichtung aus gemessen. Zwischen innerem und äußerem Winkel wird das Licht schwächer.",
|
||||
"render.spot_inner": "Innerer Halbwinkel",
|
||||
"render.spot_outer": "Äußerer Halbwinkel",
|
||||
"render.stage_frame": "Bildausschnitt",
|
||||
"render.stage_layout.appearance_note": "Zum Anwenden auf die Auswahl klicken.",
|
||||
"render.stage_layout.studio_note": "Vor dem Anwenden zur Prüfung klicken.",
|
||||
@@ -4045,7 +3865,6 @@
|
||||
"render.stage_panels.extent": "Ausdehnung",
|
||||
"render.stage_panels.fit_save": "Einpassen und speichern",
|
||||
"render.stage_panels.gpu_required": "Benötigt GPU-Pfadverfolgung",
|
||||
"render.stage_panels.grain_angle": "Maserungswinkel",
|
||||
"render.stage_panels.keep": "Behalten",
|
||||
"render.stage_panels.kept": "Beibehalten",
|
||||
"render.stage_panels.kept_fields": "Kamera, Abtastung, Abmessungen, optische Bindungen, CAD-Geometrie und technische Eigenschaften.",
|
||||
@@ -4059,7 +3878,7 @@
|
||||
"render.stage_panels.prepare_plan": "Erstellungsplan vorbereiten",
|
||||
"render.stage_panels.projects": "Projiziert",
|
||||
"render.stage_panels.quality_ack": "Ich bestätige das feste Budget und seine Auslassungen",
|
||||
"render.stage_panels.quality_omissions": "Dieses Budget legt keine adaptiven Fehlerziele, eigenen Transmissionsgrenzen, Russian-Roulette-Regeln oder Speicherung des linearen Masters fest und garantiert keine Konvergenz.",
|
||||
"render.stage_panels.quality_omissions": "Adaptives Stoppen und Fehlerziele, separate Transmissionstiefe, Russian-Roulette-Regeln, lineare Rohausgabe, EXR-Master und Konvergenznachweis.",
|
||||
"render.stage_panels.reference": "Referenz",
|
||||
"render.stage_panels.references": "Referenzen",
|
||||
"render.stage_panels.remove_optics": "Optik entfernen",
|
||||
@@ -4103,7 +3922,7 @@
|
||||
"render.stage_panels.subtitle_environment": "Originale HDR-Umgebung · gesamte Szene",
|
||||
"render.stage_panels.subtitle_maps": "Erstellte räumliche Texturen · temporäre Vorschau",
|
||||
"render.stage_panels.subtitle_physical": "Schreibgeschützter Plan · kein Material erstellt oder zugewiesen",
|
||||
"render.stage_panels.subtitle_quality": "GPU-Abtastbudget · nur aufgeführte Einstellungen",
|
||||
"render.stage_panels.subtitle_quality": "Festes GPU-Budget · zukünftige Anforderungen ausgelassen",
|
||||
"render.stage_panels.subtitle_rig": "Erstellter Lichtaufbau · Lichtquellen und passive Flächen",
|
||||
"render.stage_panels.subtitle_saved_maps": "Erstellte Texturen · mit dieser Aufnahme gespeichert",
|
||||
"render.stage_panels.subtitle_stage": "Physischer Boden und Bühne · an das Produkt angepasst",
|
||||
@@ -4128,7 +3947,7 @@
|
||||
"render.studio_area_help": "Bis zu vier einseitige Rechtecke. Orthogonale Halbkanten U und V bestimmen die Größe; U × V zeigt zur beleuchteten Seite. Die Koordinaten sind Weltmillimeter.",
|
||||
"render.studio_center": "Mittelpunkt X, Y, Z (mm)",
|
||||
"render.studio_denoise": "Rauschen im fertigen Bild reduzieren",
|
||||
"render.studio_denoise_help": "Kantenerhaltender Filter für progressive und fertige Bilder; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
|
||||
"render.studio_denoise_help": "Abschließender kantenerhaltender Filter; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
|
||||
"render.studio_direction": "Richtung zur Sonne (X, Y, Z)",
|
||||
"render.studio_environment": "Umgebung",
|
||||
"render.studio_environment_bounds": "Die Umgebungsdrehung muss endlich und innerhalb von ±1.000.000 Radiant liegen, die Intensität zwischen 0 und 100.",
|
||||
@@ -4164,19 +3983,6 @@
|
||||
"render.subsurface_enable": "Random Walk für innere Streuung",
|
||||
"render.subsurface_help": "Nur für geschlossene Dielektrika. Streuung und RGB-Absorption sind getrennte Koeffizienten pro mm. Pfade streuen innerhalb der tatsächlichen Geometrie und werden dann an der Grenzfläche gebrochen oder reflektiert. Deaktivieren Sie die Transmission gerader Schattenstrahlen, das Entrauschen von Oberflächen und das äußere Volumen; verwenden Sie eine Fresnel-Schicht. Kein Diffusions-Shader, kein Dichtegitter und kein gemessenes Material. Dichte Medien können mehr Samples und Pfadtiefe benötigen.",
|
||||
"render.subsurface_rate": "Streukollisionsrate",
|
||||
"render.surface_import.author": "Autor",
|
||||
"render.surface_import.busy": "Bild wird importiert…",
|
||||
"render.surface_import.complete": "Wählen Sie ein Bild und geben Sie Autor, Quelle und Lizenz an.",
|
||||
"render.surface_import.embedded": "Importiertes Bild",
|
||||
"render.surface_import.failed": "Der Bildimport wurde nicht abgeschlossen.",
|
||||
"render.surface_import.license": "Lizenz / Eigentum",
|
||||
"render.surface_import.raytrace_required": "Bilder erfordern CPU- oder GPU-Raytracing. Wählen Sie es unter Ausgabe.",
|
||||
"render.surface_import.repeat_u": "Wiederholung U",
|
||||
"render.surface_import.repeat_v": "Wiederholung V",
|
||||
"render.surface_import.save": "Bild am Körper speichern",
|
||||
"render.surface_import.saved_cpu": "Speichern Sie Texturen für das CPU-Rendering.",
|
||||
"render.surface_import.source": "Quelle",
|
||||
"render.surface_import.title": "Bild importieren",
|
||||
"render.tone_aces_fitted": "ACES angepasst",
|
||||
"render.tone_reinhard": "Reinhard",
|
||||
"render.transmission_scope": "GPU-Glas unterstützt zertifizierte getrennte Medien. Unbeschränkt verschachtelte Medien und ein rauschfreier Transmissionsschätzer werden nicht bereitgestellt.",
|
||||
@@ -4221,15 +4027,11 @@
|
||||
"render.use_preview": "Integrierte Vorschau verwenden",
|
||||
"render.volume_albedo": "Streualbedo · lineares RGB",
|
||||
"render.volume_anisotropy": "Mittlerer Phasenkosinus",
|
||||
"render.volume_denoise_required": "Für Atmosphäre die Rauschreduzierung ausschalten.",
|
||||
"render.volume_enable": "Teilnehmendes Medium aktivieren",
|
||||
"render.volume_extinction": "Extinktionskoeffizient",
|
||||
"render.volume_glass_required": "Atmosphäre und Glas können in dieser Einstellung nicht kombiniert werden.",
|
||||
"render.volume_help": "GPU-Transport innerhalb einer endlichen Box, mit Beer-Lambert-Abschwächung und HG-Streuung auf Kamera- und indirekten Pfaden. Verwendet ausdrückliche Studio-, HDR- und Himmelsbeleuchtung einschließlich Schattenabschwächung. Ein positiver Phasenkosinus streut nach vorn. Glasgrenzflächen und Oberflächenentrauschen müssen ausdrücklich deaktiviert werden. Extinktion null lässt den Transport unverändert. Keine physikalische Mondatmosphäre, kein Dichtegitter, keine Volumenemission, kein spektraler Transport und keine zertifizierte Materialeigenschaft.",
|
||||
"render.volume_lighting_required": "Für Atmosphäre eine Umgebung oder ein eigenes Lichtrig wählen.",
|
||||
"render.volume_max": "Obere Grenzen · Weltkoordinaten (mm)",
|
||||
"render.volume_min": "Untere Grenzen · Weltkoordinaten (mm)",
|
||||
"render.volume_single_stratum_required": "Für Atmosphäre ein Transmissionssample verwenden.",
|
||||
"render.volume_title": "Begrenztes homogenes Volumen",
|
||||
"render.wait_add_light": "Noch nicht unterstützt: benannte Lichtquellen erstellen",
|
||||
"render.wait_batch": "Noch nicht unterstützt: Renderwarteschlange gespeicherter Aufnahmen",
|
||||
@@ -4257,8 +4059,6 @@
|
||||
"sample.plate.name": "Beispielplatte",
|
||||
"sample.vents.about": "Langlöcher in einem Rechteckmuster: Zeilen, Spalten und Abstand sind Parameter",
|
||||
"sample.vents.name": "Lüftungsplatte",
|
||||
"scale.per_axis": "Je Achse",
|
||||
"scale.uniform": "Gleichmäßig",
|
||||
"search.command_keys": "Eingabe ausführen Umschalt+Eingabe ausführen und offen lassen Esc schließen",
|
||||
"search.commands": "Befehle",
|
||||
"search.design": "In diesem Design",
|
||||
@@ -4346,7 +4146,6 @@
|
||||
"sketch.dimension_applied": "Maß angewendet",
|
||||
"sketch.dimension_applied_defined": "Maß angewendet; Parameter `{name}` definiert",
|
||||
"sketch.dimension_applied_renamed": "Maß angewendet; der Parameter heißt jetzt `{name}`",
|
||||
"sketch.divide_hint": "Punkte teilen jede Kurve in gleich lange Abschnitte und behalten sie bei, wenn sie sich ändert.",
|
||||
"sketch.dof_left": "{count} Freiheitsgrade übrig",
|
||||
"sketch.dof_left_one": "{count} Freiheitsgrad übrig",
|
||||
"sketch.draw_curves": "Als {linetype} zeichnen",
|
||||
@@ -4902,7 +4701,6 @@
|
||||
"ui.click_selects_whole_bodies": "Klick wählt ganze Körper aus",
|
||||
"ui.click_then_press_key": "Klicken, dann die neue Taste drücken",
|
||||
"ui.close": "Schließen",
|
||||
"ui.close_tab": "Tab schließen",
|
||||
"ui.coil": "Schraube",
|
||||
"ui.coincident": "Koinzident",
|
||||
"ui.collapse": "Einklappen",
|
||||
@@ -5118,11 +4916,6 @@
|
||||
"ui.factor": "Faktor",
|
||||
"ui.fade": "Auslauf",
|
||||
"ui.fade_length": "Auslauflänge",
|
||||
"ui.fair_iterations": "Iterationen",
|
||||
"ui.fair_keep_edges": "Kanten behalten",
|
||||
"ui.fair_strength": "Stärke",
|
||||
"ui.fair_surface_title": "FLÄCHENBEGRADIGUNG",
|
||||
"ui.fair_tolerance_default": "0,001 der Körpergröße",
|
||||
"ui.feature_history": "Feature-Verlauf",
|
||||
"ui.feature_options": "Funktionsoptionen",
|
||||
"ui.features": "Funktionen",
|
||||
@@ -5164,6 +4957,7 @@
|
||||
"ui.fine": "Fein",
|
||||
"ui.finest": "Sehr fein",
|
||||
"ui.finish_sketch": "Skizze beenden",
|
||||
"ui.first_angle": "Erster Winkel",
|
||||
"ui.fit": "Einpassen",
|
||||
"ui.fix_unfix": "Fixieren/Lösen",
|
||||
"ui.flat": "Flach",
|
||||
@@ -5200,10 +4994,8 @@
|
||||
"ui.heading_construct": "KONSTRUKTION",
|
||||
"ui.heading_create": "ERSTELLEN",
|
||||
"ui.heading_create_sketch": "SKIZZE ERSTELLEN",
|
||||
"ui.heading_custom_view_navigator": "EIGENER ANSICHTSNAVIGATOR",
|
||||
"ui.heading_cylinder": "ZYLINDER",
|
||||
"ui.heading_design": "DESIGN",
|
||||
"ui.heading_divide_curve": "KURVE TEILEN",
|
||||
"ui.heading_draft": "ABZIEHEN",
|
||||
"ui.heading_edit": "BEARBEITEN",
|
||||
"ui.heading_equation_curve": "GLEICHUNGSKURVE",
|
||||
@@ -5284,11 +5076,6 @@
|
||||
"ui.history_play_button_show": "Wiedergabe-Schaltfläche einblenden",
|
||||
"ui.history_play_finish_action": "Die aktuelle Aktion abschließen, bevor der Verlauf abgespielt wird",
|
||||
"ui.history_play_roll_back": "Die Verlaufsmarkierung vor dem Abspielen zurücksetzen",
|
||||
"ui.history_scope_all": "Features des ganzen Designs zeigen",
|
||||
"ui.history_scope_all_shown": "Die Features des ganzen Designs",
|
||||
"ui.history_scope_pick": "Eine Komponente bearbeiten oder im Browser wählen, um nur ihre Features zu zeigen",
|
||||
"ui.history_scope_show": "Nur die Features von {name} und der Komponenten darin zeigen",
|
||||
"ui.history_scope_shown": "Die Features von {name} und der Komponenten darin",
|
||||
"ui.history_show_names": "Namen anzeigen",
|
||||
"ui.history_sketch_listed_under": "Aufgeführt unter {feature}",
|
||||
"ui.history_title": "Verlauf",
|
||||
@@ -5311,7 +5098,6 @@
|
||||
"ui.inspect": "Prüfen",
|
||||
"ui.instances": "Instanzen",
|
||||
"ui.intersect": "Schnitt",
|
||||
"ui.intersection_curve_second_pick": "Schnittkurve: zweite Fläche oder Kurve anklicken (Rechtsklick zum Neubeginn)",
|
||||
"ui.isolate": "Isolieren",
|
||||
"ui.join": "Verbinden",
|
||||
"ui.joint_play": "▶ Abspielen",
|
||||
@@ -5333,13 +5119,12 @@
|
||||
"ui.link_copies_text": "Diese Komponenten erzeugen dieselben Körper wie {name}. Sollen sie zu Platzierungen seiner Verknüpfung werden?",
|
||||
"ui.lock": "Sperren",
|
||||
"ui.loft": "Loft",
|
||||
"ui.look_at": "Senkrecht auf",
|
||||
"ui.look_at_anim_desc": "Achsenansicht, Senkrecht auf, Home und Einpassen",
|
||||
"ui.look_at": "Ansehen",
|
||||
"ui.look_at_anim_desc": "Achsenansicht, Ansehen, Home und Einpassen",
|
||||
"ui.major_radius": "Großer Radius",
|
||||
"ui.manipulator_free_alt": "Freies Ziehen des Manipulators auch mit Alt/Option",
|
||||
"ui.manipulator_free_alt_tip": "An: Alt/Option oder Strg/Cmd auf Extrusions-/Gizmo-Pfeilen deaktiviert das Einrasten von Werten. Aus: nur Strg/Cmd.",
|
||||
"ui.manufacture": "Fertigung",
|
||||
"ui.match_surface_title": "FLÄCHENANPASSUNG",
|
||||
"ui.material_review.axes": "Materiallokale Achsen; keine Körperausrichtung zugewiesen.",
|
||||
"ui.material_review.claims": "Quellen und Lizenzen sind ungeprüfte Angaben. Fehlende Werte bleiben unbekannt.",
|
||||
"ui.material_review.compressive_strength": "Druckfestigkeit",
|
||||
@@ -5362,9 +5147,6 @@
|
||||
"ui.material_review.title": "Physikalische Materialdaten prüfen",
|
||||
"ui.material_review.unknown": "Unbekannt",
|
||||
"ui.material_review.yield_strength": "Streckgrenze",
|
||||
"ui.mcp_agents": "Agenten über MCP",
|
||||
"ui.mcp_compact": "Kompakte Ausgabe",
|
||||
"ui.mcp_compact_note": "Für Agenten, die über den MCP-Server (solidcraft-cli mcp) verbunden sind: inspect_design fasst Skizzen zusammen und Werkzeugergebnisse werden nicht formatiert, sodass sie weniger Tokens verbrauchen. Gilt für Agentensitzungen, die nach der Änderung beginnen; --compact oder SOLIDCRAFT_MCP_COMPACT=1 schalten es ebenfalls ein.",
|
||||
"ui.measure": "Messen",
|
||||
"ui.mesh_format_3mf": "3MF",
|
||||
"ui.mesh_format_3mf_tagged": "3MF mit Flächenmarken",
|
||||
@@ -5406,7 +5188,6 @@
|
||||
"ui.options": "Optionen",
|
||||
"ui.orbit": "Drehen",
|
||||
"ui.orbit_around": "Umkreisen um",
|
||||
"ui.organize": "Organisieren",
|
||||
"ui.orientation": "Ausrichtung",
|
||||
"ui.origin": "Ursprung",
|
||||
"ui.palette_construction_mode": "Konstruktionsmodus (X)",
|
||||
@@ -5414,101 +5195,6 @@
|
||||
"ui.palette_sketch_options": "Skizzenoptionen",
|
||||
"ui.pan": "Verschieben",
|
||||
"ui.pan_zoom_orbit": "Verschieben, zoomen, umkreisen",
|
||||
"ui.panel_fixture.apply": "Anwenden",
|
||||
"ui.panel_fixture.cancel": "Abbrechen",
|
||||
"ui.panel_fixture.caption": "Synthetisches Panelmuster",
|
||||
"ui.panel_fixture.draft": "Beibehaltener Entwurf",
|
||||
"ui.panel_fixture.editor": "Mustereditor",
|
||||
"ui.panel_fixture.explanation": "Dieser synthetische Inhalt behält seinen Entwurf beim Ausblenden oder Verschieben.",
|
||||
"ui.panel_fixture.fill": "Füllendes Muster",
|
||||
"ui.panel_fixture.finish": "Abschließen",
|
||||
"ui.panel_fixture.finished": "Synthetische Transaktion beendet",
|
||||
"ui.panel_fixture.footer": "Musteraktionen lassen den Entwurf unverändert.",
|
||||
"ui.panel_fixture.guard": "Abbruch bei laufender Transaktion verweigern",
|
||||
"ui.panel_fixture.inline": "Einzeiliges Muster",
|
||||
"ui.panel_fixture.live": "Synthetische Transaktion läuft",
|
||||
"ui.panel_fixture.log": "Musterprotokoll",
|
||||
"ui.panel_fixture.pin": "Angeheftetes Muster",
|
||||
"ui.panel_fixture.title": "{title} · {id}",
|
||||
"ui.panel_fixture.updates": "Dienstaktualisierungen: {count}",
|
||||
"ui.panel_host.attention": "Aufmerksamkeit erforderlich",
|
||||
"ui.panel_host.bottom": "Unten",
|
||||
"ui.panel_host.close": "Panel schließen",
|
||||
"ui.panel_host.collapse": "Panel einklappen",
|
||||
"ui.panel_host.expand": "Panel ausklappen",
|
||||
"ui.panel_host.floating": "Schwebend",
|
||||
"ui.panel_host.focus": "Panel fokussieren",
|
||||
"ui.panel_host.focus_target": "{title} fokussieren",
|
||||
"ui.panel_host.header": "Panelkopf",
|
||||
"ui.panel_host.join": "Tabs verbinden",
|
||||
"ui.panel_host.join_target": "Tabs mit {title} verbinden",
|
||||
"ui.panel_host.left": "Links",
|
||||
"ui.panel_host.menu": "Panelaktionen",
|
||||
"ui.panel_host.move": "Verschieben nach",
|
||||
"ui.panel_host.overflow": "Weitere Panels",
|
||||
"ui.panel_host.popout": "In einem Fenster öffnen",
|
||||
"ui.panel_host.resize": "Panelgröße ändern",
|
||||
"ui.panel_host.right": "Rechts",
|
||||
"ui.panel_host.rule.availability": "Dieses Panel ist nicht verfügbar.",
|
||||
"ui.panel_host.rule.bounds": "Die Panels passen nicht in ihre Größenbegrenzungen.",
|
||||
"ui.panel_host.rule.capability": "Dieser Host unterstützt diese Aktion nicht.",
|
||||
"ui.panel_host.rule.collection_limit": "Das Panellayout hat seine Grenze erreicht.",
|
||||
"ui.panel_host.rule.duplicate_id": "Diese Panelkennung wird bereits verwendet.",
|
||||
"ui.panel_host.rule.exactly_once": "Jedes Panel muss einmal erscheinen.",
|
||||
"ui.panel_host.rule.feature_disabled": "Diese Aktion ist für dieses Panel deaktiviert.",
|
||||
"ui.panel_host.rule.id": "Die Panelkennung ist ungültig.",
|
||||
"ui.panel_host.rule.incompatible_tabs": "Diese Panels können keine Tabgruppe teilen.",
|
||||
"ui.panel_host.rule.lifecycle": "Das Panel konnte diese Aktion nicht abschließen.",
|
||||
"ui.panel_host.rule.missing_target": "Dieses Panelziel ist nicht mehr verfügbar.",
|
||||
"ui.panel_host.rule.no_space": "Für dieses Panellayout ist kein Platz.",
|
||||
"ui.panel_host.rule.pin_conflict": "An dieser Kante ist bereits ein anderes Panel angeheftet.",
|
||||
"ui.panel_host.rule.pin_order": "Ein angeheftetes Panel muss an seiner Kante zuerst bleiben.",
|
||||
"ui.panel_host.rule.placement": "Dieses Panel kann nicht an diese Position verschoben werden.",
|
||||
"ui.panel_host.rule.revision": "Das Layout wurde geändert. Versuchen Sie die Aktion erneut.",
|
||||
"ui.panel_host.rule.schema": "Die Panelanfrage ist ungültig.",
|
||||
"ui.panel_host.rule.version": "Diese Panellayoutversion wird nicht unterstützt.",
|
||||
"ui.panel_host.sash": "Benachbarte Panels skalieren",
|
||||
"ui.panel_host.scroll_horizontal": "Panelspalten scrollen",
|
||||
"ui.panel_host.scroll_vertical": "Panelstapel scrollen",
|
||||
"ui.panel_host.surface": "Panel",
|
||||
"ui.panel_host.tab": "Paneltab",
|
||||
"ui.panel_host.tabs": "Alle Tabs",
|
||||
"ui.panel_layout.applied": "{name} auf {workspace} angewendet",
|
||||
"ui.panel_layout.cancel": "Abbrechen",
|
||||
"ui.panel_layout.confirm_replace": "„{name}“ ersetzen?",
|
||||
"ui.panel_layout.copy": "Kopieren",
|
||||
"ui.panel_layout.current": "Aktuelles Layout",
|
||||
"ui.panel_layout.custom": "Benutzerdefiniert",
|
||||
"ui.panel_layout.custom_hint": "Panels wurden seit {name} verschoben.",
|
||||
"ui.panel_layout.details": "Details",
|
||||
"ui.panel_layout.layout_workspace": "Layout-Arbeitsbereich",
|
||||
"ui.panel_layout.page_defaults_hint": "Setzt die Einstellungen dieser Seite zurück. Panel-Anordnungen und gespeicherte Layouts bleiben erhalten.",
|
||||
"ui.panel_layout.panel_layout": "Panel-Layout",
|
||||
"ui.panel_layout.panel_style": "Panel-Stil",
|
||||
"ui.panel_layout.replace": "Layout ersetzen",
|
||||
"ui.panel_layout.replace_current": "Durch aktuelle Anordnung ersetzen",
|
||||
"ui.panel_layout.replace_short": "Ersetzen",
|
||||
"ui.panel_layout.reset": "Arbeitsbereich zurücksetzen",
|
||||
"ui.panel_layout.reset_hint": "{workspace} auf die Standardanordnung zurücksetzen. Gespeicherte Layouts bleiben erhalten.",
|
||||
"ui.panel_layout.reset_short": "Zurücksetzen",
|
||||
"ui.panel_layout.restore_failed": "Das Layout konnte nicht wiederhergestellt werden",
|
||||
"ui.panel_layout.save": "Layout speichern",
|
||||
"ui.panel_layout.save_as": "Speichern unter",
|
||||
"ui.panel_layout.save_as_tile": "Speichern unter…",
|
||||
"ui.panel_layout.selected": "ausgewählt",
|
||||
"ui.panel_layout.suggested_name": "Layout {n}",
|
||||
"ui.panel_layout.try_again": "Erneut versuchen",
|
||||
"ui.panel_layout.validation.builtin": "Integrierte Layouts können nicht geändert werden",
|
||||
"ui.panel_layout.validation.duplicate": "Dieser Name ist bereits vorhanden",
|
||||
"ui.panel_layout.validation.empty": "Namen eingeben",
|
||||
"ui.panel_layout.validation.full": "64 von 64 gespeichert. Ein Layout zum Ersetzen wählen.",
|
||||
"ui.panel_layout.validation.invalid": "1–64 Zeichen ohne Steuerzeichen verwenden",
|
||||
"ui.panel_layout.workspace.classic": "Klassisch",
|
||||
"ui.panel_layout.workspace.modelling": "Modellierung",
|
||||
"ui.panel_layout.workspace.render": "Rendern",
|
||||
"ui.panel_layout.workspace.review": "Prüfung",
|
||||
"ui.panel_layout.workspace.sketch": "Skizze",
|
||||
"ui.panel_layout.your_layout": "Eigenes Layout",
|
||||
"ui.parallel": "Parallel",
|
||||
"ui.parameters": "Parameter",
|
||||
"ui.part_size_needs_a_part": "Wählen Sie ein eingefügtes Standardbauteil (Schraube, Mutter, Scheibe oder Stift) aus, um seine Größe zu ändern. Einen Körper vergrößern oder verkleinern Sie mit Skalieren.",
|
||||
@@ -5546,7 +5232,6 @@
|
||||
"ui.point_cloud_add_filter": "Filter hinzufügen…",
|
||||
"ui.point_cloud_auto_range": "Bereich automatisch",
|
||||
"ui.point_cloud_color_by": "Färben nach",
|
||||
"ui.point_cloud_dots": "Einzelpunkte",
|
||||
"ui.point_cloud_file_colors": "Farben der Datei",
|
||||
"ui.point_cloud_filter_by": "Filtern nach",
|
||||
"ui.point_cloud_height_high": "Oberste Höhe",
|
||||
@@ -5555,7 +5240,6 @@
|
||||
"ui.point_cloud_no_file_colors": "Die Datei hat keine Farben",
|
||||
"ui.point_cloud_pause": "Pause",
|
||||
"ui.point_cloud_play": "Abspielen",
|
||||
"ui.point_cloud_reload": "Aus Datei neu laden",
|
||||
"ui.point_cloud_single_color": "Einheitliche Farbe",
|
||||
"ui.point_cloud_size": "Punktgröße",
|
||||
"ui.point_cloud_units": "Einheit der Datei",
|
||||
@@ -5574,6 +5258,7 @@
|
||||
"ui.profiles": "Profile",
|
||||
"ui.project": "Projizieren",
|
||||
"ui.project_face_edges_sketch": "Flächenkanten in neue Skizzen projizieren",
|
||||
"ui.projection_of_new_drawings": "Projektion neuer Zeichnungen",
|
||||
"ui.properties": "Eigenschaften",
|
||||
"ui.properties.copy_matrix": "Matrix kopieren",
|
||||
"ui.properties.first_moments": "Statische Momente",
|
||||
@@ -5617,8 +5302,6 @@
|
||||
"ui.repeat_nothing": "Noch nichts zu wiederholen: Starten Sie zuerst ein Werkzeug oder einen Dialog.",
|
||||
"ui.replace_with_link_done": "Die Kopien sind jetzt Platzierungen des verknüpften Designs.",
|
||||
"ui.report_a_bug": "Fehler melden",
|
||||
"ui.report_characters_left_one": "{count} Zeichen übrig",
|
||||
"ui.report_characters_left_other": "{count} Zeichen übrig",
|
||||
"ui.reset": "Zurücksetzen",
|
||||
"ui.reset_all": "Alle zurücksetzen",
|
||||
"ui.reset_the_settings_on_this_page": "Einstellungen auf dieser Seite zurücksetzen",
|
||||
@@ -5650,7 +5333,6 @@
|
||||
"ui.second_rail": "Zweite Leitkurve",
|
||||
"ui.section_analysis": "Querschnittsanalyse",
|
||||
"ui.section_size": "Querschnittsgröße",
|
||||
"ui.segments": "Segmente",
|
||||
"ui.select": "Auswählen",
|
||||
"ui.selectOther": "Andere auswählen…",
|
||||
"ui.selectTangentChain": "Tangentialkette auswählen",
|
||||
@@ -5759,6 +5441,7 @@
|
||||
"ui.text": "Text",
|
||||
"ui.theme": "Design",
|
||||
"ui.thickness": "Dicke",
|
||||
"ui.third_angle": "Dritter Winkel",
|
||||
"ui.thread": "Gewinde",
|
||||
"ui.tie_ends": "Verknüpfen",
|
||||
"ui.tie_ends_tip": "Alle unverknüpften Enden mit Abhängigkeiten versehen",
|
||||
@@ -5790,7 +5473,7 @@
|
||||
"ui.unsuppress": "Unterdrückung aufheben",
|
||||
"ui.untied_ends_tip": "Berührt nur nach Position",
|
||||
"ui.untitled": "Ohne Titel",
|
||||
"ui.up_axis_note": "Welche Achse in den Ansichten nach oben zeigt (Achsenansicht, Home, Senkrecht auf). Designs bleiben unverändert.",
|
||||
"ui.up_axis_note": "Welche Achse in den Ansichten nach oben zeigt (Achsenansicht, Home, Ansehen). Designs bleiben unverändert.",
|
||||
"ui.updateToCurrentView": "Auf aktuelle Ansicht aktualisieren",
|
||||
"ui.upload": "Hochladen",
|
||||
"ui.vertical_constraint": "Vertikale Bedingung",
|
||||
@@ -5901,14 +5584,6 @@
|
||||
"values must be numbers or expressions": "Werte müssen Zahlen oder Ausdrücke sein",
|
||||
"view.auto_ortho_faces": "Perspektivisch beim Drehen, orthografisch auf Flächen",
|
||||
"view.render_inspector": "Render-Inspektor",
|
||||
"view_gizmo.arrows": "Drehpfeile",
|
||||
"view_gizmo.home": "Schaltfläche „Ausgangsansicht“",
|
||||
"view_gizmo.no_such_body": "Diese Konstruktion hat keinen Körper mit diesem Namen.",
|
||||
"view_gizmo.note": "Wählen Sie einen Körper in der Ansicht. Er wird mit Ihren Einstellungen und Einstellungsprofilen gespeichert und in jeder Konstruktion angezeigt. Klicken Sie auf eine seiner Flächen, um direkt darauf zu blicken.",
|
||||
"view_gizmo.texture": "Textur",
|
||||
"view_gizmo.themed": "Design",
|
||||
"view_gizmo.too_detailed": "Zu detailliert für den Ansichtsnavigator (über 10.000 Dreiecke)",
|
||||
"view_gizmo.too_thin": "Zu lang und dünn für den Ansichtsnavigator: Die dünnste Seite muss mindestens ein Fünftel der längsten sein",
|
||||
"web.files.insert_drawings_hint": "CAD-Dateien, Netze, DXF oder SVG in das geöffnete Design",
|
||||
"web.files.insert_stored_drawings_hint": "In das geöffnete Design (CAD-Dateien, Netze, DXF, SVG)",
|
||||
"web.open_full": "In SolidCraft öffnen",
|
||||
|
||||
@@ -22,7 +22,8 @@
|
||||
"cmd.component_cycle_color": "Cycle Component Colour",
|
||||
"cmd.construct_point_center": "Point At Centre Of Circle/Sphere/Torus",
|
||||
"cmd.inspect_center_of_mass": "Centre of Mass",
|
||||
"cmd.inspect_colored_components": "Component Colours",
|
||||
"cmd.inspect_colored_components": "Enable Component Colours",
|
||||
"cmd.inspect_colored_components_disable": "Disable Component Colours",
|
||||
"cmd.parameters_favorite": "Favourite Parameter",
|
||||
"cmd.render_aging_describe": "Layered Surface Aging",
|
||||
"cmd.render_effects_describe": "Lens and Creative Effects",
|
||||
@@ -86,7 +87,6 @@
|
||||
"prefs.max_file_size_web": "The web app opens STEP and import files up to 512 MB and designs up to 256 MB: a browser tab can't hold larger files. The desktop app can raise these limits.",
|
||||
"prefs.megabytes": "MB",
|
||||
"prefs.network_locations_note": "For organisations that keep designs on file servers. Off: only servers you allow.",
|
||||
"prefs.select_hidden_edges_help": "Select edges through faces when Shaded with hidden edges is active, including for Fillet. Off keeps v0.30.1 selection behaviour. Hidden bodies stay unselectable.",
|
||||
"prefs.sketch.center_arc": "Centre-point arc follows the cursor",
|
||||
"prefs.sketch.conflict_colour": "Conflict colour",
|
||||
"prefs.sketch.tracking_guides": "Centre and midpoint guides",
|
||||
@@ -134,7 +134,6 @@
|
||||
"render.aging_metallic": "Layer metallic fraction",
|
||||
"render.aging_origin": "Field origin · world mm",
|
||||
"render.aging_patina": "Copper-green patina",
|
||||
"render.aging_raytrace_help": "Ageing is applied by CPU ray tracing and GPU path tracing. Raster keeps schematic rendering.",
|
||||
"render.aging_refresh_targets": "Inspect current bodies",
|
||||
"render.aging_roughness": "Layer roughness",
|
||||
"render.aging_rust": "Oxidized iron / rust",
|
||||
@@ -160,7 +159,6 @@
|
||||
"render.background_workflow": "Choose the background you intend: a camera-only colour/gradient, a physical stage that receives light, or HDR illumination. They are independent. Live Preview is a modelling view; render a new image or regrade a retained master to see camera finishing.",
|
||||
"render.base_color": "Base colour · sRGB",
|
||||
"render.bloom_help": "Creative output-only glow from positive linear highlights, spread with three normalised box passes. Radius is in output pixels. Added in linear display light before sRGB; not a lens, volumetric effect or convergence improvement. The raw EXR is unchanged.",
|
||||
"render.check_media_remaining": "Density grids, displacement, full spectral integration and propagated polarisation remain unavailable.",
|
||||
"render.chromatic_offset": "Chromatic edge offset",
|
||||
"render.cpu_area_editor_help": "Full side vectors in millimeters; U cross V points towards the emitting side. This differs from GPU half-edge vectors.",
|
||||
"render.cpu_light_help": "Up to eight directional lights. An empty custom list turns them off; selecting a preset restores its lights. Colours are linear RGB and intensity is relative, not watts.",
|
||||
@@ -180,7 +178,6 @@
|
||||
"render.flare_strength": "Highlight flare strength",
|
||||
"render.flare_threshold": "Scene-linear flare threshold",
|
||||
"render.frame_centre_mark": "Centre mark",
|
||||
"render.glass_model_help": "Glass roughness scatters transmitted rays. Dispersion and film colour use three wavelength bands. Rough sheets require straight transmitted shadows off.",
|
||||
"render.hdr_primaries_declared": "The source declares its colour primaries.",
|
||||
"render.hdr_primaries_srgb": "Colour primaries: sRGB/D65",
|
||||
"render.hdr_primaries_undeclared": "The source does not declare primaries; review the renderer colour requirements.",
|
||||
@@ -238,12 +235,6 @@
|
||||
"render.studio_area_help": "Up to four one-sided rectangles. Orthogonal half-edges U and V define size; U × V faces the lit side. Coordinates are world-space millimetres.",
|
||||
"render.studio_center": "Centre X, Y, Z (mm)",
|
||||
"render.studio_hdr_help": "Radiance .hdr, 2:1 panorama, up to 2048 × 1024. EXR is unavailable. The colour assumption applies only when primaries are unspecified.",
|
||||
"render.surface_import.complete": "Choose an image and enter its author, source and licence.",
|
||||
"render.surface_import.license": "Licence / ownership",
|
||||
"sketch.hint.arc.center_point": "Arc: click centre and start, go round either way and click the end, or type a signed sweep",
|
||||
"sketch.hint.circle.center": "Circle: click the centre, then a point on the circle",
|
||||
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to centre the second on the first",
|
||||
"sketch.hint.rectangle.center": "Centre rectangle: click the centre, then a corner",
|
||||
"sketch_text.align_center": "Centre",
|
||||
"ui.3d_mouse_kind_cap": "Return to Centre",
|
||||
"ui.3d_mouse_looks_like_cap": "The reading stays put while you hold it, the way a Return to Centre device does.",
|
||||
@@ -271,8 +262,6 @@
|
||||
"ui.layout_compact": "Compact",
|
||||
"ui.layout_floating": "Floating",
|
||||
"ui.material_review.claims": "Sources and licences are unverified claims. Missing values remain unknown.",
|
||||
"ui.organize": "Organise",
|
||||
"ui.panel_layout.workspace.modelling": "Modelling",
|
||||
"ui.point_cloud_color_by": "Colour By",
|
||||
"ui.point_cloud_file_colors": "File Colours",
|
||||
"ui.point_cloud_no_file_colors": "The file has no colours",
|
||||
|
||||
@@ -566,7 +566,6 @@
|
||||
"Select edges": "Sélectionner des arêtes",
|
||||
"Select points of the sketch being edited": "Sélectionner des points de l'esquisse en cours de modification",
|
||||
"Select the centre the pattern turns around first": "Sélectionner d'abord le centre autour duquel la répétition pivote",
|
||||
"Select the curves to divide first": "Sélectionner d'abord les courbes à diviser",
|
||||
"Select the curves to offset first": "Sélectionner d'abord les courbes à décaler",
|
||||
"Select the mirror line": "Sélectionner la ligne de symétrie",
|
||||
"Select the sketch curves or points to mirror first": "Sélectionner d'abord les courbes ou points d'esquisse à symétriser",
|
||||
@@ -957,6 +956,8 @@
|
||||
"cmd.inspect_accessibility": "Analyse d'accessibilité",
|
||||
"cmd.inspect_center_of_mass": "Centre de gravité",
|
||||
"cmd.inspect_clearance": "Jeu",
|
||||
"cmd.inspect_colored_components": "Activer les couleurs des composants",
|
||||
"cmd.inspect_colored_components_disable": "Désactiver les couleurs des composants",
|
||||
"cmd.inspect_compare": "Comparer les corps",
|
||||
"cmd.inspect_compare_picture": "Comparer les images",
|
||||
"cmd.inspect_continuity": "Analyse de continuité",
|
||||
@@ -1049,7 +1050,6 @@
|
||||
"cmd.pointcloud_edit": "Modifier le nuage de points",
|
||||
"cmd.pointcloud_insert": "Insérer un nuage de points",
|
||||
"cmd.pointcloud_list": "Nuages de points",
|
||||
"cmd.pointcloud_reload": "Recharger le nuage de points",
|
||||
"cmd.render.compare.divider": "Déplacer le séparateur de comparaison des rendus",
|
||||
"cmd.render.result.discard": "Écarter le résultat de rendu",
|
||||
"cmd.render.result.select": "Sélectionner le résultat de rendu",
|
||||
@@ -1195,7 +1195,6 @@
|
||||
"cmd.sketch_options": "Options de la palette d'esquisse",
|
||||
"cmd.sketch_paste": "Coller les entités d'esquisse",
|
||||
"cmd.sketch_point3d": "Point 3D",
|
||||
"cmd.sketch_point_divide": "Diviser la courbe",
|
||||
"cmd.sketch_polygon_circumscribed": "Polygone circonscrit",
|
||||
"cmd.sketch_polygon_edge": "Polygone par côté",
|
||||
"cmd.sketch_polygon_inscribed": "Polygone inscrit",
|
||||
@@ -1366,14 +1365,11 @@
|
||||
"dialog.computing": "Calcul… {seconds} s",
|
||||
"dialog.could_not_be_checked": "non vérifiable",
|
||||
"dialog.custom_axis": "↖ Axe personnalisé",
|
||||
"dialog.cylinder_face_tip": "La face ou le sommet du modèle sur lequel le cylindre est posé : cliquer ici, puis dessus dans la vue. Laissé vide, X, Y et Z le placent.",
|
||||
"dialog.dimension_insert_hint": "Double-cliquez sur une autre cote pilotante pour insérer son nom",
|
||||
"dialog.dimension_name": "Cote : {name}",
|
||||
"dialog.edit_title": "MODIFIER {title}",
|
||||
"dialog.no_shown_body": "Aucun corps affiché à modifier : affichez-en un ou choisissez Nouveau corps",
|
||||
"dialog.pattern_sketch_not_object": "Choisissez des corps ou des fonctions : une esquisse n'est ni l'un ni l'autre. Répétez ses courbes avec la Répétition ou la Symétrie de l'esquisse, ou extrudez-la d'abord.",
|
||||
"dialog.primitive_point_tip": "Un point qui remplit X, Y et Z : cliquer ici, puis sur un sommet, une arête ou une face du modèle, sur un point d'une esquisse ou sur l'origine.",
|
||||
"dialog.reference_edge_tip": "Une arête droite de la face choisie, depuis laquelle la distance ci-dessous est mesurée : cliquer ici, puis sur l'arête.",
|
||||
"dialog.select": "↖ Sélectionner",
|
||||
"dialog.selected_count": "↖ {count} sélectionné(s)",
|
||||
"dialog.selection_constraint": "Contrainte {id}",
|
||||
@@ -1428,7 +1424,6 @@
|
||||
"file_load.reading_percent": "Lecture du fichier... {percent} %",
|
||||
"file_load.rebuilding": "Reconstruction de l’entité {done} sur {total}",
|
||||
"file_load.recovering": "Récupération de la conception",
|
||||
"file_load.reloading_cloud": "Rechargement du nuage de points",
|
||||
"file_load.stopping": "Attente de l’arrêt de l’opération précédente...",
|
||||
"file_load.updating_links": "Mise à jour des liens",
|
||||
"file_load.waiting": "En attente d’une opération sur fichier",
|
||||
@@ -1628,7 +1623,7 @@
|
||||
"help.sketch.insert_svg": "Lit les tracés d'un dessin SVG dans une esquisse, mis à l'échelle, tournés et placés comme vous le définissez. À utiliser pour transformer un logo ou un contour vectoriel en courbes que vous pouvez extruder.",
|
||||
"help.sketch.inspect": "Ouvre un rapport sur l'esquisse ouverte : combien de degrés de liberté il reste et si elle est entièrement contrainte. À utiliser pour savoir ce qui bouge encore avant de terminer l'esquisse.",
|
||||
"help.sketch.intersect": "Ajoute les courbes ou points où des corps, faces, arêtes ou d'autres esquisses traversent le plan d'esquisse. Ils restent liés et suivent le modèle lorsqu'il change.",
|
||||
"help.sketch.intersection_curve": "Crée des courbes 3D là où deux corps, faces ou courbes d'autres esquisses se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
|
||||
"help.sketch.intersection_curve": "Crée des courbes 3D là où deux corps ou faces se rencontrent, liées à eux. À utiliser comme trajectoires ou rails guides qui suivent le modèle.",
|
||||
"help.sketch.isoparametric_curve": "Crée une courbe 3D le long de la direction U ou V d'une face, par un point que vous choisissez, liée à la face. À utiliser comme trajectoire ou ligne de partage qui suit la surface.",
|
||||
"help.sketch.line": "Dessine des lignes connectées point par point, en chaîne ouverte ou en contour fermé. C'est l'outil de base de la plupart des profils.",
|
||||
"help.sketch.line.midpoint": "Dessine une ligne de son milieu vers une extrémité, l'autre extrémité étant son symétrique. À utiliser pour des lignes centrées sur un point, comme une arête symétrique.",
|
||||
@@ -1640,7 +1635,6 @@
|
||||
"help.sketch.pattern.circular": "Copie la géométrie d'esquisse autour d'un centre, sur un cercle complet ou un angle donné. À utiliser pour des cercles de perçages, des rayons et des dents.",
|
||||
"help.sketch.pattern.rectangular": "Copie la géométrie d'esquisse en lignes et colonnes à des espacements donnés. À utiliser pour des grilles de trous, de rainures ou d'aérations.",
|
||||
"help.sketch.point": "Place un point d'esquisse. À utiliser comme référence pour des cotes, des contraintes, des centres de perçage ou des fonctions ultérieures.",
|
||||
"help.sketch.point.divide": "Place des points qui divisent les courbes choisies en longueurs égales. Les points restent sur chaque courbe et gardent leur espacement quand elle change.",
|
||||
"help.sketch.point3d": "Place un point dans l'espace, avec Esquisse 3D activée. À utiliser comme référence pour des courbes et trajectoires 3D.",
|
||||
"help.sketch.polygon.circumscribed": "Dessine un polygone régulier autour d'un cercle, dimensionné du centre au milieu d'un côté. À utiliser lorsque la largeur sur plats est connue, comme pour un écrou hexagonal.",
|
||||
"help.sketch.polygon.edge": "Dessine un polygone régulier à partir des deux extrémités d'un côté. À utiliser lorsque la longueur et la position d'un côté sont connues.",
|
||||
@@ -2206,8 +2200,6 @@
|
||||
"prefs.edges": "Arêtes et ombrage",
|
||||
"prefs.extend": "Étendre",
|
||||
"prefs.extra_navigation": "Ctrl+glisser gauche dans le vide fait orbiter ; maintenir Espace pour un panoramique.",
|
||||
"prefs.extrude_arrow_face_stops": "La flèche d'extrusion s'arrête aux faces",
|
||||
"prefs.extrude_arrow_face_stops_note": "En faisant glisser la flèche de distance d'Extrusion, elle s'arrête aux faces planes parallèles au profil, comme l'autre côté d'un bloc qu'on découpe, et souligne la face. Désactivé : la flèche avance seulement par pas ronds.",
|
||||
"prefs.extrude_view_isometric": "Passer la vue en isométrique à l'ouverture de l'extrusion",
|
||||
"prefs.extrude_view_return": "Revenir à la vue précédente à la fermeture de l'extrusion",
|
||||
"prefs.follows_os": "Suit le système",
|
||||
@@ -2287,8 +2279,6 @@
|
||||
"prefs.mouse": "Souris et pavé tactile",
|
||||
"prefs.mouse_heading": "Souris",
|
||||
"prefs.mouse_mapping": "Affectation des boutons de la souris",
|
||||
"prefs.move_drag_highlights": "Mettre en évidence ce qui est sous le pointeur pendant que les flèches de Déplacer font glisser un composant",
|
||||
"prefs.move_drag_highlights_tip": "Désactivé : pendant que les flèches de Déplacer font glisser un composant, rien n’est mis en évidence sous le pointeur, ce qui garde les grands assemblages fluides. Avec une coupe ou une analyse affichée, la mise en évidence reste, et une pièce glissée le long de ses liaisons n’en montre jamais.",
|
||||
"prefs.navigation": "Navigation",
|
||||
"prefs.navigation_note": "Comment la souris, le pavé tactile et la souris 3D déplacent la vue.",
|
||||
"prefs.navigator_fit": "Cadrer le modèle après un changement de vue du repère de navigation",
|
||||
@@ -2787,8 +2777,6 @@
|
||||
"render.cpu_lighting": "Préréglage d'éclairage",
|
||||
"render.cpu_lights": "Lumières CPU",
|
||||
"render.cpu_linear_color": "Couleur linéaire (R, V, B)",
|
||||
"render.cpu_maps_saved_help": "Le rendu CPU accepte les cartes opaques enregistrées : choisissez une surface, puis enregistrez ses cartes dans la vue. Avec l’éclairage indirect, un autre corps peut utiliser Verre ; le corps avec les cartes ne le peut pas. Désactivez les réflexions sélectionnées, l’anisotropie et les effets procéduraux. Les fichiers temporaires de cartes utilisent le GPU.",
|
||||
"render.cpu_material_certification": "La vérification CPU valide la géométrie de verre prise en charge avant le rendu. Le verre fermé utilise la distance géométrique.",
|
||||
"render.cpu_neutral": "Neutre",
|
||||
"render.cpu_opaque_required": "Le lancer de rayons CPU nécessite un arrière-plan opaque. Décochez Arrière-plan transparent pour continuer.",
|
||||
"render.cpu_options_pending": "Ce panneau CPU ne peut pas encore modifier l'émission, les effets de surface, le verre ni l'anisotropie. Effacez ces options d'API avant de l'utiliser.",
|
||||
@@ -3013,11 +3001,9 @@
|
||||
"render.frame_wide": "16:9 · Écran large",
|
||||
"render.gpu_depth": "Profondeur maximale des chemins",
|
||||
"render.gpu_options_help": "Les échantillons, la profondeur, la graine et l'exposition sont enregistrés avec ce préréglage. Seule la méthode d'image active est enregistrée.",
|
||||
"render.gpu_path_crashed": "SolidCraft s’est fermé pendant sa dernière préparation sur cet adaptateur graphique, très probablement dans le pilote graphique ; il n’est donc plus préparé de lui-même. Utilisez le lancer de rayons sur le CPU, ou réessayez (une mise à jour du pilote réessaie aussi).",
|
||||
"render.gpu_path_failed": "Traceur de chemins GPU indisponible",
|
||||
"render.gpu_path_first_setup": "La première initialisation sur ce GPU peut prendre plusieurs minutes ; SolidCraft reste utilisable pendant ce temps. Les démarrages suivants réutilisent les shaders compilés lorsque le pilote les conserve.",
|
||||
"render.gpu_path_preparing": "Traceur de chemins GPU : compilation des shaders…",
|
||||
"render.gpu_path_retry": "Réessayer",
|
||||
"render.gpu_seed": "Graine aléatoire",
|
||||
"render.gpu_unavailable": "Le tracé de chemins GPU n'est pas prêt sur cet appareil.",
|
||||
"render.ground_apply": "Ajuster et enregistrer le sol",
|
||||
@@ -3825,8 +3811,6 @@
|
||||
"sample.plate.name": "Plaque d'exemple",
|
||||
"sample.vents.about": "Rainures en répétition rectangulaire : lignes, colonnes et pas sont des paramètres",
|
||||
"sample.vents.name": "Plaque d'aération",
|
||||
"scale.per_axis": "Par axe",
|
||||
"scale.uniform": "Uniforme",
|
||||
"search.command_keys": "Entrée exécuter Maj+Entrée exécuter et garder ouvert Échap fermer",
|
||||
"search.commands": "Commandes",
|
||||
"search.design": "Dans cette conception",
|
||||
@@ -3914,7 +3898,6 @@
|
||||
"sketch.dimension_applied": "Cote appliquée",
|
||||
"sketch.dimension_applied_defined": "Cote appliquée ; paramètre `{name}` défini",
|
||||
"sketch.dimension_applied_renamed": "Cote appliquée ; paramètre nommé `{name}`",
|
||||
"sketch.divide_hint": "Les points divisent chaque courbe en longueurs égales et les gardent quand elle change.",
|
||||
"sketch.dof_left": "{count} degrés de liberté restants",
|
||||
"sketch.dof_left_one": "{count} degré de liberté restant",
|
||||
"sketch.draw_curves": "Dessiner en {linetype}",
|
||||
@@ -4457,7 +4440,6 @@
|
||||
"ui.click_selects_whole_bodies": "Un clic sélectionne des corps entiers",
|
||||
"ui.click_then_press_key": "Cliquer, puis appuyer sur la nouvelle touche",
|
||||
"ui.close": "Fermer",
|
||||
"ui.close_tab": "Fermer l’onglet",
|
||||
"ui.coil": "Hélice",
|
||||
"ui.coincident": "Coïncidence",
|
||||
"ui.collapse": "Réduire",
|
||||
@@ -4704,7 +4686,6 @@
|
||||
"ui.heading_create_sketch": "CRÉER UNE ESQUISSE",
|
||||
"ui.heading_cylinder": "CYLINDRE",
|
||||
"ui.heading_design": "DESIGN",
|
||||
"ui.heading_divide_curve": "DIVISER LA COURBE",
|
||||
"ui.heading_draft": "DÉPOUILLE",
|
||||
"ui.heading_edit": "ÉDITION",
|
||||
"ui.heading_equation_curve": "COURBE PAR ÉQUATION",
|
||||
@@ -4942,7 +4923,6 @@
|
||||
"ui.point_cloud_add_filter": "Ajouter un filtre…",
|
||||
"ui.point_cloud_auto_range": "Plage automatique",
|
||||
"ui.point_cloud_color_by": "Colorer par",
|
||||
"ui.point_cloud_dots": "Points isolés",
|
||||
"ui.point_cloud_file_colors": "Couleurs du fichier",
|
||||
"ui.point_cloud_filter_by": "Filtrer par",
|
||||
"ui.point_cloud_height_high": "Plus haut",
|
||||
@@ -4951,7 +4931,6 @@
|
||||
"ui.point_cloud_no_file_colors": "Le fichier n'a pas de couleurs",
|
||||
"ui.point_cloud_pause": "Pause",
|
||||
"ui.point_cloud_play": "Lire",
|
||||
"ui.point_cloud_reload": "Recharger le fichier",
|
||||
"ui.point_cloud_single_color": "Couleur unique",
|
||||
"ui.point_cloud_size": "Taille des points",
|
||||
"ui.point_cloud_units": "Unités du fichier",
|
||||
@@ -5044,7 +5023,6 @@
|
||||
"ui.second_rail": "Second rail",
|
||||
"ui.section_analysis": "Analyse de coupe",
|
||||
"ui.section_size": "Taille de la section",
|
||||
"ui.segments": "Segments",
|
||||
"ui.select": "Sélectionner",
|
||||
"ui.selectOther": "Sélectionner un autre…",
|
||||
"ui.selectTangentChain": "Sélectionner la chaîne tangente",
|
||||
|
||||
@@ -957,6 +957,8 @@
|
||||
"cmd.inspect_accessibility": "Análise de accesibilidade",
|
||||
"cmd.inspect_center_of_mass": "Centro de masas",
|
||||
"cmd.inspect_clearance": "Folga",
|
||||
"cmd.inspect_colored_components": "Activar as cores dos compoñentes",
|
||||
"cmd.inspect_colored_components_disable": "Desactivar as cores dos compoñentes",
|
||||
"cmd.inspect_compare": "Comparar corpos",
|
||||
"cmd.inspect_compare_picture": "Comparar a imaxe",
|
||||
"cmd.inspect_continuity": "Análise de continuidade",
|
||||
@@ -1621,7 +1623,7 @@
|
||||
"help.sketch.insert_svg": "Le os trazados dun debuxo SVG nun bosquexo, escalados, xirados e colocados onde indiques. Úsao para converter un logotipo ou un contorno vectorial en curvas que podes extruír.",
|
||||
"help.sketch.inspect": "Abre un informe do bosquexo aberto: cantos graos de liberdade quedan e se está totalmente restrinxido. Úsao para saber o que aínda se move antes de rematar o bosquexo.",
|
||||
"help.sketch.intersect": "Engade as curvas ou os puntos onde corpos, caras, arestas ou outros bosquexos cruzan o plano do bosquexo. Quedan ligados e seguen o modelo cando cambia.",
|
||||
"help.sketch.intersection_curve": "Crea curvas 3D onde se atopan dous corpos, caras ou curvas doutros bosquexos, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
|
||||
"help.sketch.intersection_curve": "Crea curvas 3D onde se atopan dous corpos ou caras, ligadas a eles. Úsaas como traxectorias ou guías que seguen o modelo.",
|
||||
"help.sketch.isoparametric_curve": "Crea unha curva 3D na dirección U ou V dunha cara por un punto que escollas, ligada á cara. Úsaa como traxectoria ou liña de división que segue a superficie.",
|
||||
"help.sketch.line": "Debuxa liñas conectadas punto a punto, como cadea aberta ou como contorno pechado. É a ferramenta básica para a maioría dos perfís.",
|
||||
"help.sketch.line.midpoint": "Debuxa unha liña desde o seu punto medio ata un extremo, co outro extremo reflectido. Úsao para liñas centradas nun punto, como unha aresta simétrica.",
|
||||
@@ -3807,8 +3809,6 @@
|
||||
"sample.plate.name": "Placa de exemplo",
|
||||
"sample.vents.about": "Ranuras nun patrón rectangular: filas, columnas e paso son parámetros",
|
||||
"sample.vents.name": "Placa de ventilación",
|
||||
"scale.per_axis": "Por eixo",
|
||||
"scale.uniform": "Uniforme",
|
||||
"search.command_keys": "Intro executar Maiús+Intro executar e manter aberto Esc pechar",
|
||||
"search.commands": "Ordes",
|
||||
"search.design": "Neste deseño",
|
||||
|
||||
@@ -957,6 +957,8 @@
|
||||
"cmd.inspect_accessibility": "Analiza dostupnosti",
|
||||
"cmd.inspect_center_of_mass": "Središte mase",
|
||||
"cmd.inspect_clearance": "Provjera zazora",
|
||||
"cmd.inspect_colored_components": "Omogući boje komponenti",
|
||||
"cmd.inspect_colored_components_disable": "Onemogući boje komponenti",
|
||||
"cmd.inspect_compare": "Usporedi tijela",
|
||||
"cmd.inspect_compare_picture": "Usporedi sliku",
|
||||
"cmd.inspect_continuity": "Analiza kontinuiteta",
|
||||
@@ -1621,7 +1623,7 @@
|
||||
"help.sketch.insert_svg": "Čita putanje SVG crteža u skicu, skalirane, zakrenute i postavljene kako zadate. Upotrijebite ga za pretvaranje logotipa ili vektorskog obrisa u krivulje koje možete ekstrudirati.",
|
||||
"help.sketch.inspect": "Otvara izvješće o otvorenoj skici: koliko je stupnjeva slobode preostalo i je li potpuno ograničena. Upotrijebite ga kako biste saznali što se još pomiče prije dovršetka skice.",
|
||||
"help.sketch.intersect": "Dodaje krivulje ili točke u kojima tijela, lica, bridovi ili druge skice presijecaju ravninu skice. Ostaju povezane i prate model kad se promijeni.",
|
||||
"help.sketch.intersection_curve": "Izrađuje 3D krivulje na mjestu gdje se dva tijela, lica ili krivulje drugih skica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
|
||||
"help.sketch.intersection_curve": "Izrađuje 3D krivulje na mjestu gdje se dva tijela ili lica susreću, povezane s njima. Upotrijebite ih kao putanje ili vodilice koje prate model.",
|
||||
"help.sketch.isoparametric_curve": "Izrađuje 3D krivulju u smjeru U ili V lica kroz točku koju odaberete, povezanu s licem. Upotrijebite je kao putanju ili liniju razdvajanja koja prati plohu.",
|
||||
"help.sketch.line": "Crta povezane linije točku po točku, kao otvoreni lanac ili zatvoreni obris. To je osnovni alat za većinu profila.",
|
||||
"help.sketch.line.midpoint": "Crta liniju od njezine središnje točke do jednog kraja, a drugi kraj zrcali. Upotrijebite ga za linije centrirane na točku, primjerice simetričan brid.",
|
||||
|
||||
@@ -957,6 +957,8 @@
|
||||
"cmd.inspect_accessibility": "Hozzáférhetőség-elemzés",
|
||||
"cmd.inspect_center_of_mass": "Tömegközéppont",
|
||||
"cmd.inspect_clearance": "Hézag",
|
||||
"cmd.inspect_colored_components": "Komponensszínek bekapcsolása",
|
||||
"cmd.inspect_colored_components_disable": "Komponensszínek kikapcsolása",
|
||||
"cmd.inspect_compare": "Testek összehasonlítása",
|
||||
"cmd.inspect_compare_picture": "Kép összehasonlítása",
|
||||
"cmd.inspect_continuity": "Folytonosságelemzés",
|
||||
@@ -1621,7 +1623,7 @@
|
||||
"help.sketch.insert_svg": "Beolvassa egy SVG-rajz útvonalait egy vázlatba, az Ön által megadott léptékkel, elforgatással és helyen. Használja egy logó vagy vektoros körvonal kihúzható görbékké alakítására.",
|
||||
"help.sketch.inspect": "Jelentést nyit a megnyitott vázlatról: hány szabadságfok maradt, és teljesen kényszerezett-e. Használja annak kiderítésére, mi mozdul még, mielőtt befejezi a vázlatot.",
|
||||
"help.sketch.intersect": "Hozzáadja azokat a görbéket vagy pontokat, ahol testek, lapok, élek vagy más vázlatok metszik a vázlatsíkot. Kapcsoltak maradnak, és követik a modellt, ha az megváltozik.",
|
||||
"help.sketch.intersection_curve": "3D görbéket hoz létre ott, ahol két test, lap vagy más vázlatok görbéi találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
|
||||
"help.sketch.intersection_curve": "3D görbéket hoz létre ott, ahol két test vagy lap találkozik, hozzájuk kapcsolva. Használja őket útvonalként vagy vezérgörbeként, amely követi a modellt.",
|
||||
"help.sketch.isoparametric_curve": "3D görbét hoz létre egy lap U vagy V iránya mentén egy kiválasztott ponton át, a laphoz kapcsolva. Használja útvonalként vagy osztóvonalként, amely követi a felületet.",
|
||||
"help.sketch.line": "Összefüggő vonalakat rajzol pontról pontra, nyitott láncként vagy zárt körvonalként. Ez a legtöbb profil alapeszköze.",
|
||||
"help.sketch.line.midpoint": "Vonalat rajzol a felezőpontjától az egyik végéig, a másik véget tükrözve. Használja egy pontra középen álló vonalakhoz, például szimmetrikus élhez.",
|
||||
@@ -3807,8 +3809,6 @@
|
||||
"sample.plate.name": "Mintalemez",
|
||||
"sample.vents.about": "Hornyok téglalap mintában: a sorok, oszlopok és osztás paraméterek",
|
||||
"sample.vents.name": "Szellőzőlemez",
|
||||
"scale.per_axis": "Tengelyenként",
|
||||
"scale.uniform": "Egyenletes",
|
||||
"search.command_keys": "Enter futtatás Shift+Enter futtatás és nyitva tartás Esc bezárás",
|
||||
"search.commands": "Parancsok",
|
||||
"search.design": "Ebben a tervben",
|
||||
|
||||
@@ -957,6 +957,8 @@
|
||||
"cmd.inspect_accessibility": "Analisis Aksesibilitas",
|
||||
"cmd.inspect_center_of_mass": "Pusat Massa",
|
||||
"cmd.inspect_clearance": "Kelonggaran",
|
||||
"cmd.inspect_colored_components": "Aktifkan Warna Komponen",
|
||||
"cmd.inspect_colored_components_disable": "Nonaktifkan Warna Komponen",
|
||||
"cmd.inspect_compare": "Bandingkan Bodi",
|
||||
"cmd.inspect_compare_picture": "Bandingkan dengan Gambar",
|
||||
"cmd.inspect_continuity": "Analisis Kontinuitas",
|
||||
@@ -1621,7 +1623,7 @@
|
||||
"help.sketch.insert_svg": "Membaca path gambar SVG ke dalam sketsa, diskalakan, diputar, dan ditempatkan sesuai pengaturan Anda. Gunakan untuk mengubah logo atau kontur vektor menjadi kurva yang dapat diekstrusi.",
|
||||
"help.sketch.inspect": "Membuka laporan tentang sketsa yang terbuka: berapa derajat kebebasan yang tersisa dan apakah sketsa terbatasi penuh. Gunakan untuk mengetahui apa yang masih bergerak sebelum Anda menyelesaikan sketsa.",
|
||||
"help.sketch.intersect": "Menambahkan kurva atau titik tempat bodi, muka, tepi, atau sketsa lain memotong bidang sketsa. Semuanya tetap tertaut dan mengikuti model saat model berubah.",
|
||||
"help.sketch.intersection_curve": "Membuat kurva 3D di tempat dua bodi, muka, atau kurva sketsa lain bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
|
||||
"help.sketch.intersection_curve": "Membuat kurva 3D di tempat dua bodi atau muka bertemu, tertaut padanya. Gunakan sebagai jalur atau rel pemandu yang mengikuti model.",
|
||||
"help.sketch.isoparametric_curve": "Membuat kurva 3D sepanjang arah U atau V sebuah muka melalui titik yang Anda pilih, tertaut ke muka itu. Gunakan sebagai jalur atau garis pisah yang mengikuti permukaan.",
|
||||
"help.sketch.line": "Menggambar garis-garis tersambung titik demi titik, sebagai rantai terbuka atau kontur tertutup. Ini alat dasar untuk sebagian besar profil.",
|
||||
"help.sketch.line.midpoint": "Menggambar garis dari titik tengahnya ke satu ujung, dengan ujung lainnya dicerminkan. Gunakan untuk garis yang dipusatkan pada sebuah titik, seperti tepi simetris.",
|
||||
@@ -3807,8 +3809,6 @@
|
||||
"sample.plate.name": "Pelat Contoh",
|
||||
"sample.vents.about": "Slot dalam pola persegi panjang: baris, kolom, dan pitch adalah parameter",
|
||||
"sample.vents.name": "Pelat Ventilasi",
|
||||
"scale.per_axis": "Per sumbu",
|
||||
"scale.uniform": "Seragam",
|
||||
"search.command_keys": "Enter jalankan Shift+Enter jalankan dan tetap buka Esc tutup",
|
||||
"search.commands": "Perintah",
|
||||
"search.design": "Di desain ini",
|
||||
|
||||
@@ -567,7 +567,6 @@
|
||||
"Select edges": "Seleziona spigoli",
|
||||
"Select points of the sketch being edited": "Seleziona punti dello schizzo in modifica",
|
||||
"Select the centre the pattern turns around first": "Seleziona prima il centro attorno a cui ruota la serie",
|
||||
"Select the curves to divide first": "Selezionare prima le curve da dividere",
|
||||
"Select the curves to offset first": "Seleziona prima le curve per l'offset",
|
||||
"Select the mirror line": "Seleziona la linea di simmetria",
|
||||
"Select the sketch curves or points to mirror first": "Seleziona prima le curve o i punti dello schizzo da specchiare",
|
||||
@@ -958,6 +957,8 @@
|
||||
"cmd.inspect_accessibility": "Analisi di accessibilità",
|
||||
"cmd.inspect_center_of_mass": "Centro di massa",
|
||||
"cmd.inspect_clearance": "Distanza libera",
|
||||
"cmd.inspect_colored_components": "Attiva colori dei componenti",
|
||||
"cmd.inspect_colored_components_disable": "Disattiva colori dei componenti",
|
||||
"cmd.inspect_compare": "Confronta corpi",
|
||||
"cmd.inspect_compare_picture": "Confronta immagine",
|
||||
"cmd.inspect_continuity": "Analisi di continuità",
|
||||
@@ -1195,7 +1196,6 @@
|
||||
"cmd.sketch_options": "Opzioni della tavolozza schizzo",
|
||||
"cmd.sketch_paste": "Incolla entità dello schizzo",
|
||||
"cmd.sketch_point3d": "Punto 3D",
|
||||
"cmd.sketch_point_divide": "Dividi curva",
|
||||
"cmd.sketch_polygon_circumscribed": "Poligono circoscritto",
|
||||
"cmd.sketch_polygon_edge": "Poligono per lato",
|
||||
"cmd.sketch_polygon_inscribed": "Poligono inscritto",
|
||||
@@ -1366,14 +1366,11 @@
|
||||
"dialog.computing": "Calcolo in corso… {seconds} s",
|
||||
"dialog.could_not_be_checked": "impossibile verificare",
|
||||
"dialog.custom_axis": "↖ Asse personalizzato",
|
||||
"dialog.cylinder_face_tip": "La faccia o il vertice del modello su cui poggia il cilindro: fai clic qui, poi su di esso nella vista. Se resta vuoto, lo posizionano X, Y e Z.",
|
||||
"dialog.dimension_insert_hint": "Fai doppio clic su un’altra quota di controllo per inserirne il nome",
|
||||
"dialog.dimension_name": "Quota: {name}",
|
||||
"dialog.edit_title": "MODIFICA {title}",
|
||||
"dialog.no_shown_body": "Nessun corpo visibile da modificare: mostrane uno o scegli Nuovo corpo",
|
||||
"dialog.pattern_sketch_not_object": "Seleziona corpi o lavorazioni: uno schizzo non è nessuno dei due. Ripeti le sue curve con Ripetizione o Specchia dello schizzo stesso, oppure estrudilo prima.",
|
||||
"dialog.primitive_point_tip": "Un punto che compila X, Y e Z: fai clic qui, poi su un vertice, uno spigolo o una faccia del modello, su un punto di uno schizzo oppure sull'origine.",
|
||||
"dialog.reference_edge_tip": "Uno spigolo rettilineo della faccia scelta da cui si misura la distanza sotto: fai clic qui, poi sullo spigolo.",
|
||||
"dialog.select": "↖ Seleziona",
|
||||
"dialog.selected_count": "↖ Selezionati: {count}",
|
||||
"dialog.selection_constraint": "Vincolo {id}",
|
||||
@@ -1627,7 +1624,7 @@
|
||||
"help.sketch.insert_svg": "Legge i tracciati di un disegno SVG in uno schizzo, scalati, ruotati e posizionati come imposti. Usalo per trasformare un logo o un contorno vettoriale in curve che puoi estrudere.",
|
||||
"help.sketch.inspect": "Apre un rapporto sullo schizzo aperto: quanti gradi di libertà restano e se è completamente vincolato. Usalo per scoprire cosa si muove ancora prima di terminare lo schizzo.",
|
||||
"help.sketch.intersect": "Aggiunge le curve o i punti in cui corpi, facce, spigoli o altri schizzi attraversano il piano dello schizzo. Restano collegati e seguono il modello quando cambia.",
|
||||
"help.sketch.intersection_curve": "Crea curve 3D dove si incontrano due corpi, facce o curve di altri schizzi, collegate a essi. Usale come percorsi o guide che seguono il modello.",
|
||||
"help.sketch.intersection_curve": "Crea curve 3D dove si incontrano due corpi o facce, collegate a essi. Usale come percorsi o guide che seguono il modello.",
|
||||
"help.sketch.isoparametric_curve": "Crea una curva 3D lungo la direzione U o V di una faccia passante per un punto che scegli, collegata alla faccia. Usala come percorso o linea di divisione che segue la superficie.",
|
||||
"help.sketch.line": "Disegna linee collegate punto per punto, come catena aperta o contorno chiuso. È lo strumento di base per la maggior parte dei profili.",
|
||||
"help.sketch.line.midpoint": "Disegna una linea dal suo punto medio fino a un'estremità, con l'altra estremità speculare. Usalo per linee centrate su un punto, come un bordo simmetrico.",
|
||||
@@ -1639,7 +1636,6 @@
|
||||
"help.sketch.pattern.circular": "Copia la geometria dello schizzo attorno a un punto centrale, su un cerchio completo o su un angolo stabilito. Usalo per cerchi di bulloni, raggi e denti.",
|
||||
"help.sketch.pattern.rectangular": "Copia la geometria dello schizzo in righe e colonne con spaziature stabilite. Usalo per griglie di fori, asole o prese d'aria.",
|
||||
"help.sketch.point": "Colloca un punto dello schizzo. Usalo come riferimento per quote, vincoli, centri di fori o lavorazioni successive.",
|
||||
"help.sketch.point.divide": "Posiziona punti che dividono le curve scelte in lunghezze uguali. I punti restano su ogni curva e ne mantengono la spaziatura quando cambia.",
|
||||
"help.sketch.point3d": "Colloca un punto nello spazio, con Schizzo 3D attivo. Usalo come riferimento per curve e percorsi 3D.",
|
||||
"help.sketch.polygon.circumscribed": "Disegna un poligono regolare attorno a un cerchio, dimensionato dal centro al punto medio di un lato. Usalo quando è nota l'apertura chiave, come per un dado esagonale.",
|
||||
"help.sketch.polygon.edge": "Disegna un poligono regolare a partire dai due estremi di un lato. Usalo quando sono note la lunghezza e la posizione di un lato.",
|
||||
@@ -3816,8 +3812,6 @@
|
||||
"sample.plate.name": "Piastra di esempio",
|
||||
"sample.vents.about": "Asole in ripetizione rettangolare: righe, colonne e passo sono parametri",
|
||||
"sample.vents.name": "Piastra di ventilazione",
|
||||
"scale.per_axis": "Per asse",
|
||||
"scale.uniform": "Uniforme",
|
||||
"search.command_keys": "Invio esegui Maiusc+Invio esegui e tieni aperto Esc chiudi",
|
||||
"search.commands": "Comandi",
|
||||
"search.design": "In questo progetto",
|
||||
@@ -3905,7 +3899,6 @@
|
||||
"sketch.dimension_applied": "Quota applicata",
|
||||
"sketch.dimension_applied_defined": "Quota applicata; parametro `{name}` definito",
|
||||
"sketch.dimension_applied_renamed": "Quota applicata; parametro denominato `{name}`",
|
||||
"sketch.divide_hint": "I punti dividono ogni curva in lunghezze uguali e le mantengono quando cambia.",
|
||||
"sketch.dof_left": "{count} gradi di libertà rimanenti",
|
||||
"sketch.dof_left_one": "{count} grado di libertà rimanente",
|
||||
"sketch.draw_curves": "Disegna come {linetype}",
|
||||
@@ -4695,7 +4688,6 @@
|
||||
"ui.heading_create_sketch": "CREA SCHIZZO",
|
||||
"ui.heading_cylinder": "CILINDRO",
|
||||
"ui.heading_design": "PROGETTAZIONE",
|
||||
"ui.heading_divide_curve": "DIVIDI CURVA",
|
||||
"ui.heading_draft": "SFORMO",
|
||||
"ui.heading_edit": "MODIFICA",
|
||||
"ui.heading_equation_curve": "CURVA DA EQUAZIONE",
|
||||
@@ -5034,7 +5026,6 @@
|
||||
"ui.second_rail": "Seconda guida",
|
||||
"ui.section_analysis": "Analisi in sezione",
|
||||
"ui.section_size": "Dimensione della sezione",
|
||||
"ui.segments": "Segmenti",
|
||||
"ui.select": "Seleziona",
|
||||
"ui.selectOther": "Seleziona altro…",
|
||||
"ui.selectTangentChain": "Seleziona catena tangente",
|
||||
|
||||
@@ -963,7 +963,8 @@
|
||||
"cmd.inspect_analysis": "ანალიზის ნახვა",
|
||||
"cmd.inspect_center_of_mass": "მასის ცენტრი",
|
||||
"cmd.inspect_clearance": "ღრეჩო",
|
||||
"cmd.inspect_colored_components": "კომპონენტების ფერები",
|
||||
"cmd.inspect_colored_components": "კომპონენტების ფერების ჩართვა",
|
||||
"cmd.inspect_colored_components_disable": "კომპონენტების ფერების გამორთვა",
|
||||
"cmd.inspect_compare": "სხეულების შედარება",
|
||||
"cmd.inspect_compare_picture": "სურათთან შედარება",
|
||||
"cmd.inspect_continuity": "უწყვეტობის ანალიზი",
|
||||
|
||||