Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5728f284b | |||
| 5965439a15 | |||
| 586a358ed4 | |||
| 7770f8982d | |||
| 72ad6383ef | |||
| 0537623a75 | |||
| 65d288227e | |||
| fedb3bcdb1 | |||
| 097b8b7550 | |||
| af7ca3a8a2 | |||
| 27ddb725c7 | |||
| a06bdf5cfc | |||
| 33fdcdb3a0 | |||
| c07ab5f8f9 | |||
| fea32f8aac | |||
| 4927214976 | |||
| b621819c0c | |||
| ec552a5300 | |||
| e0cc740cc3 | |||
| 5de48e916d | |||
| 80af7bbe3e | |||
| 191e983423 | |||
| 2b9445b049 | |||
| 7ddf48ca16 | |||
| d2f6848377 | |||
| 0b6d6fecd8 | |||
| fa691707c2 | |||
| b9a55c74e2 | |||
| 2bb9c81069 | |||
| cedb51b8ea | |||
| 66d13059dd | |||
| 800ea40c0d | |||
| 20442eecd3 | |||
| 22d1cd7032 | |||
| 6a0dc18655 | |||
| e006dbf1af | |||
| 8241a750fa | |||
| 41779cf7bb | |||
| b01b9689c8 | |||
| 4426da6f5d | |||
| 5ead82e627 | |||
| c29b0d2f13 | |||
| 480fd8d517 | |||
| 4c09fa5355 |
@@ -1,6 +1,3 @@
|
||||
# Preserve canonical source receipts and the runtime icon-discovery snapshot.
|
||||
.gitattributes text eol=lf
|
||||
/assets/icons/ui/design/requested_icons.txt text eol=lf
|
||||
# SolidCraft designs (.sc): a JSON document or a ZIP container, never Scala source, so they
|
||||
# stay out of GitHub's language statistics. Git's own text/binary detection handles both kinds.
|
||||
*.sc -linguist-detectable
|
||||
|
||||
@@ -19,7 +19,7 @@ body:
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: |
|
||||
What you clicked or typed, in order. For a problem on a particular design, `solidcraft-cli script design.sc --out repro.json` turns its history into a script that `solidcraft-cli eval repro.json` replays; attach it, trimmed to the failing step if you can.
|
||||
What you clicked or typed, in order. For a problem on a particular design, `solidcraft-cli script design.solidcraft --out repro.json` turns its history into a script that `solidcraft-cli eval repro.json` replays; attach it, trimmed to the failing step if you can.
|
||||
Using an AI agent with SolidCraft's MCP server? The `solidcraft-reporting` skill in [`skills/`](https://github.com/tangency-labs/solidcraft/tree/main/skills) has it reproduce the problem in a fresh design and write these steps for you.
|
||||
placeholder: |
|
||||
1. Sketch a circle on the XY plane
|
||||
|
||||
@@ -9,10 +9,9 @@ parameter to watch the timeline rebuild. [Documentation](https://docs.solidcraft
|
||||
[ROADMAP](https://github.com/bherbruck/solidcraft/blob/main/ROADMAP.md#known-limitations) lists
|
||||
the known limitations. Designs from every earlier version open in this one.
|
||||
|
||||
**New app ID.** The app ID is now `io.tangencylabs.solidcraft` (it was `ai.storyteller.solidcraft`,
|
||||
and `ai.storyteller.solvecraft` up to 0.7): `flatpak update` offers to replace the old app, the
|
||||
`.deb`, `.rpm` and AppImage remove the old menu entry, and settings stay. On macOS the bundle ID
|
||||
changed too, so macOS asks once more for the permissions it had granted.
|
||||
**Upgrading from 0.7 or earlier.** 0.8 changed the Linux app ID to `ai.storyteller.solidcraft`:
|
||||
`flatpak update` offers to replace the old app, and the `.deb`, `.rpm` and AppImage remove the old
|
||||
menu entry.
|
||||
|
||||
**Upgrading from 0.5 or earlier.** 0.6 renamed SolveCraft to SolidCraft and made the zip
|
||||
container the default save format. `.solvecraft` designs, old settings folders and
|
||||
@@ -43,16 +42,16 @@ installing SolidCraft; keep both remotes in the same user installation:
|
||||
```sh
|
||||
flatpak remote-add --user --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
|
||||
flatpak remote-add --user --if-not-exists solidcraft https://docs.solidcraft.io/flatpak/solidcraft.flatpakrepo
|
||||
flatpak install --user solidcraft io.tangencylabs.solidcraft
|
||||
flatpak run io.tangencylabs.solidcraft
|
||||
flatpak install --user solidcraft ai.storyteller.solidcraft
|
||||
flatpak run ai.storyteller.solidcraft
|
||||
```
|
||||
|
||||
A remote added from the old `bherbruck.github.io` address no longer finds updates: run
|
||||
`flatpak remote-modify --user --url=https://docs.solidcraft.io/flatpak/repo/ solidcraft`, then
|
||||
`flatpak update --user`.
|
||||
|
||||
Flatpak installs under an old ID (`ai.storyteller.solidcraft`, or `ai.storyteller.solvecraft` from 0.7 or
|
||||
earlier): `flatpak update --user` offers to replace it with `io.tangencylabs.solidcraft`, and settings move along.
|
||||
Flatpak installs from 0.7 or earlier use the old ID `ai.storyteller.solvecraft`: `flatpak update --user`
|
||||
offers to replace it with `ai.storyteller.solidcraft`, and settings move along.
|
||||
|
||||
Single-file `.flatpak` bundles include the runtime repository address; missing runtimes
|
||||
still need an internet connection. [Linux installation details](https://docs.solidcraft.io/guide/getting-started.html#linux-flatpak).
|
||||
|
||||
@@ -98,16 +98,15 @@ jobs:
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
permissions:
|
||||
contents: read
|
||||
# To ask whether the merge queue already tested a commit pushed to main, and which labels the
|
||||
# pull requests of a merge group carry (the render-tests opt-in).
|
||||
pull-requests: read
|
||||
# To ask whether the merge queue already tested a commit pushed to main.
|
||||
actions: read
|
||||
outputs:
|
||||
native: ${{ steps.paths.outputs.native }}
|
||||
kernel: ${{ steps.paths.outputs.kernel }}
|
||||
goldens: ${{ steps.paths.outputs.goldens }}
|
||||
windows: ${{ steps.paths.outputs.windows }}
|
||||
# The GPU path tests run (see the rule where it is computed). The job's `if` and the `ci` job's check both read this, so they cannot disagree.
|
||||
# The GPU path tests run: native, and the whole workspace or solidcraft-gpu selected. The
|
||||
# job's `if` and the `ci` job's check both read this, so they cannot disagree.
|
||||
gpu: ${{ steps.paths.outputs.gpu }}
|
||||
full: ${{ steps.paths.outputs.full }}
|
||||
# Code changed on a pull request that has no approved-for-ci label yet: only Quick compile.
|
||||
@@ -132,7 +131,6 @@ jobs:
|
||||
GROUP_HEAD_SHA: ${{ github.event.merge_group.head_sha }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
HEAD_REPO: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
RENDER_LABEL: ${{ contains(github.event.pull_request.labels.*.name, 'render-tests') }}
|
||||
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
||||
REPO: ${{ github.repository }}
|
||||
APPROVED: ${{ contains(github.event.pull_request.labels.*.name, 'approved-for-ci') }}
|
||||
@@ -142,7 +140,6 @@ jobs:
|
||||
kernel=false
|
||||
goldens=false
|
||||
windows=false
|
||||
gpu_paths=false
|
||||
full=true
|
||||
packages=
|
||||
# Whether the NUL-separated paths in $1 reach any code: scripts/ci/affected.py selects
|
||||
@@ -171,29 +168,6 @@ jobs:
|
||||
windows=true
|
||||
fi
|
||||
}
|
||||
# Whether the NUL-separated paths in $1 are render code: the render crate, the path
|
||||
# tracer and its shaders in the GPU crate, the render workspace UI, the render assets.
|
||||
# Core, kernel and CI changes do not trigger the path tests (about 56 runner-minutes):
|
||||
# the tracer reads the scene through interfaces the other tests cover, and the nightly
|
||||
# runs them for everything.
|
||||
gpu_paths() {
|
||||
while IFS= read -r -d '' path; do
|
||||
case "$path" in
|
||||
crates/render/*|crates/gpu/src/path/*|crates/ui-egui/src/render_workspace*|assets/render/*) gpu_paths=true ;;
|
||||
esac
|
||||
done < "$1"
|
||||
}
|
||||
# Opt-in for a big render change: a pull request labelled render-tests, or a merge group
|
||||
# holding one (its pull requests are the (#N) of its squash commits), runs them whatever
|
||||
# the paths say.
|
||||
group_has_render_label() {
|
||||
for pr in $(git log --format=%s "$1" | grep -o '(#[0-9]*)$' | tr -d '(#)'); do
|
||||
if gh api "repos/$REPO/issues/$pr" --jq '.labels[].name' | grep -qx render-tests; then
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
return 1
|
||||
}
|
||||
if [ "$EVENT_NAME" = push ]; then
|
||||
# The queue lands the very commit its merge_group run tested. If that run passed,
|
||||
# testing the push again proves nothing new; otherwise (a merge that skipped the
|
||||
@@ -227,20 +201,14 @@ jobs:
|
||||
# the nightly runs it whatever changed. A group that can't be compared runs it.
|
||||
if git diff --no-renames --name-only -z "$GROUP_BASE_SHA...$GROUP_HEAD_SHA" > "$RUNNER_TEMP/ci-paths"; then
|
||||
windows_paths "$RUNNER_TEMP/ci-paths" "$GROUP_BASE_SHA...$GROUP_HEAD_SHA"
|
||||
gpu_paths "$RUNNER_TEMP/ci-paths"
|
||||
if [ "$gpu_paths" = false ] && group_has_render_label "$GROUP_BASE_SHA..$GROUP_HEAD_SHA"; then
|
||||
gpu_paths=true
|
||||
fi
|
||||
else
|
||||
windows=true
|
||||
gpu_paths=true
|
||||
fi
|
||||
echo "windows=$windows gpu_paths=$gpu_paths for $GROUP_BASE_SHA...$GROUP_HEAD_SHA"
|
||||
echo "windows=$windows for $GROUP_BASE_SHA...$GROUP_HEAD_SHA"
|
||||
elif [ "$EVENT_NAME" != pull_request ]; then
|
||||
# The nightly run and Run workflow: everything, Windows included.
|
||||
native=true
|
||||
windows=true
|
||||
gpu_paths=true
|
||||
else
|
||||
# Compare both old and new paths of renames; unknown paths run native checks.
|
||||
git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/ci-paths"
|
||||
@@ -300,24 +268,12 @@ jobs:
|
||||
goldens=false
|
||||
fi
|
||||
fi
|
||||
# The GPU path tests (their own job, about 56 runner-minutes) are for render changes only:
|
||||
# - on a pull request, when it carries the render-tests label (opt-in for a big render
|
||||
# change) or touches render code (gpu_paths); no other pull request runs them;
|
||||
# - in the merge queue, when the group's diff touches render code or one of its pull
|
||||
# requests carries render-tests;
|
||||
# - in the nightly and by hand, always, so anything the above skipped is caught within a day;
|
||||
# - not on a push to main: the queue tested that commit, or the nightly will.
|
||||
# The GPU path tests (their own job) run with the other tests, when solidcraft-gpu is
|
||||
# among the packages they test.
|
||||
gpu=false
|
||||
if [ "$native" = true ]; then
|
||||
case "$EVENT_NAME" in
|
||||
pull_request)
|
||||
gpu_paths "$RUNNER_TEMP/ci-paths"
|
||||
gpu=$gpu_paths
|
||||
if [ "$RENDER_LABEL" = true ]; then gpu=true; fi ;;
|
||||
merge_group) gpu=$gpu_paths ;;
|
||||
push) gpu=false ;;
|
||||
*) gpu=true ;;
|
||||
esac
|
||||
case " $packages " in *" solidcraft-gpu "*) gpu=true ;; esac
|
||||
if [ "$full" = true ]; then gpu=true; fi
|
||||
fi
|
||||
{
|
||||
echo "native=$native"
|
||||
|
||||
@@ -55,15 +55,13 @@ jobs:
|
||||
- name: XML, desktop entry, AppStream, Flatpak manifest
|
||||
run: |
|
||||
xmllint --noout assets/platforms/windows/solidcraft.wxs assets/platforms/windows/solidcraft.en-us.wxl assets/platforms/macos/*.plist* assets/platforms/linux/*.xml*
|
||||
desktop-file-validate assets/platforms/linux/io.tangencylabs.solidcraft.desktop
|
||||
# The window's X11 class is its app id; the old ids stay only as the rename shims.
|
||||
grep -qx 'StartupWMClass=io.tangencylabs.solidcraft' assets/platforms/linux/io.tangencylabs.solidcraft.desktop
|
||||
grep -q '<id>ai.storyteller.solidcraft</id>' assets/platforms/linux/io.tangencylabs.solidcraft.metainfo.xml.in
|
||||
grep -q '<id>ai.storyteller.solvecraft</id>' assets/platforms/linux/io.tangencylabs.solidcraft.metainfo.xml.in
|
||||
grep -qx 'X-Flatpak-RenamedFrom=ai.storyteller.solidcraft.desktop;ai.storyteller.solvecraft.desktop;' assets/platforms/linux/io.tangencylabs.solidcraft.desktop
|
||||
desktop-file-validate assets/platforms/linux/ai.storyteller.solidcraft.desktop
|
||||
# The window's X11 class is its app id; the old id stays only as the rename shims.
|
||||
grep -qx 'StartupWMClass=ai.storyteller.solidcraft' assets/platforms/linux/ai.storyteller.solidcraft.desktop
|
||||
grep -q '<id>ai.storyteller.solvecraft</id>' assets/platforms/linux/ai.storyteller.solidcraft.metainfo.xml.in
|
||||
grep -qF -e '--end-of-life-rebase=' packaging/flatpak-site.sh
|
||||
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' assets/platforms/linux/io.tangencylabs.solidcraft.metainfo.xml.in >"$RUNNER_TEMP/io.tangencylabs.solidcraft.metainfo.xml"
|
||||
appstreamcli validate --no-net --explain "$RUNNER_TEMP/io.tangencylabs.solidcraft.metainfo.xml"
|
||||
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/io.tangencylabs.solidcraft.bundle.yml')); assert m['id'] == 'io.tangencylabs.solidcraft' and m['command'] == 'solidcraft'"
|
||||
sed -e 's/@VERSION@/0.0.0/' -e 's/@DATE@/2026-01-01/' assets/platforms/linux/ai.storyteller.solidcraft.metainfo.xml.in >"$RUNNER_TEMP/ai.storyteller.solidcraft.metainfo.xml"
|
||||
appstreamcli validate --no-net --explain "$RUNNER_TEMP/ai.storyteller.solidcraft.metainfo.xml"
|
||||
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/flatpak/ai.storyteller.solidcraft.bundle.yml')); assert m['id'] == 'ai.storyteller.solidcraft' and m['command'] == 'solidcraft'"
|
||||
python3 -c "import yaml; m = yaml.safe_load(open('packaging/linux/snap/snapcraft.yaml')); assert m['name'] == 'solidcraft' and m['confinement'] == 'strict' and m['apps']['solidcraft']['command'] == 'usr/bin/solidcraft'"
|
||||
python3 -m py_compile packaging/linux/snap-smoke.py
|
||||
|
||||
@@ -297,7 +297,7 @@ jobs:
|
||||
export CARGO_TARGET_DIR=/var/tmp/solidcraft-target CARGO_BUILD_JOBS=4
|
||||
bash packaging/freebsd/package.sh
|
||||
tar -tzf dist/release/solidcraft-*-freebsd-x86_64.tar.gz \
|
||||
| grep -E '/bin/solidcraft$|/bin/solidcraft-cli$|/share/applications/io\.tangencylabs\.solidcraft\.desktop$|/share/doc/solidcraft/THIRD-PARTY-LICENSES\.txt$'
|
||||
| grep -E '/bin/solidcraft$|/bin/solidcraft-cli$|/share/applications/ai\.storyteller\.solvecraft\.desktop$|/share/doc/solidcraft/THIRD-PARTY-LICENSES\.txt$'
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: release-freebsd-x86_64
|
||||
|
||||
@@ -45,3 +45,4 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
|
||||
| A bug report, a crash, a wrong result | `skills/solidcraft-reporting/SKILL.md` |
|
||||
| Checking, porting, designs in git | `skills/solidcraft-checking/SKILL.md` |
|
||||
| Assemblies, joints, motion | `skills/solidcraft-mechanisms/SKILL.md` |
|
||||
>>>>>>> 4a3e0772a (Agents: AGENTS.md becomes a router to focused skills, with a staleness check (Refs #6439))
|
||||
|
||||
@@ -128,14 +128,14 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| 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 |
|
||||
| assets/icons/app/linux/exports/hicolor/256x256/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/32x32/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/48x48/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/512x512/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/64x64/apps/io.tangencylabs.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/128x128/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/16x16/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/24x24/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/256x256/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/32x32/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/48x48/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/512x512/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/linux/exports/hicolor/64x64/apps/ai.storyteller.solidcraft.png | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/shared/source/app_icon.rs | SolidCraft contributors | Original SolidCraft drawing code: the borderless mark, drawn from the faces and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/shared/source/windows_ico.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/icons/app/windows/exports/solidcraft-executable.ico | SolidCraft contributors | Generated from assets/icons/app/shared/source/app_icon.rs with the original CLI/ICO encoders | MIT OR Apache-2.0 |
|
||||
@@ -171,9 +171,9 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/locales/zh.json | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/locales/zh-TW.json | SolidCraft contributors | Original Traditional Chinese (Taiwan) translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
| assets/materials/library.rs | SolidCraft contributors | Original SolidCraft resource data or drawing code, collected without redesign | MIT OR Apache-2.0 |
|
||||
| assets/platforms/linux/io.tangencylabs.solidcraft.desktop | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
|
||||
| assets/platforms/linux/io.tangencylabs.solidcraft.metainfo.xml.in | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
|
||||
| assets/platforms/linux/io.tangencylabs.solidcraft.mime.xml | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
|
||||
| assets/platforms/linux/ai.storyteller.solidcraft.desktop | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
|
||||
| assets/platforms/linux/ai.storyteller.solidcraft.metainfo.xml.in | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
|
||||
| assets/platforms/linux/ai.storyteller.solidcraft.mime.xml | PhotoCraft / SolidCraft contributors | PhotoCraft packaging templates (https://github.com/storytold/photocraft), adapted for SolidCraft | Apache-2.0 |
|
||||
| assets/platforms/macos/dmg/background.tiff | SolidCraft contributors | original disk image window background (blueprint sheet: grid, double border, circles, centre lines, leader arrow, title block) drawn by assets/platforms/macos/dmg/make_background.py in IBM Plex Sans/Mono; 1x and 2x representations | MIT OR Apache-2.0 |
|
||||
| assets/platforms/macos/dmg/make_background.py | SolidCraft contributors | authoring script that draws the disk image background (Pillow); not part of any build | MIT OR Apache-2.0 |
|
||||
| assets/platforms/macos/dmg/layout.json | SolidCraft contributors | disk image window size, icon size and icon positions shared by the background script and settings.py | MIT OR Apache-2.0 |
|
||||
@@ -429,20 +429,6 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
|
||||
| assets/icons/ui/design/svg/model/status_recovery.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/frame_member.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_environment.svg | SolidCraft contributors | original Studio icon master (64 grid): dome with a grey cube inside | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/style_shaded.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/style_wireframe.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/style_hidden_edges.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/style_translucent.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/select_faces_edges.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/select_bodies.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/select_features.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/select_parts.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/select_components.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/select_sketch.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/display.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/grid.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/ground_shadow.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/ambient_occlusion.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/placeholder.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/plane.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/plane_angle.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
@@ -592,11 +578,6 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
|
||||
| assets/icons/ui/design/svg/model/cone.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/locales/ar.json | Abdullah (A-AL-ANAZI) | Original Arabic translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
| assets/locales/he.json | SolidCraft contributors | Original Hebrew translation of SolidCraft's English catalog; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/cga.json | SolidCraft contributors | original SolidCraft theme file using the 16 colours of the IBM Color Graphics Adapter (black, cyan, magenta, and white of its high-intensity palette 1 for the interface) | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/1-bit.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/1-bit-dark.json | SolidCraft contributors | original SolidCraft theme file | MIT OR Apache-2.0 |
|
||||
| `assets/locales/pending/navbar-customisation.json` | SolidCraft contributors | Original navigation bar editor translations | MIT OR Apache-2.0 |
|
||||
| `assets/locales/pending/navbar-slots.json` | SolidCraft contributors | Original navigation slot translations | MIT OR Apache-2.0 |
|
||||
|
||||
## Code adapted from sibling projects
|
||||
|
||||
|
||||
@@ -136,7 +136,6 @@ The [architecture page](docs/architecture.md) has the layer table. The short ver
|
||||
[Adding a command](docs/book/src/dev/commands.md#adding-a-command); a test warns about a
|
||||
command without them, and will fail it once required), engine tests that measure the result (volume, area,
|
||||
face/edge/vertex counts against a value you can derive), and for UI work a scenario (below).
|
||||
A command on the toolbar also ships with its help tooltip (step 6 of Adding a command).
|
||||
6. **One set of camera controls.** Every page or canvas that shows a view (the modelling
|
||||
viewport, the Render preview, and any new page such as a flat pattern or drawing) calls
|
||||
`navigation::handle` (`crates/ui-egui/src/navigation.rs`) for its camera input, with
|
||||
@@ -284,12 +283,6 @@ and a fresh CI run:
|
||||
- **Commands without schemas** (`crates/engine/src/schema/unconverted.txt`): sorted, one id per
|
||||
line, and it only shrinks. Converting a command removes its line; a new command is never added
|
||||
to it.
|
||||
- **Toolbar commands without help** (`BACKLOG` in `crates/ui-egui/src/command_help.rs`): sorted,
|
||||
one id per line, and it only shrinks. Writing a command's help removes its line; a new command
|
||||
is never added to it.
|
||||
- **`ui.prefs` settings** (`docs/control-protocol.md`): one line per setting, sorted by name, in
|
||||
the list under the protocol table; add a new setting on its own line rather than editing a shared
|
||||
row.
|
||||
- **Languages** (`crates/ui-egui/src/i18n.rs`): one per line, English first, then by id (a test
|
||||
checks it).
|
||||
- **Generated files**: `THIRD-PARTY-LICENSES.txt` and `docs/features.md` are rewritten on `main`
|
||||
|
||||
@@ -4208,7 +4208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"eframe",
|
||||
@@ -4242,11 +4242,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-3dx-client"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-cli"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4258,7 +4258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-derive"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4267,7 +4267,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-doc"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -4284,7 +4284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-engine"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4305,7 +4305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-geom"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4314,7 +4314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-gpu"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4334,7 +4334,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-io"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"cadmpeg-ir",
|
||||
@@ -4358,7 +4358,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-kernel"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"proptest",
|
||||
@@ -4378,7 +4378,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-mcp"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
@@ -4387,7 +4387,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-render"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"moxcms",
|
||||
@@ -4401,7 +4401,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-sketch"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4412,14 +4412,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-space-mouse"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-egui"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"egui",
|
||||
@@ -4449,7 +4449,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-web"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -5994,7 +5994,7 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"serde",
|
||||
|
||||
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
exclude = ["vendor"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.25.1"
|
||||
version = "0.24.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
# Disclaimer
|
||||
|
||||
This disclaimer covers the software, models, data, documentation and every other file in this
|
||||
repository and in the releases built from it (together, "the files").
|
||||
|
||||
## No warranty
|
||||
|
||||
The files are provided "as is", without warranty of any kind, express or implied, including
|
||||
warranties of accuracy, merchantability, fitness for a particular purpose and non-infringement.
|
||||
Results produced with the files, such as geometry, meshes, analyses and simulations, can be
|
||||
wrong. A qualified person must check them before anyone relies on them for a design,
|
||||
manufacturing or safety decision.
|
||||
|
||||
## No liability
|
||||
|
||||
To the fullest extent permitted by law, Tangency Labs and the contributors are not liable for any
|
||||
claim, damage, loss or other liability arising from the files or from their use, modification or
|
||||
distribution, whether in contract, tort or otherwise.
|
||||
|
||||
## No indemnity
|
||||
|
||||
Neither Tangency Labs nor any contributor indemnifies, defends or holds harmless anyone who uses,
|
||||
modifies or distributes the files, including against claims that the files infringe someone
|
||||
else's rights.
|
||||
|
||||
## Harmful or unlawful use
|
||||
|
||||
Tangency Labs and the contributors do not authorise, endorse or accept any responsibility for use
|
||||
of the files to harm people, animals, property or the environment, or for any unlawful use.
|
||||
Whoever uses the files is solely responsible for how they use them, for what they make with them
|
||||
and for complying with the laws that apply to them. Tangency Labs and the contributors must not be
|
||||
named or held out as responsible for any such use.
|
||||
|
||||
## No endorsement
|
||||
|
||||
Using, modifying or distributing the files does not mean that Tangency Labs or any contributor
|
||||
endorses you, your product or your results. Do not use the name of Tangency Labs or of a
|
||||
contributor to suggest such an endorsement without written permission.
|
||||
|
||||
## Relation to the licence
|
||||
|
||||
This disclaimer adds to the warranty and liability terms of the licence the files are distributed
|
||||
under. It does not restrict any right that licence grants; where the two differ, the licence
|
||||
governs.
|
||||
@@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="assets/icons/app/linux/exports/hicolor/256x256/apps/io.tangencylabs.solidcraft.png" width="96" height="96" alt="SolidCraft" />
|
||||
<img src="assets/icons/app/linux/exports/hicolor/256x256/apps/ai.storyteller.solidcraft.png" width="96" height="96" alt="SolidCraft" />
|
||||
</p>
|
||||
|
||||
<h1 align="center">SolidCraft</h1>
|
||||
@@ -74,7 +74,7 @@ other is a tracked bug.
|
||||
- **Parameters and configurations** with unit-aware expressions.
|
||||
- **Assemblies** with joints, motion studies, contact sets, interference and mass properties.
|
||||
- **Sheet metal** with flanges, hems, fold/unfold and flat patterns, and plastic enclosure features.
|
||||
- **Files:** STEP, IGES, 3MF, STL, OBJ and glTF in and out where supported, URDF export, `.sc` designs (`.solidcraft` files from earlier releases still open).
|
||||
- **Files:** STEP, IGES, 3MF, STL, OBJ and glTF in and out where supported, URDF export, `.solidcraft` designs.
|
||||
- **Desktop app, browser app, headless CLI and MCP server**, all running the same commands.
|
||||
|
||||
The full list is in [docs/capabilities.md](docs/capabilities.md) and [docs/features.md](docs/features.md);
|
||||
@@ -121,18 +121,11 @@ the clean-room and licence rules, and where help is wanted most. Open work is in
|
||||
SolidCraft's continuous integration runs on [Blacksmith](https://www.blacksmith.sh), which
|
||||
sponsors its CI minutes.
|
||||
|
||||
## Disclaimer
|
||||
|
||||
The files in this repository are provided as is, without warranty, liability or indemnity, and
|
||||
Tangency Labs and the contributors accept no responsibility for any harmful or unlawful use of
|
||||
them: read [DISCLAIMER.md](DISCLAIMER.md).
|
||||
|
||||
## Licence
|
||||
|
||||
MIT OR Apache-2.0, at your option. Developed under the
|
||||
[clean-room contribution rules](AGENTS.md); third-party code and assets retain their
|
||||
documented licences and attribution. The licence covers the code, not the name and logo: see
|
||||
[TRADEMARKS.md](TRADEMARKS.md) for how to name a fork or a product built on SolidCraft.
|
||||
documented licences and attribution.
|
||||
|
||||
SolidCraft is independent. Behaviour is designed from public documentation, general CAD
|
||||
conventions and user feedback. Reference tests use analytic values and open-source tools.
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
# SolidCraft name and logo
|
||||
|
||||
The code is yours to use under MIT OR Apache-2.0 (see [NOTICE](NOTICE)). The name is not part of
|
||||
that licence: "SolidCraft" and the SolidCraft logo identify this project and the builds its
|
||||
maintainers publish, and Tangency Labs, which maintains SolidCraft, uses them as its trademarks.
|
||||
Neither licence grants rights to them.
|
||||
|
||||
This page says how you can use the name. It is meant to be easy to follow: most uses are fine.
|
||||
|
||||
## Fine without asking
|
||||
|
||||
- Saying what your work is built on: "based on SolidCraft", "a fork of SolidCraft", "works with
|
||||
SolidCraft", "SolidCraft plugin for …".
|
||||
- Writing, teaching, reviewing or making videos about SolidCraft, including in titles.
|
||||
- Redistributing unmodified official builds under the SolidCraft name, for example in a Linux
|
||||
distribution or package manager.
|
||||
- Using the logo to link to the project or to show that something supports SolidCraft.
|
||||
- User groups, meetups and community spaces about SolidCraft, as long as they don't look official.
|
||||
|
||||
## Use your own name
|
||||
|
||||
- A fork, a modified build or a product built on SolidCraft needs its own name and logo. "SolidCraft"
|
||||
may appear in its description ("based on SolidCraft"), not as its name or the main part of it.
|
||||
- Don't use names or logos that could be mistaken for SolidCraft, such as "SolidCraft Pro",
|
||||
"SolidKraft" or a lightly changed version of the logo.
|
||||
- Don't imply that SolidCraft's maintainers made, endorse or support your product or service unless
|
||||
they do.
|
||||
- Don't use the name in domain names, app store listings or social media accounts in a way that
|
||||
looks official.
|
||||
|
||||
## Questions
|
||||
|
||||
If you're not sure whether a use is fine, open an issue or ask on [Discord](https://discord.gg/FtU4NQNVDH).
|
||||
For anything this page doesn't allow, ask first; we're generally happy to say yes.
|
||||
|
||||
Other companies' trademarks named in this repository are covered in [NOTICE](NOTICE).
|
||||
@@ -68,7 +68,7 @@ fn run() -> Result<(), String> {
|
||||
let generation_start = Instant::now();
|
||||
let (commands, command_timings) = session.run_script_timed(&recipe, false).map_err(|e| e.to_string())?;
|
||||
let generation_seconds = generation_start.elapsed().as_secs_f64();
|
||||
let design_path = output.with_extension(solidcraft_engine::io::DESIGN_EXT);
|
||||
let design_path = output.with_extension("solidcraft");
|
||||
session.execute("file.save_as", &json!({"path":design_path})).map_err(|e| e.to_string())?;
|
||||
let scene = solidcraft_engine::product_scene::extract(&session).map_err(|e| e.to_string())?;
|
||||
let bodies: Vec<Value> = scene
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
//! no fixture.
|
||||
//!
|
||||
//! ```text
|
||||
//! solidcraft-cli bench-agent [--scale F] [--script-out script.json] [--save design.sc]
|
||||
//! solidcraft-cli bench-agent [--scale F] [--script-out script.json] [--save design.solidcraft]
|
||||
//! ```
|
||||
//! prints one JSON line per hundred commands (mean and max milliseconds, bodies so far), then the
|
||||
//! build total, then the phases `bench-open` prints for the saved design.
|
||||
@@ -205,10 +205,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
);
|
||||
let path = match flag("--save") {
|
||||
Some(p) => p.clone(),
|
||||
None => std::env::temp_dir()
|
||||
.join(format!("bench-agent-{}.{}", std::process::id(), solidcraft_engine::io::DESIGN_EXT))
|
||||
.to_string_lossy()
|
||||
.into_owned(),
|
||||
None => std::env::temp_dir().join(format!("bench-agent-{}.solidcraft", std::process::id())).to_string_lossy().into_owned(),
|
||||
};
|
||||
s.execute("file.save", &json!({"path": path})).map_err(|e| e.to_string())?;
|
||||
let r = crate::cmd_bench_open(std::slice::from_ref(&path));
|
||||
|
||||
@@ -419,7 +419,7 @@ fn wait_for_turn(control: &mut Control, mut chat: Value, began: Instant, end: In
|
||||
let idle = !matches!(state, "busy" | "starting") && !turn && !queued;
|
||||
// A turn can start and end between two polls: then the conversation has its answer.
|
||||
let answered = chat["items"].as_array().into_iter().flatten().any(|i| i.get("user").is_none());
|
||||
if idle && (seen_busy || answered || matches!(state, "exited" | "failed" | "auth" | "off") || began.elapsed() >= START_GRACE) {
|
||||
if idle && (seen_busy || answered || matches!(state, "exited" | "auth" | "off") || began.elapsed() >= START_GRACE) {
|
||||
return Ok((chat, false));
|
||||
}
|
||||
let Some(left) = end.checked_duration_since(Instant::now()).filter(|d| !d.is_zero()) else { return Ok((chat, true)) };
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
//! SolidCraft headless command line.
|
||||
//!
|
||||
//! ```text
|
||||
//! solidcraft-cli run <script.json|design.sc|part.step|mesh.3mf> [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]
|
||||
//! solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] (alias: inspect) measurements as JSON
|
||||
//! solidcraft-cli script <design.sc> [--out script.json] the design's history as a command script
|
||||
//! solidcraft-cli bench-open <design.sc | part.step> [--parse-only] [--save OUT.sc] [--display LEVEL [--edit NAME EXPR]] [--ops script.json] loading timings as JSON lines
|
||||
//! solidcraft-cli run <script.json|design.solidcraft|part.step|mesh.3mf> [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]
|
||||
//! solidcraft-cli eval <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--json-out FILE] (alias: inspect) measurements as JSON
|
||||
//! solidcraft-cli script <design.solidcraft> [--out script.json] the design's history as a command script
|
||||
//! solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json] loading timings as JSON lines
|
||||
//! solidcraft-cli bench-agent [--scale F] [--save F] [--script-out F] a ~1,300-body design built command by command, timed
|
||||
//! solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N]
|
||||
//! [--timeout-s 300] [--json out.json] [--markdown out.md]
|
||||
@@ -56,19 +56,19 @@ 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] [--json-out FILE] [--allow-errors]\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 (--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";
|
||||
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]\n solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n";
|
||||
/// `eval`'s lines in the usage.
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] (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";
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--json-out FILE] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.solidcraft|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n";
|
||||
/// `script`'s lines in the usage.
|
||||
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.sc|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
|
||||
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.solidcraft|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
|
||||
/// `bench-open`'s lines in the usage.
|
||||
const USAGE_BENCH_OPEN: &str = " solidcraft-cli bench-open <design.sc | part.step> [--parse-only] [--save OUT.sc] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]\n (--ops: after opening, run each command of the script and undo it, timing the command, the\n evaluations it caused and the view it leaves: placing and meshing what changed)\n";
|
||||
const USAGE_BENCH_OPEN: &str = " solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]\n (--ops: after opening, run each command of the script and undo it, timing the command, the\n evaluations it caused and the view it leaves: placing and meshing what changed)\n";
|
||||
/// `bench-agent`'s lines in the usage.
|
||||
const USAGE_BENCH_AGENT: &str = " solidcraft-cli bench-agent [--scale F] [--save design.sc] [--script-out script.json]\n";
|
||||
const USAGE_BENCH_AGENT: &str = " solidcraft-cli bench-agent [--scale F] [--save design.solidcraft] [--script-out script.json]\n";
|
||||
/// `bench-assistant`'s lines in the usage.
|
||||
const USAGE_BENCH_ASSISTANT: &str = " solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N] [--timeout-s 300] [--json out.json] [--markdown out.md]\n";
|
||||
/// `snapshot`'s lines in the usage.
|
||||
const USAGE_SNAPSHOT: &str = " solidcraft-cli snapshot <script|design> [--in design.sc|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]\n solidcraft-cli snapshot --in <design.sc|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)\n";
|
||||
const USAGE_SNAPSHOT: &str = " solidcraft-cli snapshot <script|design> [--in design.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]\n solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)\n";
|
||||
/// `render`'s lines in the usage.
|
||||
const USAGE_RENDER: &str = " solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]\n [--progressive] [--samples N] [--max-depth N] [--seed N] [--exposure EV] [--lighting FILE.json] [--environment FILE.json] [--tone-map reinhard|aces_fitted]\n";
|
||||
/// `exec`'s lines in the usage.
|
||||
@@ -83,7 +83,7 @@ const USAGE_ORACLE: &str = " solidcraft-cli oracle <case-dir>... [--json]\n";
|
||||
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]\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.solidcraft|script.json] [--connect 127.0.0.1:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]\n [--param-check off|warn|strict] [--render-gpu]\n solidcraft-cli mcp setup [--dry-run] [--client opencode|claude-desktop|claude-code|cursor] [--live]\n (adds a `solidcraft` server, with this program's path, to each installed AI client; --live adds `solidcraft-live` for the running app; alias: mcp install)\n";
|
||||
/// `crashes`'s lines in the usage.
|
||||
const USAGE_CRASHES: &str = " solidcraft-cli crashes [list [--json] | show <id> | replay <id> | dir | clear [<id>]]\n";
|
||||
/// `licences`'s lines in the usage.
|
||||
@@ -817,7 +817,7 @@ fn bench_ops(mut s: Session, script: &str) -> Result<Session, String> {
|
||||
|
||||
/// Separate file reading, JSON decoding/repair, timeline evaluation and display meshing.
|
||||
pub(crate) fn cmd_bench_open(args: &[String]) -> Result<(), String> {
|
||||
let path = args.first().ok_or("usage: solidcraft-cli bench-open <design.sc | part.step> [--parse-only]")?;
|
||||
let path = args.first().ok_or("usage: solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only]")?;
|
||||
let t = std::time::Instant::now();
|
||||
let step = solidcraft_engine::io::is_step_path(path);
|
||||
let limit = if step { solidcraft_engine::io::MAX_STEP_BYTES } else { solidcraft_engine::io::MAX_DESIGN_BYTES };
|
||||
@@ -863,13 +863,13 @@ pub(crate) fn cmd_bench_open(args: &[String]) -> Result<(), String> {
|
||||
json!({"step": "mesh", "ms": t.elapsed().as_secs_f64() * 1000.0, "place_ms": place_ms, "bodies": st.bodies.len(), "triangles": triangles})
|
||||
);
|
||||
let flag = |name: &str| args.iter().position(|a| a == name);
|
||||
// `--save PATH`: save the opened design without its model cache (to PATH.plain.sc) and
|
||||
// `--save PATH`: save the opened design without its model cache (to PATH.plain.solidcraft) and
|
||||
// as the app does, with it, then read the cache back.
|
||||
let s = match flag("--save").and_then(|i| args.get(i + 1)) {
|
||||
Some(out) => {
|
||||
let mut s = s;
|
||||
// Without the cache first, for comparison.
|
||||
let plain = format!("{out}.plain.{}", solidcraft_engine::io::DESIGN_EXT);
|
||||
let plain = format!("{out}.plain.solidcraft");
|
||||
let t = std::time::Instant::now();
|
||||
s.execute("file.save_as", &json!({"path": plain, "cache": false})).map_err(|e| e.to_string())?;
|
||||
let plain_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
@@ -92,7 +92,7 @@ fn designs() -> Vec<(String, PathBuf)> {
|
||||
add("regressions", root.join("examples/regressions"), "json");
|
||||
add("demo", root.join("examples/demo"), "json");
|
||||
add("typical", root.join("tests/action_matrix/designs"), "json");
|
||||
add("demo", root.join("tests/action_matrix/designs"), solidcraft_engine::io::DESIGN_EXT);
|
||||
add("demo", root.join("tests/action_matrix/designs"), "solidcraft");
|
||||
let mut cases: Vec<PathBuf> =
|
||||
std::fs::read_dir(root.join("tests/oracle")).unwrap().flatten().map(|e| e.path()).filter(|p| p.join("recipe.json").is_file()).collect();
|
||||
cases.sort();
|
||||
@@ -105,7 +105,7 @@ fn designs() -> Vec<(String, PathBuf)> {
|
||||
/// Build a design from its file.
|
||||
fn load(path: &Path) -> Result<Session, String> {
|
||||
let mut s = Session::default();
|
||||
if solidcraft_engine::io::is_design_container_path(path) {
|
||||
if path.extension().is_some_and(|x| x == "solidcraft") {
|
||||
s.execute("doc.open", &json!({"path": path.to_string_lossy()})).map_err(|e| e.to_string())?;
|
||||
return Ok(s);
|
||||
}
|
||||
@@ -1558,7 +1558,7 @@ fn sketches_on_loft_and_sweep_faces() {
|
||||
#[test]
|
||||
fn press_pull_on_the_pi_case_floor() {
|
||||
reproduces(
|
||||
Some("tests/action_matrix/designs/pi-case.sc"),
|
||||
Some("tests/action_matrix/designs/pi-case.solidcraft"),
|
||||
json!([{"command": "solid.press_pull", "params": {"face": [30, 20, 0], "distance": 1}}]),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -75,15 +75,12 @@ fn designs() -> Vec<(String, Session)> {
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.map(|e| e.path())
|
||||
.filter(|p| {
|
||||
(solidcraft_engine::io::is_design_container_path(p) || p.extension().is_some_and(|x| x == "json"))
|
||||
&& !p.to_string_lossy().ends_with(".expected.json")
|
||||
})
|
||||
.filter(|p| p.extension().is_some_and(|x| x == "solidcraft" || x == "json") && !p.to_string_lossy().ends_with(".expected.json"))
|
||||
.collect();
|
||||
files.sort();
|
||||
for p in files {
|
||||
let name = p.file_stem().unwrap_or_default().to_string_lossy().to_string();
|
||||
let s = if solidcraft_engine::io::is_design_container_path(&p) {
|
||||
let s = if p.extension().is_some_and(|x| x == "solidcraft") {
|
||||
open(&p)
|
||||
} else {
|
||||
let mut s = Session::default();
|
||||
@@ -153,7 +150,7 @@ fn pi_case_faces_per_step_at_both_heights() {
|
||||
const FACES: [(usize, usize); 13] =
|
||||
[(2, 6), (3, 10), (4, 19), (5, 19), (6, 25), (7, 43), (8, 43), (9, 55), (10, 55), (11, 59), (12, 59), (13, 63), (14, 79)];
|
||||
let dir = scratch_dir("pi");
|
||||
let design: Value = serde_json::from_slice(&std::fs::read(root().join("tests/action_matrix/designs/pi-case.sc")).unwrap()).unwrap();
|
||||
let design: Value = serde_json::from_slice(&std::fs::read(root().join("tests/action_matrix/designs/pi-case.solidcraft")).unwrap()).unwrap();
|
||||
let at = |height: &str, file: &str| {
|
||||
let mut d = design.clone();
|
||||
for p in d["params"].as_array_mut().unwrap() {
|
||||
|
||||
@@ -145,7 +145,7 @@ fn eval_and_inspect_reject_invalid_options_before_script_execution_or_reporting(
|
||||
#[test]
|
||||
fn cli_loads_both_design_encodings_by_contents() {
|
||||
let dir = Scratch::new();
|
||||
for extension in ["sc", "solidcraft", "solvecraft", "json"] {
|
||||
for extension in ["solidcraft", "solvecraft", "json"] {
|
||||
let saved = dir.0.join(format!("box.{extension}"));
|
||||
json_output(cli().arg("run").arg(dir.script()).arg("--save").arg(&saved).output().unwrap());
|
||||
let bytes = std::fs::read(&saved).unwrap();
|
||||
@@ -412,11 +412,6 @@ fn legacy_extension_and_format_tag_still_open() {
|
||||
assert_eq!(body_count(&resaved), 2);
|
||||
let reopened = solidcraft_engine::io::read_design(&std::fs::read(&resaved).unwrap()).unwrap();
|
||||
assert!(reopened.to_json().contains("\"solidcraft/"));
|
||||
// `.solidcraft` (before `.sc`) opens as `--in` and as the design to evaluate; `.sc` too.
|
||||
let current = dir.0.join("current.sc");
|
||||
json_output(cli().args(["run", "--in"]).arg(&resaved).arg(dir.script()).arg("--save").arg(¤t).output().unwrap());
|
||||
assert_eq!(body_count(¤t), 3);
|
||||
assert!(solidcraft_engine::io::is_design_container(&std::fs::read(¤t).unwrap()), "a .sc file is a design container");
|
||||
}
|
||||
|
||||
/// `snapshot` starts from `--in` as `run` does (its script runs on that design), and refuses an
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//! Straight edges stay one edge after many cuts (#1049). The owner's design
|
||||
//! (`tests/action_matrix/designs/crazy-geom-cuts.sc`, saved by 0.10.0): five extrudes,
|
||||
//! (`tests/action_matrix/designs/crazy-geom-cuts.solidcraft`, saved by 0.10.0): five extrudes,
|
||||
//! joins and cuts, over four sketches, three of them on faces of the body with the faces'
|
||||
//! edges projected. Cuts used to leave the straight edges of the walls they sliced split at
|
||||
//! vertices nothing else used, so an edge that is one straight line was drawn (and lit on hover)
|
||||
@@ -14,7 +14,7 @@ use solidcraft_engine::doc::kernel::{FaceSurface, face_surface};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
fn design() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/action_matrix/designs/crazy-geom-cuts.sc")
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../tests/action_matrix/designs/crazy-geom-cuts.solidcraft")
|
||||
}
|
||||
|
||||
fn open(path: &Path) -> Session {
|
||||
|
||||
@@ -27,12 +27,12 @@
|
||||
<!-- Installable as an app (manifest.webmanifest); the icons are the Linux exports of the app mark. -->
|
||||
<meta name="theme-color" content="#343840">
|
||||
<link rel="manifest" href="manifest.webmanifest">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/32x32/io.tangencylabs.solidcraft.png">
|
||||
<link rel="apple-touch-icon" href="icons/256x256/io.tangencylabs.solidcraft.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="icons/32x32/ai.storyteller.solidcraft.png">
|
||||
<link rel="apple-touch-icon" href="icons/256x256/ai.storyteller.solidcraft.png">
|
||||
<link data-trunk rel="copy-file" href="manifest.webmanifest" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/32x32/apps/io.tangencylabs.solidcraft.png" data-target-path="icons/32x32" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/256x256/apps/io.tangencylabs.solidcraft.png" data-target-path="icons/256x256" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/512x512/apps/io.tangencylabs.solidcraft.png" data-target-path="icons/512x512" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/32x32/apps/ai.storyteller.solidcraft.png" data-target-path="icons/32x32" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/256x256/apps/ai.storyteller.solidcraft.png" data-target-path="icons/256x256" />
|
||||
<link data-trunk rel="copy-file" href="../../assets/icons/app/linux/exports/hicolor/512x512/apps/ai.storyteller.solidcraft.png" data-target-path="icons/512x512" />
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="solidcraft_canvas"></canvas>
|
||||
|
||||
@@ -9,8 +9,8 @@
|
||||
"theme_color": "#343840",
|
||||
"categories": ["productivity", "utilities"],
|
||||
"icons": [
|
||||
{ "src": "icons/32x32/io.tangencylabs.solidcraft.png", "sizes": "32x32", "type": "image/png" },
|
||||
{ "src": "icons/256x256/io.tangencylabs.solidcraft.png", "sizes": "256x256", "type": "image/png" },
|
||||
{ "src": "icons/512x512/io.tangencylabs.solidcraft.png", "sizes": "512x512", "type": "image/png" }
|
||||
{ "src": "icons/32x32/ai.storyteller.solidcraft.png", "sizes": "32x32", "type": "image/png" },
|
||||
{ "src": "icons/256x256/ai.storyteller.solidcraft.png", "sizes": "256x256", "type": "image/png" },
|
||||
{ "src": "icons/512x512/ai.storyteller.solidcraft.png", "sizes": "512x512", "type": "image/png" }
|
||||
]
|
||||
}
|
||||
|
||||
@@ -147,21 +147,6 @@ pub fn accept_list(extensions: &[&str]) -> String {
|
||||
list.join(",")
|
||||
}
|
||||
|
||||
/// The `accept` of a file input for `extensions` on this device (`apple_touch`: an iPhone or
|
||||
/// iPad). The picker there offers only files of a type the system knows from the list and greys
|
||||
/// out the rest, `.solidcraft` designs among them, since no system type has that extension
|
||||
/// (#6977). So it offers every file there, and the opener checks what it gets.
|
||||
pub fn input_accept(extensions: &[&str], apple_touch: bool) -> String {
|
||||
if apple_touch { String::new() } else { accept_list(extensions) }
|
||||
}
|
||||
|
||||
/// The browser runs on an iPhone or iPad (every browser there is WebKit), from its
|
||||
/// `navigator.userAgent` and `navigator.maxTouchPoints`. iPadOS presents itself as a Mac; only its
|
||||
/// touch screen tells them apart.
|
||||
pub fn apple_touch(user_agent: &str, touch_points: i32) -> bool {
|
||||
["iPhone", "iPad", "iPod"].iter().any(|d| user_agent.contains(d)) || (user_agent.contains("Macintosh") && touch_points > 1)
|
||||
}
|
||||
|
||||
/// The largest file an upload for `extensions` reads: images go into a canvas, which has a size
|
||||
/// limit, so a bigger one is refused before it is read. `None`: the importer checks the file.
|
||||
pub fn upload_limit(extensions: &[&str]) -> Option<usize> {
|
||||
@@ -169,15 +154,10 @@ pub fn upload_limit(extensions: &[&str]) -> Option<usize> {
|
||||
(!extensions.is_empty() && extensions.iter().all(image)).then_some(solidcraft_engine::doc::canvas::MAX_CANVAS_BYTES)
|
||||
}
|
||||
|
||||
/// The file type a Save As dialog offers for `name`: (description, MIME type, extension). A
|
||||
/// design is `.sc` unless the name already has an older design extension, which it keeps.
|
||||
pub fn save_type(name: &str) -> (&'static str, &'static str, String) {
|
||||
let ext = std::path::Path::new(name).extension().and_then(|e| e.to_str()).map(str::to_ascii_lowercase);
|
||||
match ext.as_deref() {
|
||||
Some("json") => ("Plain JSON design", "application/json", ".json".into()),
|
||||
Some(e) if solidcraft_engine::io::is_design_ext(e) => ("SolidCraft design", "application/octet-stream", format!(".{e}")),
|
||||
_ => ("SolidCraft design", "application/octet-stream", format!(".{}", solidcraft_engine::io::DESIGN_EXT)),
|
||||
}
|
||||
/// The file type a Save As dialog offers for `name`: (description, MIME type, extension).
|
||||
pub fn save_type(name: &str) -> (&'static str, &'static str, &'static str) {
|
||||
let json = std::path::Path::new(name).extension().is_some_and(|e| e.eq_ignore_ascii_case("json"));
|
||||
if json { ("Plain JSON design", "application/json", ".json") } else { ("SolidCraft design", "application/octet-stream", ".solidcraft") }
|
||||
}
|
||||
|
||||
/// Is this a cancelled dialog (the browser's `AbortError`)? Not an error to show.
|
||||
@@ -269,7 +249,7 @@ mod web {
|
||||
let options = Object::new();
|
||||
let _ = Reflect::set(&options, &"suggestedName".into(), &name.into());
|
||||
let (description, mime, extension) = save_type(name);
|
||||
let _ = Reflect::set(&options, &"types".into(), &types(description, mime, &extension));
|
||||
let _ = Reflect::set(&options, &"types".into(), &types(description, mime, extension));
|
||||
options
|
||||
}
|
||||
|
||||
@@ -354,9 +334,7 @@ mod web {
|
||||
let options = Object::new();
|
||||
let _ = Reflect::set(&options, &"multiple".into(), &JsValue::FALSE);
|
||||
let extensions = Array::new();
|
||||
let designs = solidcraft_engine::io::DESIGN_EXTS.iter().map(|e| format!(".{e}"));
|
||||
let others = [".json", ".step", ".stp", ".3mf", ".stl", ".obj", ".glb", ".gltf", ".fcstd", ".3dm", ".dxf", ".svg"].map(String::from);
|
||||
for e in designs.chain(others) {
|
||||
for e in [".solidcraft", ".solvecraft", ".json", ".step", ".stp", ".3mf", ".stl", ".obj", ".glb", ".gltf", ".fcstd", ".3dm", ".dxf", ".svg"] {
|
||||
extensions.push(&e.into());
|
||||
}
|
||||
let accept = Object::new();
|
||||
@@ -634,8 +612,6 @@ mod tests {
|
||||
assert_eq!(route("/disk/18f-2a/Bracket.solidcraft"), WriteTo::Disk);
|
||||
assert_eq!(route("/downloads/part.step"), WriteTo::Download);
|
||||
assert_eq!(route("/upload/part.step"), WriteTo::Memory);
|
||||
// The temporary folder (a screenshot without a path, #7192) stays in memory.
|
||||
assert_eq!(route(&format!("{}/solidcraft-shot.png", solidcraft_engine::io::vfs::BROWSER_TEMP)), WriteTo::Memory);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -674,7 +650,6 @@ mod tests {
|
||||
#[test]
|
||||
fn only_design_opens_use_the_disk_dialog() {
|
||||
assert!(opens_design(&[]));
|
||||
assert!(opens_design(&["sc"]));
|
||||
assert!(opens_design(&["solidcraft"]));
|
||||
assert!(opens_design(&["solvecraft"]));
|
||||
assert!(!opens_design(&["step", "stp"]));
|
||||
@@ -696,33 +671,11 @@ mod tests {
|
||||
assert_eq!(upload_limit(&["dxf"]), None);
|
||||
}
|
||||
|
||||
/// An iPhone or iPad greys out a `.solidcraft` design the `accept` list names, as it knows no
|
||||
/// type for that extension, so its inputs offer every file; other devices keep the list (#6977).
|
||||
#[test]
|
||||
fn file_inputs_on_an_iphone_or_ipad_offer_every_file() {
|
||||
let iphone =
|
||||
"Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Mobile/15E148 Safari/604.1";
|
||||
let mac = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/18.5 Safari/605.1.15";
|
||||
let chrome_ios = "Mozilla/5.0 (iPhone; CPU iPhone OS 18_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/138.0.7204.156 Mobile/15E148 Safari/604.1";
|
||||
let chrome = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36";
|
||||
assert!(apple_touch(iphone, 5));
|
||||
assert!(apple_touch(chrome_ios, 5), "every browser on iOS has the same picker");
|
||||
assert!(apple_touch(mac, 5), "an iPad presents itself as a Mac");
|
||||
assert!(!apple_touch(mac, 0), "a Mac has no touch screen");
|
||||
assert!(!apple_touch(chrome, 10), "a touch screen elsewhere filters as before");
|
||||
assert!(!apple_touch("", 0), "hostile input is just not an iPhone");
|
||||
let designs = ["solidcraft", "json", "step"];
|
||||
assert_eq!(input_accept(&designs, true), "");
|
||||
assert_eq!(input_accept(&designs, false), ".solidcraft,.json,.step");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_save_dialog_offers_the_type_of_the_name() {
|
||||
assert_eq!(save_type("Plate.sc").2, ".sc");
|
||||
assert_eq!(save_type("Plate.JSON").2, ".json");
|
||||
assert_eq!(save_type("Plate").2, ".sc");
|
||||
// A design opened under an older extension keeps it.
|
||||
assert_eq!(save_type("Plate.solidcraft").2, ".solidcraft");
|
||||
assert_eq!(save_type("Plate.JSON").2, ".json");
|
||||
assert_eq!(save_type("Plate").2, ".solidcraft");
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -24,13 +24,11 @@ pub const OPFS: &str = disk::OPFS_PREFIX;
|
||||
pub const DOWNLOADS: &str = disk::DOWNLOADS_PREFIX;
|
||||
/// Files brought in from the computer.
|
||||
pub const UPLOAD: &str = "/upload/";
|
||||
/// The autosave of unsaved work (in OPFS: `recovery.sc` and `recovery.json`), named with
|
||||
/// `solidcraft_engine::io::DESIGN_EXT`.
|
||||
const RECOVERY: &str = "recovery.sc";
|
||||
/// The autosaves earlier releases kept, newest first, one per entry of
|
||||
/// `solidcraft_engine::io::LEGACY_DESIGN_EXTS` (compatibility shim, docs/compatibility.md): the
|
||||
/// newest is recovered when there is no current one.
|
||||
const LEGACY_RECOVERY: [&str; 2] = ["recovery.solidcraft", "recovery.solvecraft"];
|
||||
/// The autosave of unsaved work (in OPFS: `recovery.solidcraft` and `recovery.json`).
|
||||
const RECOVERY: &str = "recovery.solidcraft";
|
||||
/// The autosave earlier releases kept (compatibility shim, docs/compatibility.md): recovered
|
||||
/// when there is no current one.
|
||||
const LEGACY_RECOVERY: &str = "recovery.solvecraft";
|
||||
const RECOVERY_META: &str = "recovery.json";
|
||||
|
||||
/// What to do with a file brought in from the computer.
|
||||
@@ -284,10 +282,9 @@ pub fn start(files: &Rc<Files>) {
|
||||
}
|
||||
let mut entries = Vec::new();
|
||||
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 {
|
||||
if LEGACY_RECOVERY.contains(&name.as_str()) {
|
||||
if legacy == Some(name.as_str()) {
|
||||
if name == LEGACY_RECOVERY {
|
||||
if !has_recovery {
|
||||
vfs::insert(&format!("{OPFS}{RECOVERY}"), bytes);
|
||||
}
|
||||
continue;
|
||||
@@ -427,9 +424,7 @@ pub fn autosave(files: &Rc<Files>, doc: Option<(&solidcraft_engine::doc::Documen
|
||||
}
|
||||
None => spawn_local(async move {
|
||||
let _ = remove_file(RECOVERY).await;
|
||||
for legacy in LEGACY_RECOVERY {
|
||||
let _ = remove_file(legacy).await;
|
||||
}
|
||||
let _ = remove_file(LEGACY_RECOVERY).await;
|
||||
let _ = remove_file(RECOVERY_META).await;
|
||||
}),
|
||||
}
|
||||
@@ -518,9 +513,9 @@ fn input_picker(files: &Rc<Files>, intent: Intent) {
|
||||
input.set_type("file");
|
||||
// Everything the browser build imports, from the format registry: cadmpeg is pure Rust, so
|
||||
// FreeCAD and Rhino files are part of it; plus the DXF and SVG drawings Open and Insert take.
|
||||
let mut extensions = solidcraft_engine::io::formats::open_extensions(true);
|
||||
extensions.extend(["dxf", "svg"]);
|
||||
input.set_accept(&disk::input_accept(&extensions, apple_touch()));
|
||||
let accept =
|
||||
solidcraft_engine::io::formats::open_extensions(true).iter().chain(&["dxf", "svg"]).map(|e| format!(".{e}")).collect::<Vec<_>>().join(",");
|
||||
input.set_accept(&accept);
|
||||
// Attached (hidden) while it is open: some browsers ignore a click on a detached input.
|
||||
let _ = input.set_attribute("style", "display:none");
|
||||
if let Some(body) = doc.body() {
|
||||
@@ -551,7 +546,7 @@ fn input_picker(files: &Rc<Files>, intent: Intent) {
|
||||
/// `None` when the dialog was closed or the file could not be read.
|
||||
pub fn pick_upload(files: &Rc<Files>, extensions: &[&str]) -> impl std::future::Future<Output = Option<String>> + use<> {
|
||||
// Shown now, in the click: a file dialog needs the user's gesture.
|
||||
let chosen = choose_file(&disk::input_accept(extensions, apple_touch()));
|
||||
let chosen = choose_file(&disk::accept_list(extensions));
|
||||
let limit = disk::upload_limit(extensions);
|
||||
let files = files.clone();
|
||||
async move {
|
||||
@@ -578,14 +573,6 @@ pub fn pick_upload(files: &Rc<Files>, extensions: &[&str]) -> impl std::future::
|
||||
}
|
||||
}
|
||||
|
||||
/// The page runs on an iPhone or iPad, whose file inputs offer every file (`disk::input_accept`).
|
||||
fn apple_touch() -> bool {
|
||||
web_sys::window().is_some_and(|w| {
|
||||
let nav = w.navigator();
|
||||
disk::apple_touch(&nav.user_agent().unwrap_or_default(), nav.max_touch_points())
|
||||
})
|
||||
}
|
||||
|
||||
/// Show a hidden `<input type=file>` offering `accept` (call in the click). The promise answers
|
||||
/// the picked `File`, or `null` when the dialog is closed (the input's `cancel` event).
|
||||
fn choose_file(accept: &str) -> Result<js_sys::Promise, String> {
|
||||
|
||||
@@ -72,10 +72,10 @@ impl Download {
|
||||
}
|
||||
let Some((name, bytes)) = self.pending.take() else { return };
|
||||
// The extension selects the design loader even when the URL has no extension.
|
||||
let path = &format!("/linked/design.{}", solidcraft_engine::io::DESIGN_EXT);
|
||||
let path = "/linked/design.solidcraft";
|
||||
vfs::insert(path, bytes);
|
||||
app.open_path(path);
|
||||
if app.session.path.as_deref() == Some(path.as_str()) {
|
||||
if app.session.path.as_deref() == Some(path) {
|
||||
app.session.path = None;
|
||||
app.session.doc_mut().name = name;
|
||||
app.session.mark_unsaved();
|
||||
@@ -90,14 +90,9 @@ async fn fetch(value: &str) -> Reply {
|
||||
let window = web_sys::window().ok_or("No browser window")?;
|
||||
let page_origin = window.location().origin().map_err(|_| "Cannot read page origin")?;
|
||||
let url = design_url(value, &page_origin)?;
|
||||
let name = url.path_segments().and_then(|mut segments| segments.next_back()).filter(|name| !name.is_empty()).unwrap_or("Design");
|
||||
let name = url.path_segments().and_then(|mut segments| segments.next_back()).filter(|name| !name.is_empty()).unwrap_or("Design.solidcraft");
|
||||
let decoded = js_sys::decode_uri_component(name).ok().and_then(|name| name.as_string()).unwrap_or_else(|| name.to_owned());
|
||||
// `arm.sc` (or a legacy `arm.solidcraft`) is named `arm`.
|
||||
let stem = match decoded.rsplit_once('.') {
|
||||
Some((stem, ext)) if !stem.is_empty() && solidcraft_engine::io::is_design_ext(ext) => stem,
|
||||
_ => decoded.as_str(),
|
||||
};
|
||||
let name: String = stem.chars().take(255).collect();
|
||||
let name: String = decoded.strip_suffix(".solidcraft").unwrap_or(&decoded).chars().take(255).collect();
|
||||
let response = JsFuture::from(window.fetch_with_str(url.as_str())).await.map_err(|_| "Network request failed or the host refused CORS access")?;
|
||||
let response: web_sys::Response = response.dyn_into().map_err(|_| "Invalid download response")?;
|
||||
design_url(&response.url(), &page_origin).map_err(|_| "Design redirected to a URL that does not use HTTPS or the page's own origin")?;
|
||||
|
||||
@@ -417,6 +417,7 @@ impl Lines {
|
||||
struct Subscription {
|
||||
lines: Arc<Lines>,
|
||||
stop: Arc<AtomicBool>,
|
||||
_counted: Subscriber,
|
||||
}
|
||||
|
||||
impl Subscription {
|
||||
@@ -434,13 +435,10 @@ impl Subscription {
|
||||
let stop = Arc::new(AtomicBool::new(false));
|
||||
let first = lines.turn.lock().unwrap_or_else(PoisonError::into_inner);
|
||||
let (l, s) = (lines.clone(), stop.clone());
|
||||
// The connection's seat on the watch rides the events thread: it leaves when the thread
|
||||
// does, so the next failed write to a closed client retires it even while the connection's
|
||||
// own thread is slow to see the end of the stream (#7226).
|
||||
std::thread::Builder::new().stack_size(64 * 1024).spawn(move || send_events(design, &l, &s, seen, counted)).ok()?;
|
||||
std::thread::Builder::new().stack_size(64 * 1024).spawn(move || send_events(design, &l, &s, seen)).ok()?;
|
||||
let sent = send(&mut &lines.socket, reply);
|
||||
drop(first);
|
||||
Some((Subscription { lines, stop }, sent))
|
||||
Some((Subscription { lines, stop, _counted: counted }, sent))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -460,10 +458,8 @@ impl Drop for Subscription {
|
||||
}
|
||||
|
||||
/// The `design.changed` lines of a subscribed connection, each time the design differs from the
|
||||
/// last one it was told of (`seen`), until `stop`. Owns the connection's seat on the watch
|
||||
/// (`counted`), so the seat leaves exactly when this thread does: on `stop`, or on the first
|
||||
/// write a closed client refuses (#7226).
|
||||
fn send_events(design: &DesignWatch, lines: &Lines, stop: &AtomicBool, mut seen: (String, u64), _counted: Subscriber) {
|
||||
/// last one it was told of (`seen`), until `stop`.
|
||||
fn send_events(design: &DesignWatch, lines: &Lines, stop: &AtomicBool, mut seen: (String, u64)) {
|
||||
while !stop.load(Ordering::Relaxed) {
|
||||
let Some(now) = design.wait(&seen, stop, Duration::from_secs(1)).filter(|now| *now != seen) else { continue };
|
||||
if lines.send(&json!({"event": "design.changed", "params": {"document_id": now.0, "revision": now.1}})).is_err() {
|
||||
@@ -870,17 +866,11 @@ mod tests {
|
||||
assert_eq!(next(&mut lines), json!({"id": 3, "ok": true, "result": "ui.view"}));
|
||||
writeln!(quiet_out, "{}", json!({"id": 2, "method": "ui.view"})).unwrap();
|
||||
assert_eq!(next(&mut quiet), json!({"id": 2, "ok": true, "result": "ui.view"}), "no events without subscribing");
|
||||
// Closed, it is no longer counted. Each publish asks the events thread for one more line,
|
||||
// and its write to the closed connection is what retires the seat; that keeps the wait off
|
||||
// the connection's own thread, which anything else this process is doing may hold up past
|
||||
// the deadline (#7226).
|
||||
// Closed, it is no longer counted.
|
||||
drop((out, lines));
|
||||
let mut revision = 11;
|
||||
let deadline = Instant::now() + Duration::from_secs(10);
|
||||
while WATCH.subscribed() && Instant::now() < deadline {
|
||||
WATCH.publish("d-1", revision);
|
||||
revision += 1;
|
||||
std::thread::sleep(Duration::from_millis(20));
|
||||
std::thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
assert!(!WATCH.subscribed());
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
//! SolidCraft desktop app.
|
||||
//!
|
||||
//! Usage: `solidcraft [--control <port>] [--sample] [--crash-log] [--safe-mode] [design.sc | part.step]`
|
||||
//! Usage: `solidcraft [--control <port>] [--sample] [--crash-log] [--safe-mode] [design.solidcraft | part.step]`
|
||||
//!
|
||||
//! `--safe-mode` (or `SOLIDCRAFT_SAFE_MODE=1`) starts with default settings, no recovery offer and
|
||||
//! the software renderer; see `startup` and docs/book/src/guide/troubleshooting.md.
|
||||
@@ -92,8 +92,6 @@ struct App {
|
||||
native_menu: Option<native_menu::NativeMenu>,
|
||||
/// A device's own app moving the view (Preferences › Navigation, off by default).
|
||||
nav_bridge: nav_bridge::NavBridge,
|
||||
/// Where the next file dialog opens: it follows the open design (#7756).
|
||||
dialog_folder: DialogFolder,
|
||||
}
|
||||
|
||||
impl eframe::App for App {
|
||||
@@ -124,7 +122,6 @@ impl eframe::App for App {
|
||||
// Automation runs (a control port was asked for) leave the user's AI apps alone.
|
||||
if !self.take_focus_unwanted {
|
||||
solidcraft_ui_egui::ai_connect::begin(&mut self.solve);
|
||||
solidcraft_ui_egui::agent_chat::begin(&mut self.solve);
|
||||
}
|
||||
if let Some(dir) = self.starting.take() {
|
||||
startup::started(&dir);
|
||||
@@ -149,7 +146,6 @@ impl eframe::App for App {
|
||||
menu.tick(&mut self.solve, ctx);
|
||||
}
|
||||
self.solve.logic(ctx);
|
||||
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() {
|
||||
self.space_mouse.tick(&mut self.solve, ctx);
|
||||
@@ -244,60 +240,6 @@ impl App {
|
||||
}
|
||||
}
|
||||
|
||||
/// Where the next file dialog opens (#7756): the folder of the file last chosen in one, or of the
|
||||
/// design last opened or saved, whichever came last. Linux dialogs otherwise open in the home
|
||||
/// folder every time; macOS and Windows remember a folder of their own, which this agrees with.
|
||||
#[derive(Clone, Default)]
|
||||
struct DialogFolder(std::sync::Arc<std::sync::Mutex<DialogFolderState>>);
|
||||
|
||||
#[derive(Default)]
|
||||
struct DialogFolderState {
|
||||
folder: Option<std::path::PathBuf>,
|
||||
/// The open design's path when last looked at, to notice another design opened or saved.
|
||||
design: Option<String>,
|
||||
}
|
||||
|
||||
impl DialogFolder {
|
||||
/// A file was chosen in a dialog: the next one opens in its folder.
|
||||
fn chosen(&self, file: &std::path::Path) {
|
||||
if let (Some(folder), Ok(mut state)) = (file.parent().filter(|f| !f.as_os_str().is_empty()), self.0.lock()) {
|
||||
state.folder = Some(folder.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
/// The open design's path, every frame: a design opened or saved somewhere else moves the
|
||||
/// next dialog to its folder; the same design again leaves a folder chosen since alone.
|
||||
fn design(&self, path: Option<&str>) {
|
||||
let Ok(mut state) = self.0.lock() else { return };
|
||||
if state.design.as_deref() == path {
|
||||
return;
|
||||
}
|
||||
state.design = path.map(str::to_string);
|
||||
if let Some(folder) = path.and_then(|p| std::path::Path::new(p).parent()).filter(|f| !f.as_os_str().is_empty()) {
|
||||
state.folder = Some(folder.to_path_buf());
|
||||
}
|
||||
}
|
||||
|
||||
/// A dialog's answer as a path, its folder remembered for the next dialog.
|
||||
fn answer(&self, picked: Option<rfd::FileHandle>) -> Option<String> {
|
||||
let picked = picked?;
|
||||
self.chosen(picked.path());
|
||||
Some(picked.path().to_string_lossy().into_owned())
|
||||
}
|
||||
|
||||
fn folder(&self) -> Option<std::path::PathBuf> {
|
||||
self.0.lock().ok().and_then(|state| state.folder.clone())
|
||||
}
|
||||
|
||||
/// The dialog, opening in the remembered folder when there is one.
|
||||
fn start(&self, dialog: rfd::AsyncFileDialog) -> rfd::AsyncFileDialog {
|
||||
match self.folder() {
|
||||
Some(folder) => dialog.set_directory(folder),
|
||||
None => dialog,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Linux file dialogs match patterns by case, so each extension goes in as lower and upper
|
||||
/// case, plus FreeCAD's own mixed casing.
|
||||
fn case_variants(exts: &[&str]) -> Vec<String> {
|
||||
@@ -347,16 +289,15 @@ fn fullscreen_content_insets(raw: &mut egui::RawInput, title_height: f32, zoom:
|
||||
raw.safe_area_insets = Some(insets);
|
||||
}
|
||||
|
||||
fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services {
|
||||
fn services(cc: &eframe::CreationContext<'_>) -> Services {
|
||||
let parent = rfd::AsyncFileDialog::new().set_parent(cc);
|
||||
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 file type the command reads comes first, so the picker opens on it (#1801).
|
||||
let mut dialog = open_folder.start(open_parent.clone());
|
||||
let mut dialog = open_parent.clone();
|
||||
if !first.is_empty() {
|
||||
let label = first.iter().map(|e| e.to_uppercase()).collect::<Vec<_>>().join(", ");
|
||||
dialog = dialog.add_filter(label, &case_variants(first));
|
||||
@@ -369,8 +310,7 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
|
||||
dialog = dialog.add_filter(label, &case_variants(&exts));
|
||||
}
|
||||
let future = dialog.add_filter("All files", &["*"]).pick_file();
|
||||
let folder = open_folder.clone();
|
||||
Box::pin(async move { folder.answer(future.await) })
|
||||
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
|
||||
})),
|
||||
// One kind of file (the agent chat's attachments): only the types it names, as given and in
|
||||
// lower and upper case (Linux dialogs match case; macOS allows just the listed types).
|
||||
@@ -385,9 +325,8 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
|
||||
}
|
||||
let all: Vec<&str> = all.iter().map(String::as_str).collect();
|
||||
// Any other file attaches by reference, so the list is not the only choice.
|
||||
let future = file_folder.start(file_parent.clone()).add_filter(label, &all).add_filter("All files", &["*"]).pick_file();
|
||||
let folder = file_folder.clone();
|
||||
Box::pin(async move { folder.answer(future.await) })
|
||||
let future = file_parent.clone().add_filter(label, &all).add_filter("All files", &["*"]).pick_file();
|
||||
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
|
||||
})),
|
||||
pick_program: Some(Box::new(move || {
|
||||
let dialog = program_parent.clone().set_title("Choose your slicer");
|
||||
@@ -396,7 +335,7 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
|
||||
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
|
||||
})),
|
||||
pick_save: Some(Box::new(move |name: &str, exts: &[&str]| {
|
||||
let dialog = save_folder.start(parent.clone()).set_file_name(name);
|
||||
let dialog = parent.clone().set_file_name(name);
|
||||
let dialog = if exts == [io::DESIGN_EXT, "json"] {
|
||||
if name.ends_with(".json") {
|
||||
dialog.add_filter("Plain JSON design", &["json"]).add_filter("SolidCraft container", &[io::DESIGN_EXT])
|
||||
@@ -407,8 +346,7 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
|
||||
dialog.add_filter("File", exts)
|
||||
};
|
||||
let future = dialog.save_file();
|
||||
let folder = save_folder.clone();
|
||||
Box::pin(async move { folder.answer(future.await) })
|
||||
Box::pin(async move { future.await.map(|p| p.path().to_string_lossy().into_owned()) })
|
||||
})),
|
||||
graphics_adapters: Vec::new(),
|
||||
graphics_path: graphics_path().map(|p| p.to_string_lossy().to_string()),
|
||||
@@ -615,26 +553,26 @@ fn graphics_path() -> Option<std::path::PathBuf> {
|
||||
/// Compatibility shim (docs/compatibility.md): on the first launch after the rename, copy the
|
||||
/// settings of earlier releases into the new folder. They kept `graphics.json` (and, on Linux
|
||||
/// and macOS, the recovery folder) under the old product name, and eframe's `app.ron` under the
|
||||
/// window's old app id. In the Flatpak sandbox, earlier releases kept everything under an
|
||||
/// old app id's `~/.var/app` folder; those copies come first, newest first. The old folders
|
||||
/// window's old app id. In the Flatpak sandbox, releases before 0.8 kept everything under the
|
||||
/// old app id's `~/.var/app` folder; that copy comes first, as it is the newest. The old folders
|
||||
/// are never changed.
|
||||
fn adopt_legacy_settings() {
|
||||
let (Some(current), Some(legacy)) = (settings_dir().ok().flatten(), eframe::storage_dir("SolveCraft")) else { return };
|
||||
let legacy_eframe = eframe::storage_dir(solidcraft_engine::io::LEGACY_APP_ID);
|
||||
let old_flatpak = solidcraft_engine::io::legacy_flatpak_paths(¤t);
|
||||
if let Err(e) = adopt_settings(¤t, &old_flatpak, &legacy, legacy_eframe.as_deref()) {
|
||||
let old_flatpak = solidcraft_engine::io::legacy_flatpak_path(¤t);
|
||||
if let Err(e) = adopt_settings(¤t, old_flatpak.as_deref(), &legacy, legacy_eframe.as_deref()) {
|
||||
log::warn!("settings: could not copy the previous release's settings into {}: {e}", current.display());
|
||||
}
|
||||
}
|
||||
|
||||
fn adopt_settings(
|
||||
current: &std::path::Path,
|
||||
old_flatpak: &[std::path::PathBuf],
|
||||
old_flatpak: Option<&std::path::Path>,
|
||||
legacy: &std::path::Path,
|
||||
legacy_eframe: Option<&std::path::Path>,
|
||||
) -> std::io::Result<()> {
|
||||
let fresh = !current.exists();
|
||||
for old in old_flatpak {
|
||||
if let Some(old) = old_flatpak {
|
||||
solidcraft_engine::io::adopt_legacy_dir(current, old)?;
|
||||
}
|
||||
solidcraft_engine::io::adopt_legacy_dir(current, legacy)?;
|
||||
@@ -750,14 +688,11 @@ fn main() -> eframe::Result {
|
||||
let mut control_port: Option<u16> = solidcraft_engine::geom::env::var("CONTROL_PORT").and_then(|p| p.parse().ok());
|
||||
let mut files = Vec::new();
|
||||
let mut sample = false;
|
||||
// `--theme-file PATH`: follow that theme file for this run without saving it (#7573).
|
||||
let mut theme_file: Option<String> = None;
|
||||
let mut args = std::env::args().skip(1);
|
||||
while let Some(a) = args.next() {
|
||||
match a.as_str() {
|
||||
"--control" => control_port = args.next().and_then(|p| p.parse().ok()),
|
||||
"--sample" => sample = true,
|
||||
"--theme-file" => theme_file = args.next(),
|
||||
"--safe-mode" => safe_mode = true,
|
||||
"--version" | "-V" => {
|
||||
println!("solidcraft {}", env!("CARGO_PKG_VERSION"));
|
||||
@@ -777,9 +712,7 @@ fn main() -> eframe::Result {
|
||||
return Ok(());
|
||||
}
|
||||
"--help" | "-h" => {
|
||||
println!(
|
||||
"usage: solidcraft [--control PORT] [--sample] [--theme-file THEME.json] [--crash-log] [--safe-mode] [design.sc | part.step]"
|
||||
);
|
||||
println!("usage: solidcraft [--control PORT] [--sample] [--crash-log] [--safe-mode] [design.solidcraft | part.step]");
|
||||
return Ok(());
|
||||
}
|
||||
_ => files.push(a),
|
||||
@@ -907,8 +840,7 @@ fn main() -> eframe::Result {
|
||||
solidcraft_ui_egui::fonts::setup_bundled_fonts(&cc.egui_ctx);
|
||||
solidcraft_ui_egui::fonts::load_system_fonts_in_background(&cc.egui_ctx);
|
||||
mark("fonts");
|
||||
let dialog_folder = DialogFolder::default();
|
||||
let mut app = SolveApp::new(Session::default(), services(cc, &dialog_folder));
|
||||
let mut app = SolveApp::new(Session::default(), services(cc));
|
||||
app.apply_off_thread = Some(solidcraft_ui_egui::dialogs::APPLY_AT_ONCE);
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -931,11 +863,6 @@ fn main() -> eframe::Result {
|
||||
if let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))).filter(|_| !safe_mode) {
|
||||
app.load_prefs(&p);
|
||||
}
|
||||
if let Some(path) = &theme_file
|
||||
&& let Err(e) = solidcraft_ui_egui::prefs::follow_theme_file_for_run(&mut app, path)
|
||||
{
|
||||
app.set_status(format!("--theme-file: {e}"), true);
|
||||
}
|
||||
mark("app state and prefs");
|
||||
let gpu = cc.wgpu_render_state.as_ref().map(|rs| LostWatch {
|
||||
lost: rs.device_lost.clone(),
|
||||
@@ -1066,7 +993,6 @@ fn main() -> eframe::Result {
|
||||
#[cfg(target_os = "macos")]
|
||||
native_menu,
|
||||
nav_bridge,
|
||||
dialog_folder,
|
||||
}))
|
||||
}),
|
||||
);
|
||||
@@ -1245,40 +1171,6 @@ mod backend_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod dialog_folder_tests {
|
||||
use super::DialogFolder;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
/// #7756: a file dialog opens where the last one left off, not in the home folder.
|
||||
#[test]
|
||||
fn the_next_dialog_opens_in_the_folder_of_the_file_chosen_last() {
|
||||
let folder = DialogFolder::default();
|
||||
assert_eq!(folder.folder(), None, "before any choice the system decides");
|
||||
folder.chosen(Path::new("/work/prints/bracket.stl"));
|
||||
assert_eq!(folder.folder(), Some(PathBuf::from("/work/prints")));
|
||||
// A bare file name has no folder to remember.
|
||||
folder.chosen(Path::new("bracket.step"));
|
||||
assert_eq!(folder.folder(), Some(PathBuf::from("/work/prints")));
|
||||
}
|
||||
|
||||
/// Save As starts beside the open design (#7756), and a folder chosen since wins until another
|
||||
/// design is opened or saved.
|
||||
#[test]
|
||||
fn the_open_design_moves_the_folder_only_when_it_changes() {
|
||||
let folder = DialogFolder::default();
|
||||
folder.design(Some("/work/lid/lid.solidcraft"));
|
||||
assert_eq!(folder.folder(), Some(PathBuf::from("/work/lid")));
|
||||
folder.chosen(Path::new("/exports/lid.3mf"));
|
||||
folder.design(Some("/work/lid/lid.solidcraft"));
|
||||
assert_eq!(folder.folder(), Some(PathBuf::from("/exports")), "the same design again leaves the export folder");
|
||||
folder.design(None);
|
||||
assert_eq!(folder.folder(), Some(PathBuf::from("/exports")), "a new, unsaved design has no folder of its own");
|
||||
folder.design(Some("/work/base/base.solidcraft"));
|
||||
assert_eq!(folder.folder(), Some(PathBuf::from("/work/base")));
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod viewport_tests {
|
||||
#[cfg(target_os = "macos")]
|
||||
@@ -1388,14 +1280,14 @@ mod msaa_tests {
|
||||
std::fs::create_dir_all(&legacy_eframe).unwrap();
|
||||
std::fs::write(legacy.join("graphics.json"), r#"{"msaa":8}"#).unwrap();
|
||||
std::fs::write(legacy_eframe.join("app.ron"), "(prefs)").unwrap();
|
||||
super::adopt_settings(¤t, &[], &legacy, Some(&legacy_eframe)).unwrap();
|
||||
super::adopt_settings(¤t, None, &legacy, Some(&legacy_eframe)).unwrap();
|
||||
assert_eq!(std::fs::read_to_string(current.join("graphics.json")).unwrap(), r#"{"msaa":8}"#);
|
||||
assert_eq!(std::fs::read_to_string(current.join("app.ron")).unwrap(), "(prefs)");
|
||||
assert!(legacy.join("graphics.json").is_file() && legacy_eframe.join("app.ron").is_file());
|
||||
// Later launches leave the current settings alone.
|
||||
std::fs::write(current.join("app.ron"), "(mine)").unwrap();
|
||||
std::fs::remove_file(current.join("graphics.json")).unwrap();
|
||||
super::adopt_settings(¤t, &[], &legacy, Some(&legacy_eframe)).unwrap();
|
||||
super::adopt_settings(¤t, None, &legacy, Some(&legacy_eframe)).unwrap();
|
||||
assert_eq!(std::fs::read_to_string(current.join("app.ron")).unwrap(), "(mine)");
|
||||
assert!(!current.join("graphics.json").exists());
|
||||
let _ = std::fs::remove_dir_all(&d);
|
||||
|
||||
@@ -146,7 +146,8 @@ impl NativeMenu {
|
||||
}
|
||||
|
||||
fn action_enabled(action: &FileAction, app: &SolveApp, ctx: &egui::Context) -> bool {
|
||||
action.menu_enabled(app, ctx.egui_wants_keyboard_input())
|
||||
action.enabled(app)
|
||||
|| (matches!(action, FileAction::Undo | FileAction::Redo) && ctx.egui_wants_keyboard_input() && FileAction::Settings.enabled(app))
|
||||
}
|
||||
|
||||
pub fn tick(&mut self, app: &mut SolveApp, ctx: &egui::Context) {
|
||||
@@ -156,12 +157,6 @@ impl NativeMenu {
|
||||
item.set_enabled(enabled);
|
||||
}
|
||||
}
|
||||
for (item, action) in &self.help_actions {
|
||||
let enabled = solidcraft_ui_egui::help::available(app, action);
|
||||
if item.is_enabled() != enabled {
|
||||
item.set_enabled(enabled);
|
||||
}
|
||||
}
|
||||
if !self.recent_paths.iter().eq(app.home.recent.iter().take(32))
|
||||
&& let Err(error) = self.update_recent(app)
|
||||
{
|
||||
|
||||
@@ -543,16 +543,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn probing_a_backend_alone_never_panics() {
|
||||
// Whatever this machine has (often nothing): an answer, not a crash. On Windows the probe
|
||||
// goes the way the app's does, stopping at the first backend with a GPU: a machine with
|
||||
// D3D12 never loads its OpenGL driver into the test process, and AMD's (`atio6axx.dll`)
|
||||
// aborted the process as it exited (#1156).
|
||||
if cfg!(target_os = "windows") {
|
||||
let _ = choose_backends(true, false, false, None, has_hardware_adapter);
|
||||
} else {
|
||||
for b in WINDOWS_ORDER {
|
||||
let _ = has_hardware_adapter(b);
|
||||
}
|
||||
// Whatever this machine has (often nothing): an answer, not a crash.
|
||||
for b in WINDOWS_ORDER {
|
||||
let _ = has_hardware_adapter(b);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,18 +558,8 @@ mod tests {
|
||||
#[test]
|
||||
fn the_software_adapter_is_preferred_and_its_absence_is_an_error_not_a_panic() {
|
||||
assert!(pick_cpu_adapter(&[], None).unwrap_err().contains("none"));
|
||||
// The adapters the software renderer chooses among: it starts D3D12 alone on Windows
|
||||
// (WARP), not every backend, so the test doesn't load the OpenGL driver either (#1156).
|
||||
let options = crate::wgpu_options(true, None);
|
||||
let eframe::egui_wgpu::WgpuSetup::CreateNew(setup) = &options.wgpu_setup else { panic!("the software renderer makes its own device") };
|
||||
let backends = setup.instance_descriptor.backends;
|
||||
if cfg!(target_os = "windows") && std::env::var_os(WGPU_BACKEND_ENV).is_none() {
|
||||
assert_eq!(backends, eframe::wgpu::Backends::DX12);
|
||||
}
|
||||
let mut desc = eframe::wgpu::InstanceDescriptor::new_without_display_handle_from_env();
|
||||
desc.backends = backends;
|
||||
let instance = eframe::wgpu::Instance::new(desc);
|
||||
let adapters = pollster::block_on(instance.enumerate_adapters(backends));
|
||||
let instance = eframe::wgpu::Instance::new(eframe::wgpu::InstanceDescriptor::new_without_display_handle());
|
||||
let adapters = pollster::block_on(instance.enumerate_adapters(eframe::wgpu::Backends::all()));
|
||||
if adapters.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ The CLI still supports arbitrary PNG/ICO exports with `solidcraft-cli icon`.
|
||||
| macOS | Composer master and paired exports; bundle installs `Assets.car` and `SolidCraft.icns` |
|
||||
| Windows executable | `solidcraft-executable.ico`: original DIB/mask representations at 16/24/32/48/64/128/256 px |
|
||||
| Windows installer | `solidcraft.ico`: PNG representations at 16/20/24/32/40/48/64/128/256 px; `SOLIDCRAFT_ICONS` override retained |
|
||||
| Linux, Nix, deb/rpm, tar, Flatpak, Snap | Copy canonical hicolor images; icon name stays `io.tangencylabs.solidcraft` |
|
||||
| Linux, Nix, deb/rpm, tar, Flatpak, Snap | Copy canonical hicolor images; icon name stays `ai.storyteller.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 |
|
||||
|
||||
|
Before Width: | Height: | Size: 9.4 KiB After Width: | Height: | Size: 9.4 KiB |
|
Before Width: | Height: | Size: 660 B After Width: | Height: | Size: 660 B |
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 21 KiB |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
|
Before Width: | Height: | Size: 48 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 4.0 KiB After Width: | Height: | Size: 4.0 KiB |
@@ -27,9 +27,6 @@ error = { master = "status_error", accent = "surface" }
|
||||
warning = { master = "status_warning", accent = "mesh" }
|
||||
ground = "c_fix"
|
||||
|
||||
# Visual style: Shaded with edges is the generic style icon.
|
||||
style_shaded_edges = "style"
|
||||
|
||||
# Same finished drawing, different semantic operation. Colours live only in palette.toml.
|
||||
sketch_break = { master = "break", accent = "sketch" }
|
||||
sketch_cancel = { master = "close", accent = "sketch" }
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
accessibility false
|
||||
activate false
|
||||
agent_chat false
|
||||
align false
|
||||
ambient_occlusion false
|
||||
appearance false
|
||||
arc3 false
|
||||
arc_center false
|
||||
@@ -85,8 +83,6 @@ decal false
|
||||
delete false
|
||||
delete_face false
|
||||
dimension false
|
||||
display false
|
||||
dots false
|
||||
draft false
|
||||
draft_analysis false
|
||||
drive false
|
||||
@@ -114,9 +110,7 @@ folder false
|
||||
frame_member false
|
||||
gear false
|
||||
graph false
|
||||
grid false
|
||||
ground false
|
||||
ground_shadow false
|
||||
help false
|
||||
hem false
|
||||
hole false
|
||||
@@ -216,11 +210,6 @@ section false
|
||||
select false
|
||||
send false
|
||||
settings false
|
||||
settings_keyboard false
|
||||
settings_monitor false
|
||||
settings_mouse false
|
||||
settings_palette false
|
||||
settings_plug false
|
||||
sheet_rules false
|
||||
shell false
|
||||
shield false
|
||||
@@ -258,11 +247,6 @@ star false
|
||||
status_error false
|
||||
status_recovery false
|
||||
style false
|
||||
style_hidden_edges false
|
||||
style_shaded false
|
||||
style_shaded_edges false
|
||||
style_translucent false
|
||||
style_wireframe false
|
||||
suppress false
|
||||
surface false
|
||||
surface_blend false
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
"agent_chat": "4a9ae0e7e2c360b4858b3175674c93d096705c92318fecd481e849f3ae650bf4",
|
||||
"agent_link": "2f20d89a0e3e9b28744910dddf7f282a1099ccd91aa160c9841c5b1d9ea9ed0f",
|
||||
"align": "05615db7dd9e4be56ae898e59a68c548b8a8447794442a9a394940651204bc4f",
|
||||
"ambient_occlusion": "973f8ef2326aeaf0face576e5a2c79c7ef3d13638b2c51a5aad199e4ef7edb02",
|
||||
"appearance": "b3162edd92291a5ef402e8499cd52be05beb2fa7eda641163c3f6e98325de2bf",
|
||||
"arc3": "fdca3d501fd557ac989376a12e9c4fa8231fde2b455c20724d1079a466153e9c",
|
||||
"arc_center": "3a5fdeb1d64fc6741f944c4435870b05fccab62293e270da692c6681155cbc51",
|
||||
@@ -89,7 +88,6 @@
|
||||
"dimension": "a926b7c64bc8ca99d8d6dcbb81b95235bd3fc34fbed049aca8b01b4d1147c15d",
|
||||
"disclosure_closed": "49a350c6f2efae78de0be96c524ebee1fd1291ea449a84224b88d37164a4e807",
|
||||
"disclosure_open": "d13ce3f97e3e362a9904fa2bf3412f3f00e4136bc23538b92d41483961d280de",
|
||||
"display": "58b45e5e2230eaf2c80f74b3be2e02a23714c90d6fb03b59ed093a1949993163",
|
||||
"dots": "600f7ff209d93145fc938bc4f6a1e34473e6b9ee22a5b4718f133bc7da5ee58d",
|
||||
"draft": "9c0fc361cacd0ad95ccd54794f4825dfc9901a83a3de752a588faec365d0ee09",
|
||||
"draft_analysis": "eb39c0f90e9cf226da6307365a76015e900dd692dc3f0fdc8f02fe717226261f",
|
||||
@@ -119,9 +117,7 @@
|
||||
"folder": "c19f5230df45a1bda1e92e89de38bba21c91e6eca0517eb15847a7712012d1e5",
|
||||
"gear": "25a1ca48a587850da1104a46005a330873ed196246e3d04e12cf61f26ad2b0d5",
|
||||
"graph": "7156caedd2dbfa2ae53da18b7a2204b8db66c45126859d36caf049a3fd4335a4",
|
||||
"grid": "084b5040d57e9d5183b3db1fad0d17a13ab5bae64676eef703cc06622f9c7438",
|
||||
"ground": "23e2471a9d6872e83028e0e219e330cef1930a4d89456a0fefa1bda8a7e3eb81",
|
||||
"ground_shadow": "a96809ed87f5047c2d7f1274908ed7b65b180613322ac9c10eb0f9d6296c1b66",
|
||||
"help": "8ce4060577483dbdb510be147d2827de50ac20029354008fca55abc0c7edf05d",
|
||||
"hem": "f9b1aafbedcf3dc90c26be308814638e0095fbe31b0647092507343fa60af011",
|
||||
"hole": "f82d47751f826e08682db847a270897474661bfe9f35b6d3678f49a203172563",
|
||||
@@ -267,12 +263,6 @@
|
||||
"screw": "5e30570785360d6a591ea22862a82f1cf62228385c1e96382536db8a453ac791",
|
||||
"section": "cdd13f99f15f6d59bfa21c747810608d6afd00cb8207faf0337928c8521e7eae",
|
||||
"select": "fdf6431270006ca7409c60c37cfed59572de04229f104ec723e6010ffa2b7163",
|
||||
"select_bodies": "8e6e61e083e6097535da3b98d41b607b40fc81b72bbd8c7fb903618c5b35c568",
|
||||
"select_components": "3e6cc847b334e92ce7ec5e5f35dfa929572e0974239790d76563ca4e20b26789",
|
||||
"select_faces_edges": "7b99b5370b1647bf8cea57df7a1053caab641c2f4a94c4d797b9679e6f38d6f1",
|
||||
"select_features": "b050bc19ce86d135cbe50998be681555110e92e179c4c7b101170c6b62c19943",
|
||||
"select_parts": "569adc5595a64187411d8ffeea69eab3b76ddfa33aeead6fd728efdd993bcccc",
|
||||
"select_sketch": "b0605b34a14d4173f7ed80d877109ec0b891a1b395bcab5743a603ed2eae240a",
|
||||
"send": "f83b6cdab792f9567a81d46d7828ed20795513c57a2c7d00fbbe6bd14d2f5848",
|
||||
"settings": "9193dd3a1007c43660d0d47b6499cb2571338b67949e1a99df112057b9bf2b8a",
|
||||
"settings_keyboard": "4968c70788094365af9c6e2b1c15b2aac45cf21212a14b8a256daf1ffe17f41a",
|
||||
@@ -319,11 +309,6 @@
|
||||
"stitch": "6c3205d64007b771ed78d6f8055867578439fb25d591563b32dbd3343c98732d",
|
||||
"stop": "36967eb4d423370d0d6c679b0a71f8d09f4fb2c73eed3e8cc50c604c44b0c32b",
|
||||
"style": "6c2b04234b82ea4858c80190cc7208c60f549358253005afc35ab4e29506b578",
|
||||
"style_hidden_edges": "a707dfc0bdcabe9144865f569abfcb478e493143cbd4331efc1404cf3082f6d2",
|
||||
"style_shaded": "6e5f8f9a9030ab3717f8b2192bbfd3f6b515ad398acf6cb41bc24f1298bc73b4",
|
||||
"style_shaded_edges": "6c2b04234b82ea4858c80190cc7208c60f549358253005afc35ab4e29506b578",
|
||||
"style_translucent": "c46b0cd485cfb9023514908357048b5cba9017a0c4976239da1e98f50e4ab5f9",
|
||||
"style_wireframe": "af7e575d378b5b6850bee9d3ca384efab95c8d4c876c5e3524f3ea771c4380a3",
|
||||
"suppress": "c54fbc01c12c0175d78cd63f692e8aaf73fc2cac79752b2a3ad821ac32734343",
|
||||
"surface": "ba0e839ce0ee03e7ff95a2389a5ecb20bd80c713064d9b1bd5f461f4a80d1921",
|
||||
"surface_blend": "0642f3b4af7d22f379a3c74f54c7b1c9eef0bdda19372ce4df73c0e56bdd7bbc",
|
||||
|
||||
@@ -1,7 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Ambient occlusion</title>
|
||||
<g opacity="0.22"><path d="M13 51L51 51A38 38 0 0 0 13 13Z" fill="currentColor" stroke="currentColor" stroke-width="0" stroke-linejoin="round" stroke-linecap="round"/></g>
|
||||
<g opacity="0.38"><path d="M13 51L40 51A27 27 0 0 0 13 24Z" fill="currentColor" stroke="currentColor" stroke-width="0" stroke-linejoin="round" stroke-linecap="round"/></g>
|
||||
<g opacity="0.62"><path d="M13 51L29 51A16 16 0 0 0 13 35Z" fill="currentColor" stroke="currentColor" stroke-width="0" stroke-linejoin="round" stroke-linecap="round"/></g>
|
||||
<path d="M13 10L13 51L54 51" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 758 B |
@@ -1,8 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Display</title>
|
||||
<path d="M10 22L54 22M10 42L54 42" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="24" cy="22" r="10.5" fill="#ffffff"/>
|
||||
<circle cx="42" cy="42" r="10.5" fill="#ffffff"/>
|
||||
<circle cx="24" cy="22" r="6.4" fill="currentColor"/>
|
||||
<circle cx="42" cy="42" r="6.4" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 448 B |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Grid</title>
|
||||
<path d="M12 12L52 12L52 52L12 52Z" fill="none" stroke="currentColor" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M25.3 12L25.3 52M38.7 12L38.7 52M12 25.3L52 25.3M12 38.7L52 38.7" fill="none" stroke="currentColor" stroke-width="4" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 407 B |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Ground shadow</title>
|
||||
<path d="M32 4.8 L48.1 13.9 L48.1 32.1 L32 41.2 L15.9 32.1 L15.9 13.9Z" fill="currentColor" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9.34 31.35A25 11 0 1 0 54.66 31.35" fill="none" stroke="currentColor" stroke-width="5.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 433 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Bodies</title>
|
||||
<path d="M32 6 L55 19 L55 45 L32 58 L9 45 L9 19Z" fill="currentColor" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 254 B |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Components</title>
|
||||
<path d="M22.91 5.75 L41.09 16.25 L22.91 26.75 L4.72 16.25ZM4.72 16.25 L22.91 26.75 L22.91 47.75 L4.72 37.25ZM41.09 16.25 L59.28 26.75 L41.09 37.25 L22.91 26.75ZM22.91 26.75 L41.09 37.25 L41.09 58.25 L22.91 47.75ZM59.28 26.75 L41.09 37.25 L41.09 58.25 L59.28 47.75Z" fill="currentColor" stroke="currentColor" stroke-width="4.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.46 18.35L22.91 26.75L22.91 43.55" fill="none" stroke="#ffffff" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 621 B |
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Faces and edges</title>
|
||||
<path d="M32 6 L55 19 L32 32 L9 19Z" fill="currentColor" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 6 L55 19 L55 45 L32 58 L9 45 L9 19Z" fill="none" stroke="currentColor" stroke-width="4.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L9 19M32 32L55 19M32 32L32 58" fill="none" stroke="currentColor" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 554 B |
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Features</title>
|
||||
<path d="M32 6 L55 19 L55 45 L32 58 L9 45 L9 19Z" fill="none" stroke="currentColor" stroke-width="4.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9 19L32 32M32 32L55 19M32 32L32 58" fill="none" stroke="currentColor" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M42 19C42 22.2 37.52 24.8 32 24.8C26.48 24.8 22 22.2 22 19C22 15.8 26.48 13.2 32 13.2C37.52 13.2 42 15.8 42 19Z" fill="currentColor" stroke="currentColor" stroke-width="0.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 632 B |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Parts</title>
|
||||
<path d="M22.91 5.75 L41.09 16.25 L22.91 26.75 L4.72 16.25ZM4.72 16.25 L22.91 26.75 L22.91 47.75 L4.72 37.25Z" fill="none" stroke="currentColor" stroke-width="4.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M41.09 16.25 L59.28 26.75 L41.09 37.25 L22.91 26.75ZM22.91 26.75 L41.09 37.25 L41.09 58.25 L22.91 47.75ZM59.28 26.75 L41.09 37.25 L41.09 58.25 L59.28 47.75Z" fill="currentColor" stroke="currentColor" stroke-width="4.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 585 B |
@@ -1,10 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Sketch features</title>
|
||||
<path d="M10 46C18 14 46 14 54 46" fill="none" stroke="currentColor" stroke-width="5" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="10" cy="46" r="8.6" fill="#ffffff"/>
|
||||
<circle cx="54" cy="46" r="8.6" fill="#ffffff"/>
|
||||
<circle cx="32" cy="22.5" r="8.6" fill="#ffffff"/>
|
||||
<circle cx="10" cy="46" r="5.8" fill="currentColor"/>
|
||||
<circle cx="54" cy="46" r="5.8" fill="currentColor"/>
|
||||
<circle cx="32" cy="22.5" r="5.8" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 563 B |
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Shaded with hidden edges</title>
|
||||
<path d="M32 6 L55 19 L55 45 L32 58 L9 45 L9 19Z" fill="currentColor" stroke="currentColor" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L15.44 22.64M32 32L48.56 22.64M32 32L32 50.72" fill="none" stroke="#ffffff" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L32 13.28M32 32L48.56 41.36M32 32L15.44 41.36" fill="none" stroke="#ffffff" stroke-width="3.2" stroke-linejoin="round" stroke-linecap="round" stroke-dasharray="4.5 4"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 619 B |
@@ -1,6 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Shaded</title>
|
||||
<g opacity="0.5"><path d="M32 6 L55 19 L32 32 L9 19Z" fill="currentColor" stroke="currentColor" stroke-width="0.8" stroke-linejoin="round" stroke-linecap="round"/></g>
|
||||
<g opacity="0.8"><path d="M9 19 L32 32 L32 58 L9 45Z" fill="currentColor" stroke="currentColor" stroke-width="0.8" stroke-linejoin="round" stroke-linecap="round"/></g>
|
||||
<path d="M32 32 L55 19 L55 45 L32 58Z" fill="currentColor" stroke="currentColor" stroke-width="0.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 583 B |
@@ -1,10 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Translucent</title>
|
||||
<defs>
|
||||
<clipPath id="tr-clip"><path d="M32 8.6 L52.7 20.3 L52.7 43.7 L32 55.4 L11.3 43.7 L11.3 20.3Z"/></clipPath>
|
||||
</defs>
|
||||
<g clip-path="url(#tr-clip)"><path d="M-71 72L9 -8M-60.5 72L19.5 -8M-50 72L30 -8M-39.5 72L40.5 -8M-29 72L51 -8M-18.5 72L61.5 -8M-8 72L72 -8M2.5 72L82.5 -8M13 72L93 -8M23.5 72L103.5 -8M34 72L114 -8M44.5 72L124.5 -8M55 72L135 -8" fill="none" stroke="currentColor" stroke-width="2.8" stroke-linejoin="round" stroke-linecap="round"/></g>
|
||||
<path d="M32 6 L55 19 L55 45 L32 58 L9 45 L9 19Z" fill="none" stroke="currentColor" stroke-width="5.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L11.76 20.56M32 32L52.24 20.56M32 32L32 54.88" fill="none" stroke="#ffffff" stroke-width="6.4" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L11.3 20.3M32 32L52.7 20.3M32 32L32 55.4" fill="none" stroke="currentColor" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Wireframe</title>
|
||||
<path d="M32 6 L55 19 L55 45 L32 58 L9 45 L9 19Z" fill="none" stroke="currentColor" stroke-width="5.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L32 6M32 32L55 19M32 32L55 45M32 32L32 58M32 32L9 45M32 32L9 19" fill="none" stroke="currentColor" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 433 B |
@@ -1106,7 +1106,7 @@
|
||||
"home.new_mm": "Nyt design",
|
||||
"home.new_mm_hint": "Millimeter · åbner i Design",
|
||||
"home.open": "Åbn…",
|
||||
"home.open_hint": "Et .sc-design ({shortcut})",
|
||||
"home.open_hint": "Et .solidcraft-design ({shortcut})",
|
||||
"home.open_now": "Åbn nu",
|
||||
"home.opened_days_ago": "For {count} dage siden",
|
||||
"home.opened_hours_ago": "For {count} t siden",
|
||||
@@ -1190,9 +1190,7 @@
|
||||
"nav.free_orbit.help": "Musen og 3D-musen roterer gennem begge poler og bevarer rulning. Snap til akser med Shift er slået fra.",
|
||||
"nav.free_orbit.label": "Fri rotation af visningen",
|
||||
"nav.fusion.help": "Træk med midterste museknap panorerer, Shift+midterste roterer visningen, træk med højre roterer visningen",
|
||||
"nav.hfss.help": "Alt+træk med venstre museknap panorerer, Shift+Alt+venstre zoomer, Ctrl+træk med venstre fra et tomt område roterer visningen",
|
||||
"nav.inventor.help": "Træk med midterste museknap panorerer, Shift+midterste roterer visningen, Ctrl+midterste zoomer, træk med højre roterer visningen",
|
||||
"nav.maya.help": "Alt+træk med venstre museknap roterer visningen, Alt+midterste panorerer, Alt+højre zoomer; træk med midterste museknap panorerer også",
|
||||
"nav.openscad.help": "Træk med venstre museknap i et tomt område roterer visningen, Shift+venstre markerer med en ramme, træk i skitsegeometri redigerer den; træk med højre eller midterste museknap panorerer, Shift+højre eller Shift+midterste zoomer",
|
||||
"nav.solidworks.help": "Træk med midterste museknap roterer visningen, Ctrl+midterste panorerer, Shift+midterste zoomer, træk med højre roterer visningen",
|
||||
"nav.tinkercad.help": "Træk med højre museknap roterer visningen, træk med midterste eller Shift+højre panorerer",
|
||||
@@ -1211,11 +1209,6 @@
|
||||
"no picture": "intet billede",
|
||||
"none": "ingen",
|
||||
"parts.automatic_name": "automatisk",
|
||||
"parts.dimension.d": "Nominel diameter",
|
||||
"parts.dimension.height": "Højde",
|
||||
"parts.dimension.pitch": "Stigning",
|
||||
"parts.dimension.thickness": "Tykkelse",
|
||||
"parts.dimension.width": "Bredde",
|
||||
"pattern.alignment_2": "Justering 2",
|
||||
"pattern.alignment_hint": "Antal omfatter originalen. Centrér fordeler kopierne ligeligt; Vend vælger siden med den ekstra kopi ved et lige samlet antal.",
|
||||
"pattern.direction_1": "Retning 1",
|
||||
@@ -1331,12 +1324,6 @@
|
||||
"prefs.supersampling_brief": "Tegner visningen i større størrelse og nedskalerer den med filtrering: meget skarpt og mere krævende.",
|
||||
"prefs.tab_accent_note": "Accentfarven følger den aktive fane",
|
||||
"prefs.theme_file": "temafil",
|
||||
"prefs.theme_follow": "Følg en temafil",
|
||||
"prefs.theme_follow_error": "Ikke anvendt: {error}. Det seneste gyldige tema bevares.",
|
||||
"prefs.theme_follow_launch": "Følges kun denne gang (--theme-file); genindlæses, når et andet værktøj overskriver filen.",
|
||||
"prefs.theme_follow_note": "Genindlæses, når et andet værktøj overskriver filen. Valg af et tema stopper dette.",
|
||||
"prefs.theme_follow_off": "Fra",
|
||||
"prefs.theme_follow_stop": "Stop med at følge",
|
||||
"prefs.timeline_half_hour": "−30 min",
|
||||
"prefs.timeline_now": "nu",
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
@@ -2064,7 +2051,6 @@
|
||||
"ui.heading_utilities": "VÆRKTØJER",
|
||||
"ui.heading_utility": "VÆRKTØJ",
|
||||
"ui.heading_weldments": "SVEJSNING",
|
||||
"ui.height": "Højde",
|
||||
"ui.help": "Hjælp",
|
||||
"ui.hide": "Skjul",
|
||||
"ui.hideAxes": "Skjul akser",
|
||||
@@ -2103,7 +2089,6 @@
|
||||
"ui.language": "Sprog",
|
||||
"ui.layout_compact": "Kompakt",
|
||||
"ui.layout_floating": "Flydende",
|
||||
"ui.length": "Længde",
|
||||
"ui.length_decimals": "Længdedecimaler",
|
||||
"ui.light": "Lys",
|
||||
"ui.line": "Linje",
|
||||
@@ -2191,7 +2176,6 @@
|
||||
"ui.pinToShortcuts": "Fastgør til genveje",
|
||||
"ui.pinToToolbar": "Fastgør til værktøjslinje",
|
||||
"ui.pipe": "Rør",
|
||||
"ui.pitch": "Stigning",
|
||||
"ui.placement": "Placering",
|
||||
"ui.plane": "Plan",
|
||||
"ui.plane_at_angle": "Plan i vinkel",
|
||||
@@ -2375,7 +2359,6 @@
|
||||
"ui.visual_shaded_with_edges": "Skyggelagt med kanter",
|
||||
"ui.visual_style": "Visuel stil",
|
||||
"ui.visual_wireframe": "Trådmodel",
|
||||
"ui.width": "Bredde",
|
||||
"ui.workspace": "arbejdsområde",
|
||||
"ui.workspace_animation_description": "Eksploderede visninger, bevægelsesstudier og storyboards",
|
||||
"ui.workspace_available": "Tilgængelig",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"Base Center": "Base Centre",
|
||||
"Center X": "Centre X",
|
||||
"Center Y": "Centre Y",
|
||||
"Center of mass": "Centre of mass",
|
||||
@@ -17,12 +16,6 @@
|
||||
"cmd.sketch_slot_center_point": "Centre Point Slot",
|
||||
"cmd.sketch_slot_center_to_center": "Centre to Centre Slot",
|
||||
"pattern.alignment_hint": "Counts include the original. Centre balances copies; Flip chooses the extra side for even totals.",
|
||||
"prefs.theme_follow": "Follow a theme file",
|
||||
"prefs.theme_follow_error": "Not applied: {error}. The last good theme stays on.",
|
||||
"prefs.theme_follow_launch": "Followed for this run only (--theme-file); reloaded whenever another tool rewrites it.",
|
||||
"prefs.theme_follow_note": "Reloaded whenever another tool rewrites it. Choosing a theme stops following it.",
|
||||
"prefs.theme_follow_off": "Off",
|
||||
"prefs.theme_follow_stop": "Stop following",
|
||||
"render.aging_add": "Add aging layer",
|
||||
"render.aging_airborne": "Airborne dust / participating medium",
|
||||
"render.aging_airborne_help": "Airborne dust is a separate finite homogeneous scattering volume. It changes transport and raw EXR, not the surface-aging stack. Existing volume/glass/denoising restrictions remain explicit; no atmosphere is added automatically.",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
{
|
||||
"(active rule)": "(regla activa)",
|
||||
"0 turns it off": "0 lo desactiva",
|
||||
"1. Open GitHub draft below, then paste the report copied to your clipboard.": "1. Abre el borrador de GitHub de abajo y pega el informe copiado al portapapeles.",
|
||||
"1. Open GitHub draft below. Your title and report will be filled in.": "1. Abre el borrador de GitHub de abajo. El título y el informe se rellenarán automáticamente.",
|
||||
@@ -36,16 +35,12 @@
|
||||
"Apply your appearance first. Exports assigned appearances and materials created in this design.": "Aplique primero la apariencia. Exporta las apariencias asignadas y los materiales creados en este diseño.",
|
||||
"Arc length": "Longitud del arco",
|
||||
"Area": "Área",
|
||||
"As Picked": "Según la selección",
|
||||
"As-Built": "Tal como se construyó",
|
||||
"Assign Rule": "Asignar regla",
|
||||
"Attachment": "Vinculación",
|
||||
"BLEND SURFACE": "SUPERFICIE DE TRANSICIÓN",
|
||||
"BOUNDARY SURFACE": "SUPERFICIE DE CONTORNO",
|
||||
"BROWSER": "EXPLORADOR",
|
||||
"Ball": "Rótula",
|
||||
"Base Height": "Altura de la base",
|
||||
"Bend Line": "Línea de plegado",
|
||||
"Bend Line Position": "Posición de la línea de plegado",
|
||||
"Bend Position": "Posición del pliegue",
|
||||
"Bend Radius": "Radio de plegado",
|
||||
@@ -53,7 +48,6 @@
|
||||
"Bends": "Pliegues",
|
||||
"Bodies": "Cuerpos",
|
||||
"Body": "Cuerpo",
|
||||
"Both": "Ambos",
|
||||
"Boundary": "Contorno",
|
||||
"Bounding box": "Caja delimitadora",
|
||||
"CHECK": "COMPROBAR",
|
||||
@@ -76,7 +70,6 @@
|
||||
"Checked": "Comprobado",
|
||||
"Choose a new plane. Sketch geometry and dimensions stay unchanged in its two-dimensional coordinates.": "Elige un plano nuevo. La geometría y las cotas del boceto se mantienen en sus coordenadas bidimensionales.",
|
||||
"Choose…": "Elegir…",
|
||||
"Circle or sphere": "Círculo o esfera",
|
||||
"Circular": "Circular",
|
||||
"Circumference": "Circunferencia",
|
||||
"Clockwise": "Horario",
|
||||
@@ -88,7 +81,6 @@
|
||||
"Config Table": "Tabla de config.",
|
||||
"Configuration": "Configuración",
|
||||
"Configuration rules": "Reglas de configuración",
|
||||
"Constant radius": "Radio constante",
|
||||
"Contact": "Contacto",
|
||||
"Contact Sets": "Conjuntos de contacto",
|
||||
"Contact sets": "Conjuntos de contacto",
|
||||
@@ -113,8 +105,6 @@
|
||||
"Curvature (G2)": "Curvatura (G2)",
|
||||
"Curvature change (G3)": "Cambio de curvatura (G3)",
|
||||
"Customize Toolbar": "Personalizar barra de herramientas",
|
||||
"Cylindrical": "Cilíndrica",
|
||||
"Deboss": "Grabado",
|
||||
"Delete": "Eliminar",
|
||||
"Delete all": "Eliminar todo",
|
||||
"Delete rule": "Eliminar regla",
|
||||
@@ -132,7 +122,6 @@
|
||||
"Double-clicking the middle button in the view: Home (the home view, framing what is shown), Fit (frame what is shown, keeping the orientation) or nothing": "Doble clic con el botón central en la vista: Inicio (vista inicial que encuadra lo visible), Ajustar (encuadrar sin cambiar la orientación) o nada",
|
||||
"Drive Joint": "Accionar unión",
|
||||
"Duration": "Duración",
|
||||
"Edge": "Arista",
|
||||
"Edges": "Aristas",
|
||||
"Edit": "Editar",
|
||||
"Edit Profile Sketch": "Editar boceto del perfil",
|
||||
@@ -140,7 +129,6 @@
|
||||
"Enabled": "Activado",
|
||||
"End bulge": "Abombamiento final",
|
||||
"End continuity": "Continuidad final",
|
||||
"Equal Distance": "Distancia igual",
|
||||
"Explode": "Explosionar",
|
||||
"Explode Text": "Descomponer texto",
|
||||
"Export Animation…": "Exportar animación…",
|
||||
@@ -160,8 +148,6 @@
|
||||
"Face attachment lost": "Se ha perdido la vinculación de la cara",
|
||||
"Faces": "Caras",
|
||||
"Faces / edges / vertices": "Caras / aristas / vértices",
|
||||
"Faces/Body": "Caras/Cuerpo",
|
||||
"Factor must be a positive number": "El factor debe ser un número positivo",
|
||||
"Fastener": "Sujeción",
|
||||
"Favorites": "Favoritos",
|
||||
"Favourite": "Favorito",
|
||||
@@ -170,28 +156,20 @@
|
||||
"Filter": "Filtrar",
|
||||
"Find in Browser": "Buscar en el explorador",
|
||||
"Finish": "Acabado",
|
||||
"First edge": "Primera arista",
|
||||
"First point": "Primer punto",
|
||||
"Flange Gap": "Separación de pestaña",
|
||||
"Flat Pattern": "Desarrollo plano",
|
||||
"Flat size": "Tamaño del desarrollo",
|
||||
"Follow camera (Look At sketches)": "Seguir cámara (orientar hacia los bocetos)",
|
||||
"Follow the camera (Look At sketches)": "Seguir la cámara (orientar hacia los bocetos)",
|
||||
"Font": "Fuente",
|
||||
"Free Move": "Movimiento libre",
|
||||
"From Bodies": "Desde cuerpos",
|
||||
"From Snap": "Desde el enganche",
|
||||
"From edges": "Aristas de origen",
|
||||
"Full Length": "Longitud total",
|
||||
"Gap": "Separación",
|
||||
"Grid": "Cuadrícula",
|
||||
"HISTORY": "HISTORIAL",
|
||||
"Height": "Altura",
|
||||
"Hem Gap": "Separación de reborde",
|
||||
"Hidden edges": "Aristas ocultas",
|
||||
"History layout": "Disposición del historial",
|
||||
"Hole Depth": "Profundidad del agujero",
|
||||
"Hole Diameter": "Diámetro del agujero",
|
||||
"Holes": "Agujeros",
|
||||
"Holes (edges on them)": "Agujeros (aristas que contienen)",
|
||||
"Home view": "Vista de inicio",
|
||||
@@ -207,14 +185,10 @@
|
||||
"Isocurve": "Isocurva",
|
||||
"Issue title": "Título de la incidencia",
|
||||
"JOINTS": "UNIONES",
|
||||
"Joint 1": "Unión 1",
|
||||
"Joint 2": "Unión 2",
|
||||
"Joint Origin1": "Origen de unión1",
|
||||
"K Factor": "Factor K",
|
||||
"Keep (points on it)": "Conservar (puntos sobre ella)",
|
||||
"Keep Tools": "Conservar herramientas",
|
||||
"Keep of the other surface": "Conservar de la otra superficie",
|
||||
"Knife Edge": "Borde afilado",
|
||||
"LIBRARY": "BIBLIOTECA",
|
||||
"LINKS": "VÍNCULOS",
|
||||
"Later": "Más tarde",
|
||||
@@ -225,9 +199,7 @@
|
||||
"Letter Spacing": "Espaciado entre letras",
|
||||
"Library": "Biblioteca",
|
||||
"Library file": "Archivo de biblioteca",
|
||||
"Library response is missing": "Falta la respuesta de la biblioteca",
|
||||
"Line Spacing": "Interlineado",
|
||||
"Line on plane": "Línea en plano",
|
||||
"Local library": "Biblioteca local",
|
||||
"Loop": "Bucle",
|
||||
"MANAGE": "GESTIONAR",
|
||||
@@ -241,12 +213,9 @@
|
||||
"Material": "Material",
|
||||
"Material copied. Existing body assignments are unchanged.": "Material copiado. Se conservan las asignaciones actuales de los cuerpos.",
|
||||
"Max": "Máx.",
|
||||
"Max Thickness": "Espesor máx.",
|
||||
"Mid Radius": "Radio intermedio",
|
||||
"Middle double-click": "Doble clic central",
|
||||
"Min": "Mín.",
|
||||
"Min Draft": "Ángulo de salida mín.",
|
||||
"Min Thickness": "Espesor mín.",
|
||||
"Mitre": "Inglete",
|
||||
"Model Parameters": "Parámetros del modelo",
|
||||
"Modeled": "Modelado",
|
||||
@@ -261,7 +230,6 @@
|
||||
"Name": "Nombre",
|
||||
"Name on import": "Nombre al importar",
|
||||
"Nearest": "Más cercano",
|
||||
"Neutral plane": "Plano neutro",
|
||||
"New": "Nuevo",
|
||||
"New Comp.": "Nuevo comp.",
|
||||
"New Sketch": "Nuevo boceto",
|
||||
@@ -272,7 +240,6 @@
|
||||
"No matching appearances": "No hay apariencias coincidentes",
|
||||
"No matching physical materials.": "No hay materiales físicos coincidentes.",
|
||||
"No visible bodies or sketches to export.": "No hay cuerpos ni bocetos visibles para exportar.",
|
||||
"Nominal Radius": "Radio nominal",
|
||||
"None": "Ninguno",
|
||||
"Not available yet": "Todavía no disponible",
|
||||
"Notch": "Muesca",
|
||||
@@ -288,25 +255,19 @@
|
||||
"Overall length": "Longitud total",
|
||||
"Param. Graph": "Gráfico de parám.",
|
||||
"Parameter": "Parámetro",
|
||||
"Part length must be greater than zero and at most 2000 mm": "La longitud de la pieza debe ser mayor que cero y como máximo 2000 mm",
|
||||
"Parts": "Piezas",
|
||||
"Pass through": "Pasante",
|
||||
"Path": "Trayectoria",
|
||||
"Pattern": "Patrón",
|
||||
"Per Edge": "Por arista",
|
||||
"Physical material definitions": "Definiciones de materiales físicos",
|
||||
"Pick a part size": "Elige un tamaño de pieza",
|
||||
"Pick the tool bodies": "Selecciona los cuerpos herramienta",
|
||||
"Picked": "Seleccionado",
|
||||
"Picked point": "Punto seleccionado",
|
||||
"Pin-slot": "Pasador-ranura",
|
||||
"Place at the origin instead": "Colocar en el origen",
|
||||
"Planar": "Plana",
|
||||
"Plastic Rules": "Reglas de plástico",
|
||||
"Playback": "Reproducción",
|
||||
"Plot": "Graficar",
|
||||
"Point on line": "Punto en línea",
|
||||
"Point on plane": "Punto en plano",
|
||||
"Position": "Posición",
|
||||
"Position (0 to 1)": "Posición (de 0 a 1)",
|
||||
"Position (G0)": "Posición (G0)",
|
||||
@@ -316,7 +277,6 @@
|
||||
"Preview leaves the current design unchanged.": "La vista previa no modifica el diseño actual.",
|
||||
"Profiles/Curves": "Perfiles/curvas",
|
||||
"Quantity": "Cantidad",
|
||||
"Quantity must be at least 2": "La cantidad debe ser al menos 2",
|
||||
"RECENT": "RECIENTES",
|
||||
"REFERENCE": "REFERENCIA",
|
||||
"RULED SURFACE": "SUPERFICIE REGLADA",
|
||||
@@ -328,8 +288,6 @@
|
||||
"Recover unsaved design?": "¿Recuperar el diseño sin guardar?",
|
||||
"Rectangular": "Rectangular",
|
||||
"Reference face": "Cara de referencia",
|
||||
"Relief Depth": "Profundidad de alivio",
|
||||
"Relief Width": "Ancho de alivio",
|
||||
"Remove Tools": "Eliminar herramientas",
|
||||
"Remove from list": "Quitar de la lista",
|
||||
"Remove override": "Quitar anulación",
|
||||
@@ -346,7 +304,6 @@
|
||||
"Restore the inherited component or physical material appearance": "Restaurar la apariencia heredada del componente o del material físico",
|
||||
"Result": "Resultado",
|
||||
"Retake screenshot": "Repetir captura de pantalla",
|
||||
"Reveal Height": "Altura del escalón",
|
||||
"Reverse": "Invertir",
|
||||
"Reverse Direction": "Invertir dirección",
|
||||
"Revert": "Revertir",
|
||||
@@ -354,10 +311,7 @@
|
||||
"Revert Pos.": "Revertir pos.",
|
||||
"Revert Position": "Revertir posición",
|
||||
"Review the report and screenshot before opening GitHub.": "Revisa el informe y la captura antes de abrir GitHub.",
|
||||
"Revolute": "Revolución",
|
||||
"Rigid": "Rígida",
|
||||
"Rim Edge": "Arista del borde",
|
||||
"Rim face": "Cara del borde",
|
||||
"Roll History Marker Here": "Desplazar marcador de la línea de tiempo hasta aquí",
|
||||
"Round": "Redondo",
|
||||
"Rule": "Regla",
|
||||
@@ -374,22 +328,11 @@
|
||||
"Search": "Buscar",
|
||||
"Search commands": "Buscar comandos",
|
||||
"Search this library": "Buscar en esta biblioteca",
|
||||
"Second edge": "Segunda arista",
|
||||
"Second point": "Segundo punto",
|
||||
"Section": "Sección",
|
||||
"Section position": "Posición de la sección",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleccionar otro: mantén pulsado el botón izquierdo sobre el modelo (o haz clic derecho › Seleccionar otro…) para ver todo lo que hay bajo el cursor; vuelve a pulsar en el mismo lugar para seleccionar el siguiente elemento. Haz doble clic con el botón central para volver a Inicio (Preferencias › Navegación).",
|
||||
"Select bodies (a face picks its body)": "Selecciona cuerpos (al seleccionar una cara se selecciona su cuerpo)",
|
||||
"Select bodies or faces instead": "Selecciona cuerpos o caras en su lugar",
|
||||
"Select points of the sketch being edited": "Selecciona puntos del boceto que se está editando",
|
||||
"Select the centre the pattern turns around first": "Selecciona primero el centro alrededor del cual gira el patrón",
|
||||
"Select the curves to offset first": "Selecciona primero las curvas que se van a desfasar",
|
||||
"Select the mirror line": "Selecciona la línea de simetría",
|
||||
"Select the sketch curves or points to mirror first": "Selecciona primero las curvas o puntos del boceto que se van a reflejar",
|
||||
"Select the sketch curves or points to move first": "Selecciona primero las curvas o puntos del boceto que se van a mover",
|
||||
"Select the sketch curves or points to pattern first": "Selecciona primero las curvas o puntos del boceto para el patrón",
|
||||
"Select the sketch curves or points to scale first": "Selecciona primero las curvas o puntos del boceto que se van a escalar",
|
||||
"Select the sketch curves or points to transform first": "Selecciona primero las curvas o puntos del boceto que se van a transformar",
|
||||
"Selection filter": "Filtro de selección",
|
||||
"Semantic faces share their appearance across instances.": "Las caras semánticas comparten su apariencia entre instancias.",
|
||||
"Send your report on GitHub": "Enviar tu informe a GitHub",
|
||||
@@ -407,11 +350,8 @@
|
||||
"Sketch geometry and profiles": "Geometría y perfiles del boceto",
|
||||
"Sketch plane has no body-face attachment.": "El plano del boceto no está vinculado a ninguna cara del cuerpo.",
|
||||
"Sketch points": "Puntos del boceto",
|
||||
"Slider": "Deslizante",
|
||||
"Slot width": "Anchura de la ranura",
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft se cerró antes de guardar estos diseños. Puedes abrir su último guardado automático.",
|
||||
"Spacing 2 must be a non-zero number": "El espaciado 2 debe ser un número distinto de cero",
|
||||
"Spacing must be a non-zero number": "El espaciado debe ser un número distinto de cero",
|
||||
"Specialise": "Especializar",
|
||||
"Speed": "Velocidad",
|
||||
"Square": "Cuadrado",
|
||||
@@ -421,7 +361,6 @@
|
||||
"Start angle": "Ángulo inicial",
|
||||
"Start bulge": "Abombamiento inicial",
|
||||
"Start continuity": "Continuidad inicial",
|
||||
"Stationary Side": "Lado fijo",
|
||||
"Step": "Paso",
|
||||
"Steps": "Pasos",
|
||||
"Stitch the kept pieces": "Coser las piezas conservadas",
|
||||
@@ -430,41 +369,29 @@
|
||||
"Style": "Estilo",
|
||||
"Suppressed": "Suprimido",
|
||||
"Surface": "Superficie",
|
||||
"Surfaces": "Superficies",
|
||||
"Swap Ends": "Intercambiar extremos",
|
||||
"TRIM": "RECORTAR",
|
||||
"Tangent (G1)": "Tangencia (G1)",
|
||||
"Tangent Chain": "Cadena tangente",
|
||||
"Target Face (optional)": "Cara de destino (opcional)",
|
||||
"Tear": "Desgarro",
|
||||
"The dashed sketch is a saved plane guide. Select a new face to repair the attachment.": "El boceto discontinuo sirve de guía del plano guardado. Selecciona una cara nueva para reparar la vinculación.",
|
||||
"The design changed. Reopen Change Part Size for the component.": "El diseño ha cambiado. Vuelve a abrir Cambiar tamaño de pieza para el componente.",
|
||||
"The direction must be an axis or a straight sketch curve that lies in the sketch plane": "La dirección debe ser un eje o una curva recta del boceto que esté en el plano del boceto",
|
||||
"The mirror line cannot also be mirrored": "La línea de simetría no puede reflejarse también",
|
||||
"The mirror line must be a straight curve of this sketch, not an origin axis": "La línea de simetría debe ser una curva recta de este boceto, no un eje del origen",
|
||||
"The saved author occurrence is missing. Select a valid author placement before rebinding.": "Falta la instancia de autor guardada. Selecciona una posición de autor válida antes de volver a vincular.",
|
||||
"Then": "Entonces",
|
||||
"These features will be deleted:": "Se eliminarán estas operaciones:",
|
||||
"These features will fail:": "Estas operaciones fallarán:",
|
||||
"Thickness Outside": "Espesor hacia fuera",
|
||||
"Thickness Variation": "Variación de espesor",
|
||||
"Thin": "Delgado",
|
||||
"Thin Extrude": "Extrusión delgada",
|
||||
"Third plane": "Tercer plano",
|
||||
"Third point": "Tercer punto",
|
||||
"This child is shared. Deleting it removes it from every copy of its parent.": "Este elemento secundario es compartido. Si lo eliminas, desaparecerá de todas las copias de su elemento principal.",
|
||||
"This child is shared. Position changes apply to every copy of its parent.": "Este elemento secundario es compartido. Los cambios de posición se aplican a todas las copias de su elemento principal.",
|
||||
"This face cannot provide a durable attachment. Choose another face or explicitly use a fixed plane.": "Esta cara no permite una vinculación persistente. Elige otra cara o utiliza expresamente un plano fijo.",
|
||||
"Through point": "Por un punto",
|
||||
"To Offset": "Hasta desfase",
|
||||
"To Snap": "Hasta el enganche",
|
||||
"To edges": "Aristas de destino",
|
||||
"Tolerance": "Tolerancia",
|
||||
"Too close": "Demasiado cerca",
|
||||
"Trimming tool (plane, face or curves)": "Herramienta de recorte (plano, cara o curvas)",
|
||||
"Turn off section analysis before exporting the view.": "Desactiva el análisis de sección antes de exportar la vista.",
|
||||
"Type": "Tipo",
|
||||
"Type a distance or an angle to move by": "Escribe una distancia o un ángulo para mover",
|
||||
"UNTRIM": "ANULAR RECORTE",
|
||||
"Unit": "Unidad",
|
||||
"Updating the preview": "Actualizando vista previa",
|
||||
@@ -493,17 +420,6 @@
|
||||
"Z angle": "Ángulo Z",
|
||||
"Z distance": "Distancia Z",
|
||||
"Zebra": "Cebra",
|
||||
"`joint` must be an id or a name": "`joint` debe ser un id o un nombre",
|
||||
"`params` must be an object": "`params` debe ser un objeto",
|
||||
"`values` must be a list": "`values` debe ser una lista",
|
||||
"a chamfer needs a corner of exactly two lines": "un chaflán necesita una esquina de exactamente dos líneas",
|
||||
"a file operation is in progress": "hay una operación de archivo en curso",
|
||||
"a picked curve is no longer in a sketch": "una curva elegida ya no está en un boceto",
|
||||
"a picked curve's sketch has not been solved": "el boceto de una curva elegida no se ha resuelto",
|
||||
"a picked edge has no points": "una arista elegida no tiene puntos",
|
||||
"a picked edge is gone": "una arista elegida ya no existe",
|
||||
"a picked edge's body is gone": "el cuerpo de una arista elegida ya no existe",
|
||||
"add a key for a joint first": "añade primero una clave para una unión",
|
||||
"angle, height": "ángulo, altura",
|
||||
"appearance.dielectric_ior": "Índice de refracción dieléctrico",
|
||||
"appearance.metallic": "Metalicidad",
|
||||
@@ -512,7 +428,6 @@
|
||||
"appearance.pbr_opaque": "PBR opaco",
|
||||
"appearance.roughness": "Rugosidad",
|
||||
"appearance.surface_response": "Respuesta de la superficie",
|
||||
"bom.group_children": "Subpiezas en la fila de su pieza",
|
||||
"browser.body_name": "Cuerpo{number}",
|
||||
"browser.calibrate_title": "Calibrar: {name}",
|
||||
"browser.capture_many": "Se han movido {count} componentes, pero sus posiciones no se han capturado.",
|
||||
@@ -523,8 +438,6 @@
|
||||
"browser.move_title": "Mover: {name}",
|
||||
"browser.parameters": "Parámetros ({count})",
|
||||
"browser.precision": "Precisión: {precision}",
|
||||
"browser.sketch_attachment_lost": "{name} (fijación perdida)",
|
||||
"browser.sketch_editing": "{name} (en edición)",
|
||||
"browser.sketch_name": "Boceto{number}",
|
||||
"browser.units": "Unidades: {units}",
|
||||
"cam.flat": "Cara plana",
|
||||
@@ -534,11 +447,6 @@
|
||||
"cam.roller": "Rodillo",
|
||||
"cam.roller_pick": "Rodillo (opcional)",
|
||||
"cam.tip_pick": "Punta (opcional)",
|
||||
"choose a profile": "elige un perfil",
|
||||
"choose the cam's and the follower's joints": "elige las uniones de la leva y del seguidor",
|
||||
"choose the joints to couple": "elige las uniones que acoplar",
|
||||
"click a plane or planar face": "hacer clic en un plano o una cara plana",
|
||||
"click sketch curves": "hacer clic en curvas del boceto",
|
||||
"cmd.appearance_assign": "Apariencia",
|
||||
"cmd.appearance_library": "Biblioteca de apariencias",
|
||||
"cmd.appearance_list": "Listar apariencias",
|
||||
@@ -851,7 +759,6 @@
|
||||
"cmd.sketch_spline_control_point_5": "Spline por puntos de control (grado 5)",
|
||||
"cmd.sketch_spline_fit_point": "Spline por puntos de ajuste",
|
||||
"cmd.sketch_spun_profile": "Perfil de revolución",
|
||||
"cmd.sketch_toggle_diameter": "Alternar radio / diámetro",
|
||||
"cmd.sketch_toggle_driven": "Alternar cota de referencia",
|
||||
"cmd.sketch_trim_path": "Recortar a lo largo de la trayectoria",
|
||||
"cmd.solid_align": "Alinear",
|
||||
@@ -976,8 +883,6 @@
|
||||
"dxf.progress.write": "Escribiendo DXF (5/5)",
|
||||
"dxf.tolerance": "Tolerancia de curva",
|
||||
"dxf.tolerance_help": "Solo cuerpos visibles y bocetos mostrados. Las curvas usan segmentos rectos. Una tolerancia menor da curvas más finas y consume más tiempo y memoria.",
|
||||
"each rail's curves must be in one sketch": "las curvas de cada guía deben estar en un solo boceto",
|
||||
"feature computation ended without a result": "el cálculo de la operación terminó sin resultado",
|
||||
"file_load.built": "Construido hasta ahora: {built} de {total} operaciones.",
|
||||
"file_load.converting": "Convirtiendo sólido {done} de {total}",
|
||||
"file_load.inserting": "Insertando geometría",
|
||||
@@ -1038,7 +943,7 @@
|
||||
"home.new_mm": "Nuevo diseño",
|
||||
"home.new_mm_hint": "Milímetros · se abre en Diseño",
|
||||
"home.open": "Abrir…",
|
||||
"home.open_hint": "Un diseño .sc ({shortcut})",
|
||||
"home.open_hint": "Un diseño .solidcraft ({shortcut})",
|
||||
"home.open_now": "Abrir ahora",
|
||||
"home.opened_days_ago": "Hace {count} días",
|
||||
"home.opened_hours_ago": "Hace {count} h",
|
||||
@@ -1172,63 +1077,18 @@
|
||||
"no hole": "sin agujero",
|
||||
"no interference": "sin interferencias",
|
||||
"no joint that moves yet": "todavía no hay ninguna unión móvil",
|
||||
"no motion study yet": "todavía no hay ningún estudio de movimiento",
|
||||
"no picture": "sin imagen",
|
||||
"no such joint": "no existe esa unión",
|
||||
"none": "ninguno",
|
||||
"not a move": "no es un movimiento",
|
||||
"not a number": "no es un número",
|
||||
"open a sketch first": "abre primero un boceto",
|
||||
"parts.automatic_name": "automático",
|
||||
"parts.dimension.across_flats": "Entrecaras",
|
||||
"parts.dimension.bore": "Diámetro interior",
|
||||
"parts.dimension.head_diameter": "Diámetro cabeza",
|
||||
"parts.dimension.head_height": "Altura cabeza",
|
||||
"parts.dimension.height": "Altura",
|
||||
"parts.dimension.inner": "Diámetro interior",
|
||||
"parts.dimension.outer": "Diámetro exterior",
|
||||
"parts.dimension.socket": "Hexágono interior",
|
||||
"parts.dimension.socket_depth": "Profundidad hexágono",
|
||||
"parts.dimension.thickness": "Espesor",
|
||||
"parts.dimension.width": "Anchura",
|
||||
"parts.family.bearing": "Rodamiento rígido de bolas (envolvente)",
|
||||
"parts.family.dowel_pin": "Pasador cilíndrico",
|
||||
"parts.family.hex_bolt": "Tornillo de cabeza hexagonal",
|
||||
"parts.family.hex_nut": "Tuerca hexagonal",
|
||||
"parts.family.set_screw": "Prisionero con hexágono interior, punta plana",
|
||||
"parts.family.socket_head_cap_screw": "Tornillo cilíndrico con hexágono interior",
|
||||
"parts.family.washer": "Arandela plana",
|
||||
"pattern.alignment_2": "Alineación 2",
|
||||
"pattern.alignment_hint": "Los recuentos incluyen el original. Centrar equilibra las copias; Invertir elige el lado adicional cuando el total es par.",
|
||||
"pattern.direction_1": "Dirección 1",
|
||||
"pattern.direction_2": "Dirección 2",
|
||||
"pattern.flip_direction": "Invertir dirección",
|
||||
"pick a body or some of its faces": "elige un cuerpo o algunas de sus caras",
|
||||
"pick a hole to seat it, or OK places it at the origin": "selecciona un agujero para colocarlo o pulsa Aceptar para situarlo en el origen",
|
||||
"pick a planar face": "elige una cara plana",
|
||||
"pick a plane or a face as the tool": "elige un plano o una cara como herramienta",
|
||||
"pick a size": "elige un tamaño",
|
||||
"pick edges of a body": "elige aristas de un cuerpo",
|
||||
"pick edges of one body": "elige aristas de un solo cuerpo",
|
||||
"pick edges of the holes to remove": "elige las aristas de los agujeros que quitar",
|
||||
"pick faces of one body": "elige caras de un solo cuerpo",
|
||||
"pick faces to offset": "elige las caras que desfasar",
|
||||
"pick lines of one sketch": "elige líneas de un solo boceto",
|
||||
"pick one or two vertices, edges or faces": "selecciona uno o dos vértices, aristas o caras",
|
||||
"pick one plane or face as the tool, or sketch curves": "elige un plano o una cara como herramienta, o curvas de boceto",
|
||||
"pick sections in two or more sketches": "elige secciones en dos o más bocetos",
|
||||
"pick sketch curves or edges": "elige curvas de boceto o aristas",
|
||||
"pick the face to untrim": "elige la cara a la que quitar el recorte",
|
||||
"pick what to keep": "elige qué conservar",
|
||||
"pick what to keep of the other surface": "elige qué conservar de la otra superficie",
|
||||
"prefs.cpu": "CPU (software)",
|
||||
"prefs.minutes_suffix": " min",
|
||||
"prefs.theme_follow": "Seguir un archivo de tema",
|
||||
"prefs.theme_follow_error": "No aplicado: {error}. Se mantiene el último tema válido.",
|
||||
"prefs.theme_follow_launch": "Solo en esta sesión (--theme-file); se recarga cada vez que otra herramienta reescribe el archivo.",
|
||||
"prefs.theme_follow_note": "Se recarga cada vez que otra herramienta reescribe el archivo. Elegir un tema deja de seguirlo.",
|
||||
"prefs.theme_follow_off": "Desactivado",
|
||||
"prefs.theme_follow_stop": "Dejar de seguir",
|
||||
"properties.title": "Propiedades: {name}",
|
||||
"recovery.days_ago": "hace {count} días",
|
||||
"recovery.detail": "{file} · {features} operaciones · autoguardado {when}",
|
||||
@@ -1853,26 +1713,6 @@
|
||||
"search.suggest": "¿Quisiste decir {label}?",
|
||||
"search.try": "Escribe el nombre de un comando, una operación de este diseño o un parámetro. Para ejecutar un comando con valores, escribe su id y un objeto JSON: {example}",
|
||||
"search.valid": "Válido",
|
||||
"select a direction (an axis, a sketch line or a straight edge) first": "selecciona primero una dirección (un eje, una línea de boceto o una arista recta)",
|
||||
"select a direction (an axis, a sketch line or a straight edge) for the profile to follow": "selecciona una dirección (un eje, una línea de boceto o una arista recta) que seguirá el perfil",
|
||||
"select a point on a component": "selecciona un punto en un componente",
|
||||
"select a profile, edges or sketch lines first": "selecciona primero un perfil, aristas o líneas de boceto",
|
||||
"select an axis": "selecciona un eje",
|
||||
"select an axis, a straight or circular edge, or a cylinder, cone or torus face first": "selecciona primero un eje, una arista recta o circular, o una cara cilíndrica, cónica o toroidal",
|
||||
"select bodies first": "selecciona primero cuerpos",
|
||||
"select corner points, or the two lines of one corner": "selecciona puntos de esquina, o las dos líneas de una esquina",
|
||||
"select one corner point, or the two lines of one corner": "selecciona un punto de esquina, o las dos líneas de una esquina",
|
||||
"select sheet edges first": "selecciona primero aristas de chapa",
|
||||
"select the bend line (a sketch line) first": "selecciona primero la línea de plegado (una línea de boceto)",
|
||||
"select the cam's profile (a sketch profile or the cam's edges)": "selecciona el perfil de la leva (un perfil de boceto o las aristas de la leva)",
|
||||
"select the corner to cut first": "selecciona primero la esquina que cortar",
|
||||
"select the corners to round first": "selecciona primero las esquinas que redondear",
|
||||
"select the fixed face first (a plate's large face)": "selecciona primero la cara fija (la cara grande de una placa)",
|
||||
"select the frame sketch's lines first": "selecciona primero las líneas del boceto del marco",
|
||||
"select the path (sketch lines across the edge)": "selecciona la trayectoria (líneas de boceto a lo largo de la arista)",
|
||||
"select the rim face first": "selecciona primero la cara del borde",
|
||||
"select the sheet edge first": "selecciona primero la arista de la chapa",
|
||||
"select two components": "selecciona dos componentes",
|
||||
"settings.display_quality_note": "Los niveles más finos muestran las caras y aristas curvas con mayor suavidad, pero tardan más en generar la malla. Solo cambia el visor, nunca el diseño ni las exportaciones.",
|
||||
"settings.frame_rate_note": "Muestra los fotogramas por segundo en la esquina inferior derecha de la vista.",
|
||||
"settings.measure_axis_distances": "Mostrar distancias X/Y/Z",
|
||||
@@ -1885,22 +1725,16 @@
|
||||
"settings.reset_intro": "Estos ajustes no funcionan en este ordenador e impedirían iniciar SolidCraft, por lo que se han cambiado:",
|
||||
"settings.reset_title": "Se han cambiado los ajustes",
|
||||
"settings.supersampling_note": "Dibuja la vista a mayor resolución y después la reduce: bordes, líneas y cuadrícula más suaves en pantallas inferiores a 4K. Consume recursos de GPU: unos 2,25× píxeles a 1,5× y 4× a 2×.",
|
||||
"sheet.flange_type_base": "Base",
|
||||
"sheet.flange_type_contour": "Contorno",
|
||||
"sheet.flange_type_edge": "Arista",
|
||||
"shortcut.json_error": "los parámetros deben estar en formato JSON: {error}",
|
||||
"shortcut.no_command": "no existe el comando `{id}`; el texto anterior al JSON debe ser un ID de comando",
|
||||
"shortcut.pin": "Fijar en la parte superior del cuadro S",
|
||||
"shortcut.unpin": "Desfijar",
|
||||
"sketch is missing": "falta el boceto",
|
||||
"sketch.degrees_of_freedom": "{count} grados de libertad",
|
||||
"sketch.dof_left": "Quedan {count} grados de libertad",
|
||||
"sketch.dof_left_one": "Queda {count} grado de libertad",
|
||||
"sketch.draw_curves": "Dibujar como {linetype}",
|
||||
"sketch.make_selection": "Convertir la selección a {linetype}",
|
||||
"sketch.open_ends": "Abiertos {count}",
|
||||
"sketch.status_conflict": "restricciones en conflicto",
|
||||
"sketch.status_fully_constrained": "totalmente restringido",
|
||||
"sketch.untied_ends": "Sin vincular {count}",
|
||||
"sketch_panel.attached": "Vinculado a {body} / {path}",
|
||||
"sketch_panel.author": "Ubicación de autoría: {path}",
|
||||
@@ -1922,24 +1756,7 @@
|
||||
"sketch_text.path_hint": "Curva del boceto que seguirá el texto: haz clic aquí y después en la curva",
|
||||
"sketch_text.use_font_file": "Usar el archivo de fuente {path}",
|
||||
"solid": "sólido",
|
||||
"the angle must be a number": "el ángulo debe ser un número",
|
||||
"the curves must be in one sketch": "las curvas deben estar en un solo boceto",
|
||||
"the direction must be an axis, a sketch line or a straight edge": "la dirección debe ser un eje, una línea de boceto o una arista recta",
|
||||
"the duration must be a number of seconds, 0.001 or more": "la duración debe ser un número de segundos, 0.001 o más",
|
||||
"the line's sketch is gone": "el boceto de la línea ya no existe",
|
||||
"the lines must be in one sketch": "las líneas deben estar en un solo boceto",
|
||||
"the lines must be in one sketch, not the section's": "las líneas deben estar en un solo boceto, no en el de la sección",
|
||||
"the parts library is empty": "la biblioteca de piezas está vacía",
|
||||
"the parts library is not available": "la biblioteca de piezas no está disponible",
|
||||
"the path curves must be in one other sketch": "las curvas de la trayectoria deben estar en un solo boceto distinto",
|
||||
"the path's sketch is gone": "el boceto de la trayectoria ya no existe",
|
||||
"the position must be a number": "la posición debe ser un número",
|
||||
"the preview failed": "falló la vista previa",
|
||||
"the profiles and curves must be in one sketch": "los perfiles y las curvas deben estar en un solo boceto",
|
||||
"the reference face must be on the edges' body": "la cara de referencia debe estar en el cuerpo de las aristas",
|
||||
"the scale must be above 0": "la escala debe ser mayor que 0",
|
||||
"the section's profiles must be in one sketch": "los perfiles de la sección deben estar en un solo boceto",
|
||||
"the two origins are on the same component": "los dos orígenes están en el mismo componente",
|
||||
"thread.automatic_size": "Tamaño automático",
|
||||
"thread.designation": "Designación",
|
||||
"thread.iso_metric": "Métrica ISO",
|
||||
@@ -1966,7 +1783,6 @@
|
||||
"trace.points": "{count} puntos a lo largo de la trayectoria",
|
||||
"trace.stopped_by_contact": "Termina en la muestra {sample}: {a} atravesaría {b}",
|
||||
"trace.stopped_by_loop": "Termina en la muestra {sample}: el bucle que pasa por {joint} no puede cerrarse más",
|
||||
"trim by sketch curves, or by one plane or face": "recorta con curvas de boceto, o con un plano o una cara",
|
||||
"ui.1_degree_of_freedom": "1 grado de libertad",
|
||||
"ui.3d_mouse_axes_reassigned": "Los ejes están reasignados en Preferencias, y eso sigue aplicándose sobre esta calibración.",
|
||||
"ui.3d_mouse_button": "Botón {number}",
|
||||
@@ -2393,8 +2209,6 @@
|
||||
"ui.design_properties.revision": "Revisión",
|
||||
"ui.diameter": "Diámetro",
|
||||
"ui.dimension": "Cota",
|
||||
"ui.direct_interaction": "Interacción directa",
|
||||
"ui.direct_interaction_note": "Trabaja directamente sobre el modelo en la vista. Pulsa una cara plana o un perfil y tira: hacia arriba extruye, hacia abajo dentro de un cuerpo corta, y el movimiento lateral fija el paso",
|
||||
"ui.direction": "Dirección",
|
||||
"ui.display_quality": "Calidad de visualización",
|
||||
"ui.display_quality_coarse": "Baja",
|
||||
@@ -2418,7 +2232,6 @@
|
||||
"ui.editJointLimits": "Editar límites de unión",
|
||||
"ui.edit_section_analysis": "Editar análisis de sección",
|
||||
"ui.ellipse": "Elipse",
|
||||
"ui.emboss_wrap_note": "Los perfiles de líneas ortogonales tangentes a un cilindro compatible se envuelven automáticamente. Elegir una pared para definir el destino.",
|
||||
"ui.end_radius": "Radio final",
|
||||
"ui.end_scale": "Escala final",
|
||||
"ui.entities": "Entidades",
|
||||
@@ -2484,7 +2297,6 @@
|
||||
"ui.heading_extrude": "EXTRUIR",
|
||||
"ui.heading_fillet": "REDONDEO",
|
||||
"ui.heading_finish_sketch": "FINALIZAR BOCETO",
|
||||
"ui.heading_form": "FORMA",
|
||||
"ui.heading_front": "FRONTAL",
|
||||
"ui.heading_hole": "AGUJERO",
|
||||
"ui.heading_home": "INICIO",
|
||||
@@ -2533,7 +2345,6 @@
|
||||
"ui.heading_utilities": "UTILIDADES",
|
||||
"ui.heading_utility": "UTILIDAD",
|
||||
"ui.heading_weldments": "SOLDADURA",
|
||||
"ui.height": "Altura",
|
||||
"ui.help": "Ayuda",
|
||||
"ui.hide": "Ocultar",
|
||||
"ui.hideAxes": "Ocultar ejes",
|
||||
@@ -2561,14 +2372,11 @@
|
||||
"ui.intersect": "Intersecar",
|
||||
"ui.isolate": "Aislar",
|
||||
"ui.join": "Unir",
|
||||
"ui.joint_play": "▶ Reproducir",
|
||||
"ui.joint_stop": "■ Detener",
|
||||
"ui.joints": "Uniones",
|
||||
"ui.keyboard_shortcuts": "Atajos de teclado",
|
||||
"ui.language": "Idioma",
|
||||
"ui.layout_compact": "Compacta",
|
||||
"ui.layout_floating": "Flotante",
|
||||
"ui.length": "Longitud",
|
||||
"ui.length_decimals": "Decimales de longitud",
|
||||
"ui.light": "Claro",
|
||||
"ui.line": "Línea",
|
||||
@@ -2576,7 +2384,6 @@
|
||||
"ui.loft": "Solevación",
|
||||
"ui.look_at": "Mirar hacia",
|
||||
"ui.look_at_anim_desc": "Vista de eje, Mirar hacia, Inicio y Ajustar",
|
||||
"ui.major_radius": "Radio mayor",
|
||||
"ui.manipulator_free_alt": "Arrastre libre del manipulador también con Alt/Option",
|
||||
"ui.manipulator_free_alt_tip": "Activado: Alt/Option o Ctrl/Cmd sobre las flechas de extrusión / manipulador desactiva el ajuste de valores. Desactivado: solo Ctrl/Cmd.",
|
||||
"ui.manufacture": "Fabricación",
|
||||
@@ -2606,10 +2413,8 @@
|
||||
"ui.mesh_format_3mf": "3MF",
|
||||
"ui.mesh_format_3mf_tagged": "3MF con etiquetas de cara",
|
||||
"ui.mesh_format_stl": "STL",
|
||||
"ui.minor_radius": "Radio menor",
|
||||
"ui.mirror": "Simetría",
|
||||
"ui.mirror_line": "Línea de simetría",
|
||||
"ui.modifiers_either": "{first} o {second}",
|
||||
"ui.move": "Mover",
|
||||
"ui.multiple": "Múltiple",
|
||||
"ui.named_views": "Vistas guardadas",
|
||||
@@ -2708,7 +2513,6 @@
|
||||
"ui.reverse_horizontal_pan_direction": "Invertir dirección del desplazamiento horizontal",
|
||||
"ui.reverse_horizontal_pan_tip": "El desplazamiento horizontal con dos dedos y la rueda de pulgar desplazan en sentido contrario",
|
||||
"ui.reverse_zoom_direction": "Invertir dirección del zoom",
|
||||
"ui.revolutions": "Vueltas",
|
||||
"ui.revolve": "Revolución",
|
||||
"ui.ribbon_exit": "Salir",
|
||||
"ui.ribbon_labels": "Mostrar texto bajo los iconos de la cinta",
|
||||
@@ -2724,7 +2528,6 @@
|
||||
"ui.scale": "Escala",
|
||||
"ui.second_rail": "Segunda línea guía",
|
||||
"ui.section_analysis": "Análisis de sección",
|
||||
"ui.section_size": "Tamaño de la sección",
|
||||
"ui.select": "Seleccionar",
|
||||
"ui.selectOther": "Seleccionar otro…",
|
||||
"ui.selectTangentChain": "Seleccionar cadena tangente",
|
||||
@@ -2775,7 +2578,6 @@
|
||||
"ui.start": "Iniciar",
|
||||
"ui.start_over": "Volver a empezar",
|
||||
"ui.start_page": "Página de inicio",
|
||||
"ui.stitch_tolerance_hint": "estricta",
|
||||
"ui.stretch": "Estirar",
|
||||
"ui.supersampling": "Supermuestreo (SSAA)",
|
||||
"ui.supersampling_adaptive": "Solo cuando la vista está inmóvil",
|
||||
@@ -2806,8 +2608,6 @@
|
||||
"ui.to_object": "Hasta objeto",
|
||||
"ui.tool_direction": "Dirección de herramienta",
|
||||
"ui.top": "Superior",
|
||||
"ui.top_diameter": "Diámetro superior",
|
||||
"ui.top_radius": "Radio superior",
|
||||
"ui.torus": "Toro",
|
||||
"ui.total_angle": "Ángulo total",
|
||||
"ui.trackpad_pan_tip": "Los gestos del panel táctil desplazan la vista (desactivado: hacen zoom como la rueda); la rueda del ratón hace zoom, la rueda lateral desplaza horizontalmente y el gesto de pellizco siempre hace zoom",
|
||||
@@ -2833,7 +2633,6 @@
|
||||
"ui.visual_shaded_with_edges": "Sombreado con aristas",
|
||||
"ui.visual_style": "Estilo visual",
|
||||
"ui.visual_wireframe": "Estructura alámbrica",
|
||||
"ui.width": "Anchura",
|
||||
"ui.workspace": "espacio de trabajo",
|
||||
"ui.workspace_animation_description": "Vistas explosionadas, estudios de movimiento y guiones gráficos",
|
||||
"ui.workspace_available": "Disponible",
|
||||
@@ -2889,8 +2688,5 @@
|
||||
"update.up_to_date": "SolidCraft está actualizado.",
|
||||
"update.update_now": "Actualizar ahora",
|
||||
"update.you_have": "Tienes la versión {version}.",
|
||||
"use a positive nominal diameter and a pitch smaller than it": "usa un diámetro nominal positivo y un paso menor que él",
|
||||
"values must be finite numbers": "los valores deben ser números finitos",
|
||||
"values must be numbers or expressions": "los valores deben ser números o expresiones",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ elegir · Intro / Tab completar · Esc cerrar"
|
||||
}
|
||||
|
||||
@@ -961,7 +961,7 @@
|
||||
"home.new_mm": "Nowy projekt",
|
||||
"home.new_mm_hint": "Milimetry · otwiera w obszarze Projektowanie",
|
||||
"home.open": "Otwórz…",
|
||||
"home.open_hint": "Projekt .sc ({shortcut})",
|
||||
"home.open_hint": "Projekt .solidcraft ({shortcut})",
|
||||
"home.open_now": "Otwórz teraz",
|
||||
"home.opened_days_ago": "{count} dni temu",
|
||||
"home.opened_hours_ago": "{count} godz. temu",
|
||||
@@ -1057,11 +1057,6 @@
|
||||
"no picture": "brak obrazu",
|
||||
"none": "brak",
|
||||
"parts.automatic_name": "automatycznie",
|
||||
"parts.dimension.d": "Średnica nominalna",
|
||||
"parts.dimension.height": "Wysokość",
|
||||
"parts.dimension.pitch": "Skok",
|
||||
"parts.dimension.thickness": "Grubość",
|
||||
"parts.dimension.width": "Szerokość",
|
||||
"pattern.alignment_2": "Wyrównanie 2",
|
||||
"pattern.alignment_hint": "Liczba obejmuje oryginał. Wyśrodkuj rozkłada kopie po obu stronach; Odwróć wybiera stronę z dodatkową kopią przy parzystej liczbie.",
|
||||
"pattern.direction_1": "Kierunek 1",
|
||||
@@ -1178,12 +1173,6 @@
|
||||
"prefs.supersampling_brief": "Rysuje widok w większym rozmiarze i go zmniejsza: bardzo ostro, ale bardziej obciąża.",
|
||||
"prefs.tab_accent_note": "Kolor akcentu podąża za aktywną kartą",
|
||||
"prefs.theme_file": "plik motywu",
|
||||
"prefs.theme_follow": "Śledź plik motywu",
|
||||
"prefs.theme_follow_error": "Nie zastosowano: {error}. Ostatni poprawny motyw pozostaje aktywny.",
|
||||
"prefs.theme_follow_launch": "Śledzenie tylko w tej sesji (--theme-file); plik jest wczytywany ponownie po każdym nadpisaniu przez inne narzędzie.",
|
||||
"prefs.theme_follow_note": "Plik jest wczytywany ponownie po każdym nadpisaniu przez inne narzędzie. Wybór motywu kończy śledzenie.",
|
||||
"prefs.theme_follow_off": "Wyłączone",
|
||||
"prefs.theme_follow_stop": "Zakończ śledzenie",
|
||||
"prefs.timeline_half_hour": "−30 min",
|
||||
"prefs.timeline_now": "teraz",
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
@@ -1898,7 +1887,6 @@
|
||||
"ui.heading_utilities": "NARZĘDZIA",
|
||||
"ui.heading_utility": "NARZĘDZIE",
|
||||
"ui.heading_weldments": "SPAWANIE",
|
||||
"ui.height": "Wysokość",
|
||||
"ui.help": "Pomoc",
|
||||
"ui.hide": "Ukryj",
|
||||
"ui.hideAxes": "Ukryj osie",
|
||||
@@ -1937,7 +1925,6 @@
|
||||
"ui.language": "Język",
|
||||
"ui.layout_compact": "Kompaktowy",
|
||||
"ui.layout_floating": "Pływający",
|
||||
"ui.length": "Długość",
|
||||
"ui.length_decimals": "Miejsca dziesiętne długości",
|
||||
"ui.light": "Jasny",
|
||||
"ui.line": "Linia",
|
||||
@@ -2026,7 +2013,6 @@
|
||||
"ui.pinToShortcuts": "Przypnij do skrótów",
|
||||
"ui.pinToToolbar": "Przypnij do paska narzędzi",
|
||||
"ui.pipe": "Rura",
|
||||
"ui.pitch": "Skok",
|
||||
"ui.placement": "Umiejscowienie",
|
||||
"ui.placement_handles": "Uchwyty umieszczania",
|
||||
"ui.placement_handles_tip": "Uchwyty na podglądzie podparcia, występu lub zatrzasku: przeciągnij znacznik, aby przesunąć element po modelu, obróć go pierścieniem",
|
||||
@@ -2202,7 +2188,6 @@
|
||||
"ui.visual_shaded_with_edges": "Cieniowany z krawędziami",
|
||||
"ui.visual_style": "Styl wizualny",
|
||||
"ui.visual_wireframe": "Model krawędziowy",
|
||||
"ui.width": "Szerokość",
|
||||
"ui.workspace": "środowisko pracy",
|
||||
"ui.workspace_animation_description": "Widoki rozstrzelone, badania ruchu i scenorysy",
|
||||
"ui.workspace_available": "Dostępne",
|
||||
|
||||
@@ -1074,7 +1074,7 @@
|
||||
"home.new_mm": "Novo projeto",
|
||||
"home.new_mm_hint": "Milímetros · abre em Projeto",
|
||||
"home.open": "Abrir…",
|
||||
"home.open_hint": "Um projeto .sc ({shortcut})",
|
||||
"home.open_hint": "Um projeto .solidcraft ({shortcut})",
|
||||
"home.open_now": "Abrir agora",
|
||||
"home.opened_days_ago": "Há {count} dias",
|
||||
"home.opened_hours_ago": "Há {count} h",
|
||||
@@ -1159,9 +1159,7 @@
|
||||
"nav.free_orbit.label": "Órbita livre",
|
||||
"nav.free_orbit.snap_help": "Mouse e mouse 3D giram através dos dois polos e preservam a rotação da imagem. Mantenha Shift pressionado durante uma órbita por arraste para alinhar a direção da vista e a rotação da imagem.",
|
||||
"nav.fusion.help": "Arrastar com o meio desloca, Shift+meio orbita, arrastar com o direito orbita",
|
||||
"nav.hfss.help": "Alt+arrastar com o esquerdo desloca, Shift+Alt+esquerdo aplica zoom, Ctrl+arrastar com o esquerdo em área vazia orbita",
|
||||
"nav.inventor.help": "Arrastar com o meio desloca, Shift+meio orbita, Ctrl+meio aplica zoom, arrastar com o direito orbita",
|
||||
"nav.maya.help": "Alt+arrastar com o esquerdo orbita, Alt+meio desloca, Alt+direito aplica zoom; arrastar com o meio também desloca",
|
||||
"nav.openscad.help": "Arrastar com o esquerdo no espaço vazio orbita, Shift+arrastar com o esquerdo seleciona por caixa, arrastar a geometria do esboço a edita; arrastar com o direito ou o meio desloca, Shift+arrastar com o direito ou o meio aplica zoom",
|
||||
"nav.solidworks.help": "Arrastar com o meio orbita, Ctrl+meio desloca, Shift+meio aplica zoom, arrastar com o direito orbita",
|
||||
"nav.tinkercad.help": "Arrastar com o direito orbita, arrastar com o meio ou Shift+direito desloca",
|
||||
@@ -1180,11 +1178,6 @@
|
||||
"no picture": "sem imagem",
|
||||
"none": "nenhum",
|
||||
"parts.automatic_name": "automático",
|
||||
"parts.dimension.d": "Diâmetro nominal",
|
||||
"parts.dimension.height": "Altura",
|
||||
"parts.dimension.pitch": "Passo",
|
||||
"parts.dimension.thickness": "Espessura",
|
||||
"parts.dimension.width": "Largura",
|
||||
"pattern.alignment_2": "Alinhamento 2",
|
||||
"pattern.alignment_hint": "As contagens incluem o original. Centralizar equilibra as cópias; Inverter escolhe o lado extra para totais pares.",
|
||||
"pattern.direction_1": "Direção 1",
|
||||
@@ -1300,12 +1293,6 @@
|
||||
"prefs.supersampling_brief": "Desenha a vista maior e a reduz com filtragem: muito nítida, e mais pesada.",
|
||||
"prefs.tab_accent_note": "A cor de destaque acompanha a aba ativa",
|
||||
"prefs.theme_file": "arquivo de tema",
|
||||
"prefs.theme_follow": "Seguir um arquivo de tema",
|
||||
"prefs.theme_follow_error": "Não aplicado: {error}. O último tema válido continua ativo.",
|
||||
"prefs.theme_follow_launch": "Seguido apenas nesta sessão (--theme-file); recarregado sempre que outra ferramenta reescreve o arquivo.",
|
||||
"prefs.theme_follow_note": "Recarregado sempre que outra ferramenta reescreve o arquivo. Escolher um tema interrompe o acompanhamento.",
|
||||
"prefs.theme_follow_off": "Desativado",
|
||||
"prefs.theme_follow_stop": "Parar de seguir",
|
||||
"prefs.timeline_half_hour": "−30 min",
|
||||
"prefs.timeline_now": "agora",
|
||||
"prefs.timeline_quarter_hour": "−15",
|
||||
@@ -2030,7 +2017,6 @@
|
||||
"ui.heading_utilities": "UTILITÁRIOS",
|
||||
"ui.heading_utility": "UTILITÁRIO",
|
||||
"ui.heading_weldments": "SOLDAGEM",
|
||||
"ui.height": "Altura",
|
||||
"ui.help": "Ajuda",
|
||||
"ui.hide": "Ocultar",
|
||||
"ui.hideAxes": "Ocultar eixos",
|
||||
@@ -2069,7 +2055,6 @@
|
||||
"ui.language": "Idioma",
|
||||
"ui.layout_compact": "Compacto",
|
||||
"ui.layout_floating": "Flutuante",
|
||||
"ui.length": "Comprimento",
|
||||
"ui.length_decimals": "Casas decimais de comprimento",
|
||||
"ui.light": "Claro",
|
||||
"ui.line": "Linha",
|
||||
@@ -2157,7 +2142,6 @@
|
||||
"ui.pinToShortcuts": "Fixar nos atalhos",
|
||||
"ui.pinToToolbar": "Fixar na barra de ferramentas",
|
||||
"ui.pipe": "Tubo",
|
||||
"ui.pitch": "Passo",
|
||||
"ui.placement": "Posicionamento",
|
||||
"ui.plane": "Plano",
|
||||
"ui.plane_at_angle": "Plano em ângulo",
|
||||
@@ -2332,7 +2316,6 @@
|
||||
"ui.visual_shaded_with_edges": "Sombreado com arestas",
|
||||
"ui.visual_style": "Estilo visual",
|
||||
"ui.visual_wireframe": "Aramado",
|
||||
"ui.width": "Largura",
|
||||
"ui.workspace": "espaço de trabalho",
|
||||
"ui.workspace_animation_description": "Vistas explodidas, estudos de movimento e storyboards",
|
||||
"ui.workspace_available": "Disponível",
|
||||
|
||||
@@ -899,7 +899,7 @@
|
||||
"home.new_mm": "Proiect nou",
|
||||
"home.new_mm_hint": "Milimetri · se deschide în Proiectare",
|
||||
"home.open": "Deschide…",
|
||||
"home.open_hint": "Un proiect .sc ({shortcut})",
|
||||
"home.open_hint": "Un proiect .solidcraft ({shortcut})",
|
||||
"home.open_now": "Deschis acum",
|
||||
"home.opened_days_ago": "Acum {count} zile",
|
||||
"home.opened_hours_ago": "Acum {count} h",
|
||||
@@ -993,9 +993,6 @@
|
||||
"no picture": "fără imagine",
|
||||
"none": "niciunul",
|
||||
"parts.automatic_name": "automat",
|
||||
"parts.dimension.height": "Înălțime",
|
||||
"parts.dimension.thickness": "Grosime",
|
||||
"parts.dimension.width": "Lățime",
|
||||
"pattern.alignment_2": "Aliniere 2",
|
||||
"pattern.alignment_hint": "Numărul include originalul. Centrat echilibrează copiile; Inversare alege partea suplimentară pentru totaluri pare.",
|
||||
"pattern.direction_1": "Direcția 1",
|
||||
@@ -1005,12 +1002,6 @@
|
||||
"pick one or two vertices, edges or faces": "alegeți unul sau două vârfuri, muchii sau fețe",
|
||||
"prefs.cpu": "CPU (software)",
|
||||
"prefs.minutes_suffix": " min",
|
||||
"prefs.theme_follow": "Urmărește un fișier de temă",
|
||||
"prefs.theme_follow_error": "Nu s-a aplicat: {error}. Ultima temă validă rămâne activă.",
|
||||
"prefs.theme_follow_launch": "Urmărit doar în această sesiune (--theme-file); reîncărcat când alt instrument rescrie fișierul.",
|
||||
"prefs.theme_follow_note": "Reîncărcat când alt instrument rescrie fișierul. Alegerea unei teme oprește urmărirea.",
|
||||
"prefs.theme_follow_off": "Dezactivat",
|
||||
"prefs.theme_follow_stop": "Oprește urmărirea",
|
||||
"properties.title": "Proprietăți: {name}",
|
||||
"recovery.days_ago": "acum {count} zile",
|
||||
"recovery.detail": "{file} · {features} operații · salvat automat {when}",
|
||||
@@ -1645,7 +1636,6 @@
|
||||
"ui.heading_utilities": "UTILITARE",
|
||||
"ui.heading_utility": "UTILITAR",
|
||||
"ui.heading_weldments": "SUDURĂ",
|
||||
"ui.height": "Înălțime",
|
||||
"ui.help": "Ajutor",
|
||||
"ui.hide": "Ascunde",
|
||||
"ui.hideAxes": "Ascunde axele",
|
||||
@@ -1677,7 +1667,6 @@
|
||||
"ui.language": "Limbă",
|
||||
"ui.layout_compact": "Compact",
|
||||
"ui.layout_floating": "Plutitor",
|
||||
"ui.length": "Lungime",
|
||||
"ui.length_decimals": "Zecimale lungime",
|
||||
"ui.light": "Luminos",
|
||||
"ui.line": "Linie",
|
||||
@@ -1934,7 +1923,6 @@
|
||||
"ui.visual_shaded_with_edges": "Umbrit cu muchii",
|
||||
"ui.visual_style": "Stil vizual",
|
||||
"ui.visual_wireframe": "Cadru de sârmă",
|
||||
"ui.width": "Lățime",
|
||||
"ui.workspace": "spațiu de lucru",
|
||||
"ui.workspace_animation_description": "Vederi explodate, studii de mișcare și storyboard-uri",
|
||||
"ui.workspace_available": "Disponibil",
|
||||
|
||||
@@ -5,12 +5,12 @@ GenericName=3D CAD
|
||||
Comment=Parametric 3D CAD: sketch, constrain, extrude, and change a parameter to rebuild
|
||||
Exec=solidcraft %f
|
||||
TryExec=solidcraft
|
||||
Icon=io.tangencylabs.solidcraft
|
||||
Icon=ai.storyteller.solidcraft
|
||||
Terminal=false
|
||||
StartupNotify=true
|
||||
StartupWMClass=io.tangencylabs.solidcraft
|
||||
StartupWMClass=ai.storyteller.solidcraft
|
||||
Categories=Graphics;3DGraphics;Engineering;
|
||||
Keywords=cad;3d;parametric;sketch;step;stl;modelling;modeling;
|
||||
MimeType=application/x-solidcraft;model/step;model/iges;model/stl;model/3mf;
|
||||
# Desktop ids of earlier releases; launchers keep pinned entries across the renames.
|
||||
X-Flatpak-RenamedFrom=ai.storyteller.solidcraft.desktop;ai.storyteller.solvecraft.desktop;
|
||||
# Releases before 0.8 used this desktop id; launchers keep pinned entries across the rename.
|
||||
X-Flatpak-RenamedFrom=ai.storyteller.solvecraft.desktop;
|
||||
@@ -2,7 +2,7 @@
|
||||
<!-- AppStream metadata (software centres, Flathub). Template: package.sh substitutes @VERSION@
|
||||
and @DATE@ from Cargo.toml and the build date. -->
|
||||
<component type="desktop-application">
|
||||
<id>io.tangencylabs.solidcraft</id>
|
||||
<id>ai.storyteller.solidcraft</id>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>MIT OR Apache-2.0</project_license>
|
||||
<name>SolidCraft</name>
|
||||
@@ -24,8 +24,8 @@
|
||||
<li>Every action is a command, scriptable from a CLI, a JSON control channel and an MCP server</li>
|
||||
</ul>
|
||||
</description>
|
||||
<launchable type="desktop-id">io.tangencylabs.solidcraft.desktop</launchable>
|
||||
<icon type="stock">io.tangencylabs.solidcraft</icon>
|
||||
<launchable type="desktop-id">ai.storyteller.solidcraft.desktop</launchable>
|
||||
<icon type="stock">ai.storyteller.solidcraft</icon>
|
||||
<url type="homepage">https://docs.solidcraft.io/</url>
|
||||
<url type="bugtracker">https://github.com/bherbruck/solidcraft/issues</url>
|
||||
<url type="help">https://docs.solidcraft.io/</url>
|
||||
@@ -56,15 +56,11 @@
|
||||
<binary>solidcraft</binary>
|
||||
<binary>solidcraft-cli</binary>
|
||||
<mediatype>application/x-solidcraft</mediatype>
|
||||
<!-- The app and desktop ids of earlier releases (docs/compatibility.md). -->
|
||||
<id>ai.storyteller.solidcraft</id>
|
||||
<id>ai.storyteller.solidcraft.desktop</id>
|
||||
<!-- The desktop id of releases before 0.8 (docs/compatibility.md). -->
|
||||
<id>ai.storyteller.solvecraft.desktop</id>
|
||||
</provides>
|
||||
<!-- Earlier app ids (docs/compatibility.md): ai.storyteller.solidcraft from 0.8 until the move
|
||||
to Tangency Labs, ai.storyteller.solvecraft before 0.8. -->
|
||||
<!-- Releases before 0.8 used the app id ai.storyteller.solvecraft (docs/compatibility.md). -->
|
||||
<replaces>
|
||||
<id>ai.storyteller.solidcraft</id>
|
||||
<id>ai.storyteller.solvecraft</id>
|
||||
</replaces>
|
||||
<content_rating type="oars-1.1"/>
|
||||
@@ -4,13 +4,10 @@
|
||||
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
|
||||
<mime-type type="application/x-solidcraft">
|
||||
<comment>SolidCraft design</comment>
|
||||
<icon name="io.tangencylabs.solidcraft"/>
|
||||
<icon name="ai.storyteller.solidcraft"/>
|
||||
<sub-class-of type="application/json"/>
|
||||
<!-- shared-mime-info also gives *.sc to Scala scripts (text/x-scala, weight 50); with
|
||||
SolidCraft installed its designs win. -->
|
||||
<glob pattern="*.sc" weight="60"/>
|
||||
<!-- Compatibility shim (docs/compatibility.md): designs saved before .sc, and before the rename. -->
|
||||
<glob pattern="*.solidcraft"/>
|
||||
<!-- Compatibility shim (docs/compatibility.md): designs saved before the rename. -->
|
||||
<glob pattern="*.solvecraft"/>
|
||||
<alias type="application/x-solvecraft"/>
|
||||
</mime-type>
|
||||
@@ -13,7 +13,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>SolidCraft</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>io.tangencylabs.solidcraft</string>
|
||||
<string>ai.storyteller.solidcraft</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
@@ -45,11 +45,8 @@
|
||||
<key>CFBundleTypeName</key>
|
||||
<string>SolidCraft design</string>
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<!-- solidcraft: designs saved before .sc; solvecraft: before the rename (compatibility
|
||||
shims, docs/compatibility.md). -->
|
||||
<array><string>sc</string><string>solidcraft</string><string>solvecraft</string></array>
|
||||
<key>LSItemContentTypes</key>
|
||||
<array><string>io.tangencylabs.solidcraft.design</string></array>
|
||||
<!-- solvecraft: designs saved before the rename (compatibility shim, docs/compatibility.md). -->
|
||||
<array><string>solidcraft</string><string>solvecraft</string></array>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Editor</string>
|
||||
<key>LSHandlerRank</key>
|
||||
@@ -66,24 +63,5 @@
|
||||
<string>Alternate</string>
|
||||
</dict>
|
||||
</array>
|
||||
<!-- The design type: .sc, and the older .solidcraft and .solvecraft (docs/compatibility.md). -->
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>UTTypeIdentifier</key>
|
||||
<string>io.tangencylabs.solidcraft.design</string>
|
||||
<key>UTTypeDescription</key>
|
||||
<string>SolidCraft design</string>
|
||||
<key>UTTypeConformsTo</key>
|
||||
<array><string>public.data</string><string>public.content</string></array>
|
||||
<key>UTTypeTagSpecification</key>
|
||||
<dict>
|
||||
<key>public.filename-extension</key>
|
||||
<array><string>sc</string><string>solidcraft</string><string>solvecraft</string></array>
|
||||
<key>public.mime-type</key>
|
||||
<array><string>application/x-solidcraft</string></array>
|
||||
</dict>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -11,6 +11,6 @@ Some earlier releases were installed as SolveCraft.app. Move that one to the Tra
|
||||
once SolidCraft.app is in Applications, and replace any Dock shortcut with the
|
||||
old name (open SolidCraft.app, then choose Options > Keep in Dock).
|
||||
|
||||
Designs saved as .solidcraft or .solvecraft still open, and SolidCraft copies
|
||||
your settings and recovery files from the old SolveCraft folder on its first
|
||||
launch. New designs are saved as .sc.
|
||||
Designs saved as .solvecraft still open, and SolidCraft copies your settings
|
||||
and recovery files from the old SolveCraft folder on its first launch.
|
||||
New designs are saved as .solidcraft.
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
Per-machine install with a setup wizard: Welcome, install folder (Program Files\SolidCraft by
|
||||
default, remembered for upgrades), Ready, progress, and a Finish page with a ticked "Launch
|
||||
SolidCraft" box. Installs a Start Menu shortcut, solidcraft-cli (and adds its folder to the system PATH), the licence files (including
|
||||
THIRD-PARTY-LICENSES.txt), makes SolidCraft the owner of `.sc` (and of the older `.solidcraft` and pre-rename `.solvecraft`), and offers it under
|
||||
THIRD-PARTY-LICENSES.txt), makes SolidCraft the owner of `.solidcraft` (and of the pre-rename `.solvecraft`), and offers it under
|
||||
"Open with" for STEP, IGES, 3MF and STL. `msiexec /qn` installs silently.
|
||||
Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
|
||||
-->
|
||||
@@ -84,17 +84,15 @@
|
||||
Icon="SolidcraftIcon.exe" IconIndex="0" />
|
||||
</File>
|
||||
|
||||
<!-- .sc: SolidCraft's own format; SolidCraft is its default app. -->
|
||||
<!-- .solidcraft: SolidCraft's own format; SolidCraft is its default app. -->
|
||||
<ProgId Id="Solidcraft.Design" Description="SolidCraft Design" Icon="SolidcraftExe" IconIndex="0">
|
||||
<Extension Id="sc" ContentType="application/x-solidcraft">
|
||||
<Extension Id="solidcraft" ContentType="application/x-solidcraft">
|
||||
<Verb Id="open" Command="Open" TargetFile="SolidcraftExe" Argument=""%1"" />
|
||||
</Extension>
|
||||
</ProgId>
|
||||
<!-- Compatibility shim (docs/compatibility.md): designs saved as .solidcraft (before .sc)
|
||||
and as .solvecraft (before the rename) open as the same ProgId. Registered by hand: a
|
||||
second Extension under one ProgId would write its open verb twice (WIX0091). -->
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.solidcraft" Value="Solidcraft.Design" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.solidcraft" Name="Content Type" Value="application/x-solidcraft" Type="string" />
|
||||
<!-- Compatibility shim (docs/compatibility.md): designs saved before the rename open as
|
||||
the same ProgId. Registered by hand: a second Extension under one ProgId would write
|
||||
its open verb twice (WIX0091). -->
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.solvecraft" Value="Solidcraft.Design" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\.solvecraft" Name="Content Type" Value="application/x-solvecraft" Type="string" />
|
||||
<!-- STEP, IGES and meshes: offered under "Open with", never forced as the default. -->
|
||||
@@ -110,7 +108,6 @@
|
||||
<!-- "Open with" app list and the Run dialog (Win+R "solidcraft"). -->
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solidcraft.exe" Name="FriendlyAppName" Value="SolidCraft" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solidcraft.exe\shell\open\command" Value=""[#SolidcraftExe]" "%1"" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solidcraft.exe\SupportedTypes" Name=".sc" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solidcraft.exe\SupportedTypes" Name=".solidcraft" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solidcraft.exe\SupportedTypes" Name=".solvecraft" Value="" Type="string" />
|
||||
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solidcraft.exe\SupportedTypes" Name=".step" Value="" Type="string" />
|
||||
|
||||
@@ -8,15 +8,6 @@ const fn icon_primary(rgb: [u8; 3]) -> Color32 {
|
||||
impl Tokens {
|
||||
pub const fn light() -> Tokens {
|
||||
Tokens {
|
||||
nav_hover: Color32::from_rgb(233, 237, 242),
|
||||
nav_press: Color32::from_rgb(220, 226, 234),
|
||||
nav_dim: Color32::from_rgb(136, 149, 167),
|
||||
nav_viewport: Color32::from_rgb(238, 241, 245),
|
||||
nav_tip: Color32::from_rgb(27, 36, 48),
|
||||
nav_tip_ink: Color32::from_rgb(255, 255, 255),
|
||||
nav_tip_key_border: Color32::from_rgba_unmultiplied_const(128, 140, 160, 153),
|
||||
nav_field: Color32::from_rgb(255, 255, 255),
|
||||
|
||||
borders: crate::theme::Borders::LEGACY,
|
||||
icon_palette: None,
|
||||
file_dim: Color32::from_rgb(58, 70, 86),
|
||||
@@ -82,7 +73,6 @@ impl Tokens {
|
||||
form_ink: Color32::from_rgb(27, 36, 48),
|
||||
form_dim: Color32::from_rgb(74, 88, 104),
|
||||
form_primary: Color32::from_rgb(35, 110, 173),
|
||||
form_primary_fill: Color32::from_rgb(35, 110, 173),
|
||||
form_error: Color32::from_rgb(161, 52, 44),
|
||||
form_error_fill: Color32::from_rgb(252, 239, 237),
|
||||
form_success: Color32::from_rgb(39, 107, 44),
|
||||
@@ -323,14 +313,6 @@ impl Tokens {
|
||||
t.render_ink = Color32::from_rgb(224, 116, 174);
|
||||
t.render_soft = Color32::from_rgb(64, 35, 54);
|
||||
t.tab_on_fill_dark = Color32::BLACK;
|
||||
t.nav_hover = Color32::from_rgb(58, 65, 80);
|
||||
t.nav_press = Color32::from_rgb(70, 78, 96);
|
||||
t.nav_dim = Color32::from_rgb(124, 135, 154);
|
||||
t.nav_viewport = Color32::from_rgb(38, 43, 53);
|
||||
t.nav_tip = Color32::from_rgb(238, 241, 245);
|
||||
t.nav_tip_ink = Color32::from_rgb(27, 36, 48);
|
||||
t.nav_tip_key_border = Color32::from_rgba_unmultiplied_const(128, 140, 160, 153);
|
||||
t.nav_field = Color32::from_rgb(47, 52, 62);
|
||||
t.dialog_body = Color32::from_rgb(39, 43, 52);
|
||||
t.dialog_footer = Color32::from_rgb(47, 52, 62);
|
||||
t.dialog_rule = Color32::from_rgb(53, 59, 71);
|
||||
@@ -381,7 +363,6 @@ impl Tokens {
|
||||
t.form_ink = t.text;
|
||||
t.form_dim = Color32::from_rgb(180, 187, 200);
|
||||
t.form_primary = t.accent;
|
||||
t.form_primary_fill = t.form_primary;
|
||||
t.form_error = t.error;
|
||||
t.form_error_fill = t.timeline_item_error;
|
||||
t.form_success = Color32::from_rgb(143, 219, 154);
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
{
|
||||
"base": "dark",
|
||||
"colors": {
|
||||
"accent": "#ffffff",
|
||||
"accent_soft": "#000000",
|
||||
"agent_busy": "#ffffff",
|
||||
"agent_ok": "#ffffff",
|
||||
"app_bar": "#000000",
|
||||
"app_bar_hover": "#000000",
|
||||
"app_bar_hover_button": "#000000",
|
||||
"app_bar_hover_strong": "#000000",
|
||||
"app_bar_hover_subtle": "#000000",
|
||||
"app_bar_separator": "#ffffff",
|
||||
"app_bar_tab_active": "#000000",
|
||||
"app_bar_text": "#ffffff",
|
||||
"axis_x": "#ffffff",
|
||||
"axis_y": "#ffffff",
|
||||
"axis_z": "#ffffff",
|
||||
"bend_down": "#ffffff",
|
||||
"bend_up": "#ffffff",
|
||||
"body": "#000000",
|
||||
"body_edge": "#ffffff",
|
||||
"body_selected": "#ffffff",
|
||||
"border": "#ffffff",
|
||||
"box_crossing": "#ffffff",
|
||||
"box_window": "#ffffff",
|
||||
"chip": "#ffffff",
|
||||
"construction_geom": "#ffffff",
|
||||
"construction_plane": "#000000",
|
||||
"continuity": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#ffffff"
|
||||
],
|
||||
"control_border": "#ffffff",
|
||||
"control_fill": "#000000",
|
||||
"cube_border": "#ffffff",
|
||||
"cube_face": "#000000",
|
||||
"cube_hover": "#000000",
|
||||
"cube_text": "#ffffff",
|
||||
"dialog_bg": "#000000",
|
||||
"dialog_body": "#000000",
|
||||
"dialog_border": "#ffffff",
|
||||
"dialog_footer": "#000000",
|
||||
"dialog_label": "#ffffff",
|
||||
"dialog_rule": "#ffffff",
|
||||
"dimension": "#ffffff",
|
||||
"dimension_driven": "#ffffff",
|
||||
"error": "#ffffff",
|
||||
"field": "#000000",
|
||||
"file_background": "#000000",
|
||||
"file_border": "#ffffff",
|
||||
"file_dim": "#ffffff",
|
||||
"file_image_tint": "#ffffff",
|
||||
"file_primary": "#000000",
|
||||
"file_primary_border": "#ffffff",
|
||||
"file_primary_ink": "#ffffff",
|
||||
"file_rail": "#000000",
|
||||
"file_rail_hover": "#000000",
|
||||
"file_rail_ink": "#ffffff",
|
||||
"file_recovery": "#000000",
|
||||
"file_recovery_border": "#ffffff",
|
||||
"file_recovery_ink": "#ffffff",
|
||||
"file_recovery_well": "#000000",
|
||||
"file_surface": "#000000",
|
||||
"form_accent": "#ffffff",
|
||||
"form_dim": "#ffffff",
|
||||
"form_error": "#ffffff",
|
||||
"form_error_fill": "#000000",
|
||||
"form_fill": "#ffffff",
|
||||
"form_ink": "#ffffff",
|
||||
"form_primary": "#000000",
|
||||
"form_primary_fill": "#ffffff",
|
||||
"form_soft": "#000000",
|
||||
"form_success": "#ffffff",
|
||||
"form_success_fill": "#000000",
|
||||
"form_tab_ink": "#ffffff",
|
||||
"glyph_bg": "#000000",
|
||||
"glyph_edge": "#ffffff",
|
||||
"glyph_text": "#ffffff",
|
||||
"grid": "#000000",
|
||||
"grid_major": "#ffffff",
|
||||
"hover": "#000000",
|
||||
"hover_edge": "#ffffff",
|
||||
"hover_edge_halo": "#000000",
|
||||
"hover_profile": "#ffffff",
|
||||
"hover_profile_edge": "#000000",
|
||||
"icon": "#ffffff",
|
||||
"icons.agent": "#ffffff",
|
||||
"icons.highlight": "#000000",
|
||||
"icons.marker_halo": "#000000",
|
||||
"interference": "#ffffff",
|
||||
"logo": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#ffffff"
|
||||
],
|
||||
"manipulator": "#ffffff",
|
||||
"mesh_accent": "#ffffff",
|
||||
"mesh_fill": "#ffffff",
|
||||
"mesh_ink": "#ffffff",
|
||||
"mesh_soft": "#000000",
|
||||
"nav_dim": "#ffffff",
|
||||
"nav_field": "#000000",
|
||||
"nav_hover": "#000000",
|
||||
"nav_press": "#ffffff",
|
||||
"nav_tip": "#ffffff",
|
||||
"nav_tip_ink": "#000000",
|
||||
"nav_tip_key_border": "#000000",
|
||||
"nav_viewport": "#000000",
|
||||
"on_accent": "#000000",
|
||||
"origin_plane": "#000000",
|
||||
"origin_plane_edge": "#ffffff",
|
||||
"origin_plane_hover": "#ffffff",
|
||||
"origin_point": "#ffffff",
|
||||
"overlay": "#000000",
|
||||
"panel": "#000000",
|
||||
"panel_header": "#000000",
|
||||
"preview_add": "#ffffff",
|
||||
"preview_cut": "#ffffff",
|
||||
"preview_new": "#ffffff",
|
||||
"preview_sketch": "#ffffff",
|
||||
"profile_fill": "#000000",
|
||||
"render_accent": "#ffffff",
|
||||
"render_fill": "#ffffff",
|
||||
"render_ink": "#ffffff",
|
||||
"render_soft": "#000000",
|
||||
"rubber_band": "#ffffff",
|
||||
"section_cap": "#ffffff",
|
||||
"sel_edge": "#000000",
|
||||
"sel_edge_rim": "#000000",
|
||||
"sel_face": "#ffffff",
|
||||
"sel_profile": "#ffffff",
|
||||
"sel_profile_edge": "#000000",
|
||||
"sel_vertex": "#ffffff",
|
||||
"selected_bg": "#ffffff",
|
||||
"selected_ink": "#000000",
|
||||
"sheet_fill": "#ffffff",
|
||||
"sheet_ink": "#ffffff",
|
||||
"sheet_soft": "#000000",
|
||||
"sketch": "#ffffff",
|
||||
"sketch_accent": "#ffffff",
|
||||
"sketch_centerline": "#ffffff",
|
||||
"sketch_construction": "#ffffff",
|
||||
"sketch_fixed": "#ffffff",
|
||||
"sketch_halo": "#000000",
|
||||
"sketch_ink": "#ffffff",
|
||||
"sketch_locked": "#ffffff",
|
||||
"sketch_lost": "#ffffff",
|
||||
"sketch_point": "#ffffff",
|
||||
"sketch_point_free": "#ffffff",
|
||||
"sketch_projected": "#ffffff",
|
||||
"sketch_soft": "#000000",
|
||||
"surface_accent": "#ffffff",
|
||||
"surface_fill": "#ffffff",
|
||||
"surface_ink": "#ffffff",
|
||||
"surface_soft": "#000000",
|
||||
"tab_on_fill_dark": "#ffffff",
|
||||
"text": "#ffffff",
|
||||
"text_dim": "#ffffff",
|
||||
"timeline": "#000000",
|
||||
"timeline_item": "#000000",
|
||||
"timeline_item_error": "#000000",
|
||||
"timeline_marker": "#ffffff",
|
||||
"toolbar": "#000000",
|
||||
"viewport_bottom": "#000000",
|
||||
"viewport_top": "#000000",
|
||||
"warning": "#ffffff"
|
||||
},
|
||||
"format": "solidcraft-theme/1",
|
||||
"name": "1-bit Dark",
|
||||
"radii": {
|
||||
"radius_sm": 0,
|
||||
"radius_md": 0,
|
||||
"radius_lg": 0,
|
||||
"radius_xl": 0,
|
||||
"control_radius": 0,
|
||||
"sketch_mode_radius": 0
|
||||
},
|
||||
"icon_palette": {
|
||||
"ink": "#ffffff",
|
||||
"line": "#ffffff",
|
||||
"passive-1": "#000000",
|
||||
"passive-2": "#000000",
|
||||
"passive-3": "#000000",
|
||||
"guide": "#ffffff",
|
||||
"solid": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#ffffff"
|
||||
],
|
||||
"surface": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#ffffff"
|
||||
],
|
||||
"mesh": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#ffffff"
|
||||
],
|
||||
"form": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#ffffff"
|
||||
],
|
||||
"sketch": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#ffffff"
|
||||
],
|
||||
"constraint": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#ffffff"
|
||||
],
|
||||
"construction": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#ffffff"
|
||||
],
|
||||
"active-1": "#000000",
|
||||
"active-2": "#000000",
|
||||
"active-3": "#ffffff",
|
||||
"component-orange-1": "#000000",
|
||||
"component-orange-2": "#000000",
|
||||
"component-orange-3": "#ffffff",
|
||||
"component-yellow-1": "#000000",
|
||||
"component-yellow-2": "#000000",
|
||||
"component-yellow-3": "#ffffff"
|
||||
},
|
||||
"borders": {
|
||||
"primary": {
|
||||
"width": 1,
|
||||
"color": "#ffffff"
|
||||
},
|
||||
"secondary": {
|
||||
"width": 1,
|
||||
"color": "#ffffff"
|
||||
},
|
||||
"tertiary": {
|
||||
"width": 1,
|
||||
"color": "#ffffff"
|
||||
},
|
||||
"container": {
|
||||
"width": 1,
|
||||
"color": "#ffffff"
|
||||
},
|
||||
"control": {
|
||||
"width": 1,
|
||||
"color": "#ffffff"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,255 +0,0 @@
|
||||
{
|
||||
"base": "light",
|
||||
"colors": {
|
||||
"accent": "#000000",
|
||||
"accent_soft": "#ffffff",
|
||||
"agent_busy": "#000000",
|
||||
"agent_ok": "#000000",
|
||||
"app_bar": "#ffffff",
|
||||
"app_bar_hover": "#ffffff",
|
||||
"app_bar_hover_button": "#ffffff",
|
||||
"app_bar_hover_strong": "#ffffff",
|
||||
"app_bar_hover_subtle": "#ffffff",
|
||||
"app_bar_separator": "#000000",
|
||||
"app_bar_tab_active": "#ffffff",
|
||||
"app_bar_text": "#000000",
|
||||
"axis_x": "#000000",
|
||||
"axis_y": "#000000",
|
||||
"axis_z": "#000000",
|
||||
"bend_down": "#000000",
|
||||
"bend_up": "#000000",
|
||||
"body": "#ffffff",
|
||||
"body_edge": "#000000",
|
||||
"body_selected": "#000000",
|
||||
"border": "#000000",
|
||||
"box_crossing": "#000000",
|
||||
"box_window": "#000000",
|
||||
"chip": "#000000",
|
||||
"construction_geom": "#000000",
|
||||
"construction_plane": "#ffffff",
|
||||
"continuity": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#000000"
|
||||
],
|
||||
"control_border": "#000000",
|
||||
"control_fill": "#ffffff",
|
||||
"cube_border": "#000000",
|
||||
"cube_face": "#ffffff",
|
||||
"cube_hover": "#ffffff",
|
||||
"cube_text": "#000000",
|
||||
"dialog_bg": "#ffffff",
|
||||
"dialog_body": "#ffffff",
|
||||
"dialog_border": "#000000",
|
||||
"dialog_footer": "#ffffff",
|
||||
"dialog_label": "#000000",
|
||||
"dialog_rule": "#000000",
|
||||
"dimension": "#000000",
|
||||
"dimension_driven": "#000000",
|
||||
"error": "#000000",
|
||||
"field": "#ffffff",
|
||||
"file_background": "#ffffff",
|
||||
"file_border": "#000000",
|
||||
"file_dim": "#000000",
|
||||
"file_image_tint": "#ffffff",
|
||||
"file_primary": "#ffffff",
|
||||
"file_primary_border": "#000000",
|
||||
"file_primary_ink": "#000000",
|
||||
"file_rail": "#ffffff",
|
||||
"file_rail_hover": "#ffffff",
|
||||
"file_rail_ink": "#000000",
|
||||
"file_recovery": "#ffffff",
|
||||
"file_recovery_border": "#000000",
|
||||
"file_recovery_ink": "#000000",
|
||||
"file_recovery_well": "#ffffff",
|
||||
"file_surface": "#ffffff",
|
||||
"form_accent": "#000000",
|
||||
"form_dim": "#000000",
|
||||
"form_error": "#000000",
|
||||
"form_error_fill": "#ffffff",
|
||||
"form_fill": "#000000",
|
||||
"form_ink": "#000000",
|
||||
"form_primary": "#ffffff",
|
||||
"form_primary_fill": "#000000",
|
||||
"form_soft": "#ffffff",
|
||||
"form_success": "#000000",
|
||||
"form_success_fill": "#ffffff",
|
||||
"form_tab_ink": "#000000",
|
||||
"glyph_bg": "#ffffff",
|
||||
"glyph_edge": "#000000",
|
||||
"glyph_text": "#000000",
|
||||
"grid": "#ffffff",
|
||||
"grid_major": "#000000",
|
||||
"hover": "#ffffff",
|
||||
"hover_edge": "#000000",
|
||||
"hover_edge_halo": "#ffffff",
|
||||
"hover_profile": "#000000",
|
||||
"hover_profile_edge": "#ffffff",
|
||||
"icon": "#000000",
|
||||
"icons.agent": "#000000",
|
||||
"icons.highlight": "#ffffff",
|
||||
"icons.marker_halo": "#ffffff",
|
||||
"interference": "#000000",
|
||||
"logo": [
|
||||
"#000000",
|
||||
"#000000",
|
||||
"#000000"
|
||||
],
|
||||
"manipulator": "#000000",
|
||||
"mesh_accent": "#000000",
|
||||
"mesh_fill": "#000000",
|
||||
"mesh_ink": "#000000",
|
||||
"mesh_soft": "#ffffff",
|
||||
"nav_dim": "#000000",
|
||||
"nav_field": "#ffffff",
|
||||
"nav_hover": "#ffffff",
|
||||
"nav_press": "#000000",
|
||||
"nav_tip": "#000000",
|
||||
"nav_tip_ink": "#ffffff",
|
||||
"nav_tip_key_border": "#ffffff",
|
||||
"nav_viewport": "#ffffff",
|
||||
"on_accent": "#ffffff",
|
||||
"origin_plane": "#ffffff",
|
||||
"origin_plane_edge": "#000000",
|
||||
"origin_plane_hover": "#000000",
|
||||
"origin_point": "#000000",
|
||||
"overlay": "#ffffff",
|
||||
"panel": "#ffffff",
|
||||
"panel_header": "#ffffff",
|
||||
"preview_add": "#000000",
|
||||
"preview_cut": "#000000",
|
||||
"preview_new": "#000000",
|
||||
"preview_sketch": "#000000",
|
||||
"profile_fill": "#ffffff",
|
||||
"render_accent": "#000000",
|
||||
"render_fill": "#000000",
|
||||
"render_ink": "#000000",
|
||||
"render_soft": "#ffffff",
|
||||
"rubber_band": "#000000",
|
||||
"section_cap": "#000000",
|
||||
"sel_edge": "#ffffff",
|
||||
"sel_edge_rim": "#ffffff",
|
||||
"sel_face": "#000000",
|
||||
"sel_profile": "#000000",
|
||||
"sel_profile_edge": "#ffffff",
|
||||
"sel_vertex": "#000000",
|
||||
"selected_bg": "#000000",
|
||||
"selected_ink": "#ffffff",
|
||||
"sheet_fill": "#000000",
|
||||
"sheet_ink": "#000000",
|
||||
"sheet_soft": "#ffffff",
|
||||
"sketch": "#000000",
|
||||
"sketch_accent": "#000000",
|
||||
"sketch_centerline": "#000000",
|
||||
"sketch_construction": "#000000",
|
||||
"sketch_fixed": "#000000",
|
||||
"sketch_halo": "#ffffff",
|
||||
"sketch_ink": "#000000",
|
||||
"sketch_locked": "#000000",
|
||||
"sketch_lost": "#000000",
|
||||
"sketch_point": "#000000",
|
||||
"sketch_point_free": "#000000",
|
||||
"sketch_projected": "#000000",
|
||||
"sketch_soft": "#ffffff",
|
||||
"surface_accent": "#000000",
|
||||
"surface_fill": "#000000",
|
||||
"surface_ink": "#000000",
|
||||
"surface_soft": "#ffffff",
|
||||
"tab_on_fill_dark": "#000000",
|
||||
"text": "#000000",
|
||||
"text_dim": "#000000",
|
||||
"timeline": "#ffffff",
|
||||
"timeline_item": "#ffffff",
|
||||
"timeline_item_error": "#ffffff",
|
||||
"timeline_marker": "#000000",
|
||||
"toolbar": "#ffffff",
|
||||
"viewport_bottom": "#ffffff",
|
||||
"viewport_top": "#ffffff",
|
||||
"warning": "#000000"
|
||||
},
|
||||
"format": "solidcraft-theme/1",
|
||||
"name": "1-bit",
|
||||
"radii": {
|
||||
"radius_sm": 0,
|
||||
"radius_md": 0,
|
||||
"radius_lg": 0,
|
||||
"radius_xl": 0,
|
||||
"control_radius": 0,
|
||||
"sketch_mode_radius": 0
|
||||
},
|
||||
"icon_palette": {
|
||||
"ink": "#000000",
|
||||
"line": "#000000",
|
||||
"passive-1": "#ffffff",
|
||||
"passive-2": "#ffffff",
|
||||
"passive-3": "#ffffff",
|
||||
"guide": "#000000",
|
||||
"solid": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#000000"
|
||||
],
|
||||
"surface": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#000000"
|
||||
],
|
||||
"mesh": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#000000"
|
||||
],
|
||||
"form": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#000000"
|
||||
],
|
||||
"sketch": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#000000"
|
||||
],
|
||||
"constraint": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#000000"
|
||||
],
|
||||
"construction": [
|
||||
"#ffffff",
|
||||
"#ffffff",
|
||||
"#000000"
|
||||
],
|
||||
"active-1": "#ffffff",
|
||||
"active-2": "#ffffff",
|
||||
"active-3": "#000000",
|
||||
"component-orange-1": "#ffffff",
|
||||
"component-orange-2": "#ffffff",
|
||||
"component-orange-3": "#000000",
|
||||
"component-yellow-1": "#ffffff",
|
||||
"component-yellow-2": "#ffffff",
|
||||
"component-yellow-3": "#000000"
|
||||
},
|
||||
"borders": {
|
||||
"primary": {
|
||||
"width": 1,
|
||||
"color": "#000000"
|
||||
},
|
||||
"secondary": {
|
||||
"width": 1,
|
||||
"color": "#000000"
|
||||
},
|
||||
"tertiary": {
|
||||
"width": 1,
|
||||
"color": "#000000"
|
||||
},
|
||||
"container": {
|
||||
"width": 1,
|
||||
"color": "#000000"
|
||||
},
|
||||
"control": {
|
||||
"width": 1,
|
||||
"color": "#000000"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,6 @@
|
||||
"dimension_driven": "#6c6863",
|
||||
"error": "#c71529",
|
||||
"field": "#fffdfa",
|
||||
"file_rail": "#e1ddd8",
|
||||
"file_rail_hover": "#ebe7e2",
|
||||
"file_rail_ink": "#13110e",
|
||||
"form_accent": "#5844bc",
|
||||
"form_dim": "#6c6863",
|
||||
"form_error": "#c71529",
|
||||
|
||||
@@ -1,224 +0,0 @@
|
||||
{
|
||||
"base": "dark",
|
||||
"colors": {
|
||||
"accent": "#55ffff",
|
||||
"accent_soft": "#ff55ff",
|
||||
"agent_busy": "#55ffff",
|
||||
"agent_ok": "#55ff55",
|
||||
"app_bar": "#000000",
|
||||
"app_bar_hover": "#ffffff18",
|
||||
"app_bar_hover_button": "#ffffff22",
|
||||
"app_bar_hover_strong": "#ffffff30",
|
||||
"app_bar_hover_subtle": "#ffffff10",
|
||||
"app_bar_separator": "#00aaaa",
|
||||
"app_bar_tab_active": "#aa00aa",
|
||||
"app_bar_text": "#ffffff",
|
||||
"axis_x": "#ff5555",
|
||||
"axis_y": "#55ff55",
|
||||
"axis_z": "#5555ff",
|
||||
"bend_down": "#ffff55",
|
||||
"bend_up": "#55ff55",
|
||||
"body": "#aa00aa",
|
||||
"body_edge": "#55ffff",
|
||||
"body_selected": "#ffff55",
|
||||
"border": "#55ffff",
|
||||
"box_crossing": "#ff55ff",
|
||||
"box_window": "#55ffff",
|
||||
"chip": "#aa00aa",
|
||||
"construction_geom": "#aaaaaa",
|
||||
"construction_plane": "#ffff5522",
|
||||
"continuity": [
|
||||
"#55ff55",
|
||||
"#5555ff",
|
||||
"#ffff55",
|
||||
"#ff55ff"
|
||||
],
|
||||
"control_border": "#55ffff",
|
||||
"control_fill": "#000000",
|
||||
"cube_border": "#55ffff",
|
||||
"cube_face": "#000000",
|
||||
"cube_hover": "#aa00aa",
|
||||
"cube_text": "#ffffff",
|
||||
"dialog_bg": "#000000",
|
||||
"dialog_body": "#000000",
|
||||
"dialog_border": "#55ffff",
|
||||
"dialog_footer": "#000000",
|
||||
"dialog_label": "#55ffff",
|
||||
"dialog_rule": "#00aaaa",
|
||||
"dimension": "#ffffff",
|
||||
"dimension_driven": "#aaaaaa",
|
||||
"error": "#ff5555",
|
||||
"field": "#000000",
|
||||
"file_background": "#000000",
|
||||
"file_border": "#55ffff",
|
||||
"file_dim": "#55ffff",
|
||||
"file_image_tint": "#ffffff",
|
||||
"file_primary": "#000000",
|
||||
"file_primary_border": "#55ffff",
|
||||
"file_primary_ink": "#55ffff",
|
||||
"file_rail": "#000000",
|
||||
"file_rail_hover": "#aa00aa",
|
||||
"file_rail_ink": "#ffffff",
|
||||
"file_recovery": "#000000",
|
||||
"file_recovery_border": "#ff55ff",
|
||||
"file_recovery_ink": "#ff55ff",
|
||||
"file_recovery_well": "#000000",
|
||||
"file_surface": "#000000",
|
||||
"form_accent": "#aa0000",
|
||||
"form_dim": "#55ffff",
|
||||
"form_error": "#ff5555",
|
||||
"form_error_fill": "#000000",
|
||||
"form_fill": "#ff5555",
|
||||
"form_ink": "#ffffff",
|
||||
"form_primary": "#ff55ff",
|
||||
"form_primary_fill": "#ff55ff",
|
||||
"form_soft": "#000000",
|
||||
"form_success": "#55ff55",
|
||||
"form_success_fill": "#000000",
|
||||
"form_tab_ink": "#ff5555",
|
||||
"glyph_bg": "#000000",
|
||||
"glyph_edge": "#ff55ff",
|
||||
"glyph_text": "#ff55ff",
|
||||
"grid": "#555555",
|
||||
"grid_major": "#00aaaa",
|
||||
"hover": "#aa00aa",
|
||||
"hover_edge": "#ffffff",
|
||||
"hover_edge_halo": "#000000",
|
||||
"hover_profile": "#ffffff40",
|
||||
"hover_profile_edge": "#ffffff",
|
||||
"icon": "#ffffff",
|
||||
"icons.agent": "#ff55ff",
|
||||
"icons.highlight": "#ffffff",
|
||||
"icons.marker_halo": "#000000cc",
|
||||
"interference": "#ff5555",
|
||||
"logo": [
|
||||
"#ffffff",
|
||||
"#55ffff",
|
||||
"#ff55ff"
|
||||
],
|
||||
"manipulator": "#55ffff",
|
||||
"mesh_accent": "#00aa00",
|
||||
"mesh_fill": "#55ff55",
|
||||
"mesh_ink": "#55ff55",
|
||||
"mesh_soft": "#000000",
|
||||
"nav_dim": "#55ffff",
|
||||
"nav_field": "#000000",
|
||||
"nav_hover": "#aa00aa",
|
||||
"nav_press": "#ff55ff",
|
||||
"nav_tip": "#ffffff",
|
||||
"nav_tip_ink": "#000000",
|
||||
"nav_tip_key_border": "#55ffff",
|
||||
"nav_viewport": "#000000",
|
||||
"on_accent": "#000000",
|
||||
"origin_plane": "#55ffff20",
|
||||
"origin_plane_edge": "#55ffff80",
|
||||
"origin_plane_hover": "#ff55ff40",
|
||||
"origin_point": "#ffffff",
|
||||
"overlay": "#000000",
|
||||
"panel": "#000000",
|
||||
"panel_header": "#000000",
|
||||
"preview_add": "#55ffff",
|
||||
"preview_cut": "#ff555566",
|
||||
"preview_new": "#ffff55",
|
||||
"preview_sketch": "#55ff55",
|
||||
"profile_fill": "#55ffff22",
|
||||
"render_accent": "#aaaaaa",
|
||||
"render_fill": "#ffffff",
|
||||
"render_ink": "#ffffff",
|
||||
"render_soft": "#000000",
|
||||
"rubber_band": "#55ffff",
|
||||
"section_cap": "#ffff55",
|
||||
"sel_edge": "#ffff55",
|
||||
"sel_edge_rim": "#000000",
|
||||
"sel_face": "#ffff55",
|
||||
"sel_profile": "#ffff55",
|
||||
"sel_profile_edge": "#ffff55",
|
||||
"sel_vertex": "#ffff55",
|
||||
"selected_bg": "#ff55ff",
|
||||
"selected_ink": "#000000",
|
||||
"sheet_fill": "#aaaaaa",
|
||||
"sheet_ink": "#aaaaaa",
|
||||
"sheet_soft": "#000000",
|
||||
"sketch": "#55ffff",
|
||||
"sketch_accent": "#ff55ff",
|
||||
"sketch_centerline": "#ffff55",
|
||||
"sketch_construction": "#aaaaaa",
|
||||
"sketch_fixed": "#ffffff",
|
||||
"sketch_halo": "#000000",
|
||||
"sketch_ink": "#ff55ff",
|
||||
"sketch_locked": "#55ff55",
|
||||
"sketch_lost": "#ff5555",
|
||||
"sketch_point": "#ffffff",
|
||||
"sketch_point_free": "#55ffff",
|
||||
"sketch_projected": "#ff55ff",
|
||||
"sketch_soft": "#000000",
|
||||
"surface_accent": "#aa5500",
|
||||
"surface_fill": "#ffff55",
|
||||
"surface_ink": "#ffff55",
|
||||
"surface_soft": "#000000",
|
||||
"tab_on_fill_dark": "#000000",
|
||||
"text": "#ffffff",
|
||||
"text_dim": "#55ffff",
|
||||
"timeline": "#000000",
|
||||
"timeline_item": "#000000",
|
||||
"timeline_item_error": "#aa0000",
|
||||
"timeline_marker": "#55ffff",
|
||||
"toolbar": "#000000",
|
||||
"viewport_bottom": "#000000",
|
||||
"viewport_top": "#000000",
|
||||
"warning": "#ffff55"
|
||||
},
|
||||
"format": "solidcraft-theme/1",
|
||||
"name": "CGA",
|
||||
"radii": {
|
||||
"radius_sm": 0,
|
||||
"radius_md": 0,
|
||||
"radius_lg": 0,
|
||||
"radius_xl": 0,
|
||||
"control_radius": 0,
|
||||
"sketch_mode_radius": 0
|
||||
},
|
||||
"icon_palette": {
|
||||
"ink": "#000000",
|
||||
"line": "#ffffff",
|
||||
"passive-1": "#ffffff",
|
||||
"passive-2": "#aaaaaa",
|
||||
"passive-3": "#555555",
|
||||
"guide": "#55ffff",
|
||||
"solid": [
|
||||
"#ffffff",
|
||||
"#55ffff",
|
||||
"#00aaaa"
|
||||
],
|
||||
"surface": [
|
||||
"#ffffff",
|
||||
"#ffff55",
|
||||
"#aa5500"
|
||||
],
|
||||
"mesh": [
|
||||
"#ffffff",
|
||||
"#55ff55",
|
||||
"#00aa00"
|
||||
],
|
||||
"form": [
|
||||
"#ffffff",
|
||||
"#ff5555",
|
||||
"#aa0000"
|
||||
],
|
||||
"sketch": [
|
||||
"#ffffff",
|
||||
"#ff55ff",
|
||||
"#aa00aa"
|
||||
],
|
||||
"constraint": [
|
||||
"#ffffff",
|
||||
"#ff55ff",
|
||||
"#aa00aa"
|
||||
],
|
||||
"construction": [
|
||||
"#ffffff",
|
||||
"#ffff55",
|
||||
"#aa5500"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -54,9 +54,6 @@
|
||||
"dimension_driven": "#8f9296",
|
||||
"error": "#f85052",
|
||||
"field": "#2c2e31",
|
||||
"file_rail": "#0f1113",
|
||||
"file_rail_hover": "#222527",
|
||||
"file_rail_ink": "#e1e3e5",
|
||||
"form_accent": "#a6a2fc",
|
||||
"form_dim": "#8f9296",
|
||||
"form_error": "#ff6060",
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
//! a component's over the physical material's look; a body with none of these keeps the colour
|
||||
//! it was imported with.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solidcraft_geom::Vec3;
|
||||
@@ -129,95 +129,6 @@ impl Appearances {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.bodies.is_empty() && self.faces.is_empty() && self.components.is_empty()
|
||||
}
|
||||
|
||||
/// Bound bulk appearance operations, including designs read from untrusted files.
|
||||
pub fn check_budget(&self) -> crate::Result<()> {
|
||||
if self.bodies.len().saturating_add(self.faces.len()).saturating_add(self.components.len()) > 100_000 {
|
||||
return Err(crate::DocError::Invalid("at most 100000 appearance overrides can be processed".into()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Build a replacement without changing the source. Equality includes the label and
|
||||
/// every optical value. Physical-material defaults are deliberately outside this scope.
|
||||
/// `None` means no change, allowing callers to preserve history and revision.
|
||||
pub fn replacing(&self, from: &Look, to: &Look, scope: Option<&AppearanceScope>) -> crate::Result<Option<(Self, usize)>> {
|
||||
self.check_budget()?;
|
||||
for look in [from, to] {
|
||||
if !look.opacity.is_finite() || !(0.0..=1.0).contains(&look.opacity) {
|
||||
return Err(crate::DocError::Invalid("opacity must be finite and in 0..1".into()));
|
||||
}
|
||||
look.opaque_surface()?;
|
||||
}
|
||||
if from == to {
|
||||
return Ok(None);
|
||||
}
|
||||
let body_in_scope = |body: &str| scope.is_none_or(|s| s.bodies.contains(body));
|
||||
let component_in_scope = |id: &u64| scope.is_none_or(|s| s.components.contains(id));
|
||||
let count = self.bodies.iter().filter(|(body, look)| body_in_scope(body) && *look == from).count()
|
||||
+ self.faces.iter().filter(|face| body_in_scope(&face.body) && &face.look == from).count()
|
||||
+ self.components.iter().filter(|(id, look)| component_in_scope(id) && *look == from).count();
|
||||
if count == 0 {
|
||||
return Ok(None);
|
||||
}
|
||||
let mut replaced = self.clone();
|
||||
for (body, look) in &mut replaced.bodies {
|
||||
if body_in_scope(body) && look == from {
|
||||
*look = to.clone();
|
||||
}
|
||||
}
|
||||
for face in &mut replaced.faces {
|
||||
if body_in_scope(&face.body) && &face.look == from {
|
||||
face.look = to.clone();
|
||||
}
|
||||
}
|
||||
for (id, look) in &mut replaced.components {
|
||||
if component_in_scope(id) && look == from {
|
||||
*look = to.clone();
|
||||
}
|
||||
}
|
||||
Ok(Some((replaced, count)))
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact override containers for replacement. A body includes its face overrides;
|
||||
/// a component selects its own override, independently of the bodies it owns.
|
||||
#[derive(Clone, Debug, Default)]
|
||||
pub struct AppearanceScope {
|
||||
pub bodies: BTreeSet<String>,
|
||||
pub components: BTreeSet<u64>,
|
||||
}
|
||||
|
||||
/// A target in an evaluated model. Face indices are those of `model.faces`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum AppearanceTarget<'a> {
|
||||
Body(&'a str),
|
||||
Face { body: &'a str, face: usize },
|
||||
Component(u64),
|
||||
}
|
||||
|
||||
/// The four override steps, in descending precedence. Imported paint is a display
|
||||
/// fallback, not an override or physical material, and is not a fifth step.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct AppearanceChain {
|
||||
pub face: Option<Look>,
|
||||
pub body: Option<Look>,
|
||||
pub component: Option<Look>,
|
||||
pub material: Option<Look>,
|
||||
}
|
||||
|
||||
impl AppearanceChain {
|
||||
pub fn steps(&self) -> [(&'static str, Option<&Look>); 4] {
|
||||
[("face", self.face.as_ref()), ("body", self.body.as_ref()), ("component", self.component.as_ref()), ("material", self.material.as_ref())]
|
||||
}
|
||||
|
||||
pub fn active(&self) -> Option<&'static str> {
|
||||
self.steps().into_iter().find_map(|(step, look)| look.map(|_| step))
|
||||
}
|
||||
|
||||
pub fn resolved(&self) -> Option<&Look> {
|
||||
self.steps().into_iter().find_map(|(_, look)| look)
|
||||
}
|
||||
}
|
||||
|
||||
/// The built-in appearance library: (name, colour, opacity).
|
||||
@@ -250,96 +161,15 @@ pub fn material_look(material: &str) -> Option<Look> {
|
||||
}
|
||||
|
||||
impl Document {
|
||||
/// Read the same precedence used by body and face display resolution. Components
|
||||
/// have no material assignment of their own: the material step is present only
|
||||
/// when their directly owned bodies all have the same material look. Empty or
|
||||
/// mixed components have no material step. Root is not an assignable component.
|
||||
pub fn appearance_chain(&self, state: &crate::ModelState, target: AppearanceTarget<'_>) -> crate::Result<AppearanceChain> {
|
||||
self.appearances.check_budget()?;
|
||||
match target {
|
||||
AppearanceTarget::Body(name) | AppearanceTarget::Face { body: name, .. } => {
|
||||
let body = state.body(name).ok_or_else(|| crate::DocError::Invalid(format!("no body `{name}`")))?;
|
||||
let mut chain = self.body_appearance_chain(body.definition_name(), self.model_body_component(body));
|
||||
if let AppearanceTarget::Face { face, .. } = target {
|
||||
if face >= body.body.face_count() {
|
||||
return Err(crate::DocError::Invalid("unknown face index".into()));
|
||||
}
|
||||
let mut overrides = Vec::new();
|
||||
self.apply_face_overrides(body, &mut overrides);
|
||||
chain.face = overrides.into_iter().find_map(|(index, look)| (index == face).then_some(look));
|
||||
}
|
||||
Ok(chain)
|
||||
}
|
||||
AppearanceTarget::Component(id) => {
|
||||
if id == 0 || !self.components.iter().any(|c| c.id == id) {
|
||||
return Err(crate::DocError::Invalid("unknown or root component".into()));
|
||||
}
|
||||
if state.bodies.len() > 100_000 {
|
||||
return Err(crate::DocError::Invalid("too many bodies to read a component appearance chain".into()));
|
||||
}
|
||||
// Legacy ownership can refer to a feature. Index it once, rather
|
||||
// than scanning the timeline for each body of a large component.
|
||||
let features: BTreeMap<_, _> = self.features.iter().rev().map(|f| (f.id, f.component)).collect();
|
||||
let owner = |b: &&crate::ModelBody| {
|
||||
if b.transferred || b.source_name.is_some() {
|
||||
b.component
|
||||
} else {
|
||||
self.body_components.get(&b.name).copied().or_else(|| features.get(&b.feature).copied()).unwrap_or(0)
|
||||
}
|
||||
};
|
||||
let mut materials = state
|
||||
.bodies
|
||||
.iter()
|
||||
.filter(|b| owner(b) == id)
|
||||
.map(|b| self.materials.get(b.definition_name()).and_then(|m| self.material_look(m)));
|
||||
let material = materials.next().flatten().filter(|first| materials.all(|look| look.as_ref() == Some(first)));
|
||||
Ok(AppearanceChain { face: None, body: None, component: self.appearances.components.get(&id).cloned(), material })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn body_appearance_chain(&self, name: &str, component: u64) -> AppearanceChain {
|
||||
AppearanceChain {
|
||||
face: None,
|
||||
body: self.appearances.bodies.get(name).cloned(),
|
||||
component: (component != 0).then(|| self.appearances.components.get(&component).cloned()).flatten(),
|
||||
material: self.materials.get(name).and_then(|material| self.material_look(material)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Resolve a picked face through the same appearance chain used by the viewport.
|
||||
pub fn model_face_look(&self, body: &crate::ModelBody, face: usize) -> Option<Look> {
|
||||
self.face_colors(body).into_iter().find(|(index, _)| *index == face).map(|(_, look)| look).or_else(|| self.model_display_look(body))
|
||||
}
|
||||
|
||||
/// Body appearance including the imported fallback used by `body_color`.
|
||||
pub fn model_display_look(&self, body: &crate::ModelBody) -> Option<Look> {
|
||||
self.model_body_look(body).or_else(|| {
|
||||
let color = body.body.color().or_else(|| body.imported.as_ref().and_then(|p| p.color))?;
|
||||
Some(Look::custom(color.map(|v| (v.clamp(0.0, 1.0) * 255.0).round() as u8), self.model_body_opacity(body)))
|
||||
})
|
||||
}
|
||||
|
||||
/// Distinct appearances used by an object, including its face overrides. `None` is
|
||||
/// the unassigned/imported body appearance. Repeated faces count as one object user.
|
||||
pub fn model_used_looks(&self, body: &crate::ModelBody) -> Vec<Option<Look>> {
|
||||
let faces = self.face_colors(body);
|
||||
let mut looks = Vec::new();
|
||||
if faces.len() < body.body.face_count() || faces.is_empty() {
|
||||
looks.push(self.model_display_look(body));
|
||||
}
|
||||
for (_, look) in faces {
|
||||
let look = Some(look);
|
||||
if !looks.contains(&look) {
|
||||
looks.push(look);
|
||||
}
|
||||
}
|
||||
looks
|
||||
}
|
||||
|
||||
/// Shared definition appearance for a computed occurrence body.
|
||||
pub fn model_body_look(&self, body: &crate::ModelBody) -> Option<Look> {
|
||||
self.body_appearance_chain(body.definition_name(), self.model_body_component(body)).resolved().cloned()
|
||||
let name = body.definition_name();
|
||||
self.appearances
|
||||
.bodies
|
||||
.get(name)
|
||||
.cloned()
|
||||
.or_else(|| self.appearances.components.get(&self.model_body_component(body)).cloned())
|
||||
.or_else(|| self.materials.get(name).and_then(|material| self.material_look(material)))
|
||||
}
|
||||
/// Effective body opacity, including paint carried from an imported body.
|
||||
pub fn model_body_opacity(&self, body: &crate::ModelBody) -> f64 {
|
||||
@@ -352,7 +182,15 @@ impl Document {
|
||||
/// A body's look (without face appearances): its own, else its component's, else its
|
||||
/// material's.
|
||||
pub fn body_look(&self, name: &str, feature: u64) -> Option<Look> {
|
||||
self.body_appearance_chain(name, self.body_component(name, feature)).resolved().cloned()
|
||||
self.appearances
|
||||
.bodies
|
||||
.get(name)
|
||||
.cloned()
|
||||
.or_else(|| {
|
||||
let c = self.body_component(name, feature);
|
||||
(c != 0).then(|| self.appearances.components.get(&c).cloned()).flatten()
|
||||
})
|
||||
.or_else(|| self.materials.get(name).and_then(|m| self.material_look(m)))
|
||||
}
|
||||
|
||||
/// Face appearances on a body: (point on the face, look).
|
||||
@@ -363,46 +201,21 @@ impl Document {
|
||||
/// The faces of a body with appearances of their own: (B-rep face index, look). A later
|
||||
/// assignment to the same face wins.
|
||||
pub fn face_colors(&self, b: &crate::ModelBody) -> Vec<(usize, Look)> {
|
||||
let mut out = self.imported_face_colors(b);
|
||||
for (index, look) in self.authored_face_colors(b) {
|
||||
out.retain(|(old, _)| *old != index);
|
||||
out.push((index, look));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Imported paint alone, after applying body-appearance precedence.
|
||||
pub(crate) fn imported_face_colors(&self, b: &crate::ModelBody) -> Vec<(usize, Look)> {
|
||||
// An assigned body appearance replaces imported paint; design face appearances still win.
|
||||
// Face colours the body was imported with, under the design's own.
|
||||
let to8 = |x: f32| (x.clamp(0.0, 1.0) * 255.0).round() as u8;
|
||||
if self.appearances.bodies.contains_key(b.definition_name()) {
|
||||
Vec::new()
|
||||
} else {
|
||||
match b.body.paint() {
|
||||
Some(p) => p.faces.iter().map(|f| (f.face, Look::custom(f.color.map(to8), f.opacity as f64))).collect(),
|
||||
// Carried from an earlier version of the body: by face name (worked out only here:
|
||||
// the viewport asks every body for its face colours on every change).
|
||||
None => match &b.imported {
|
||||
Some(imp) => crate::naming::face_names(b)
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, n)| imp.faces.get(crate::naming::strip_piece(n)).map(|(c, o)| (i, Look::custom(c.map(to8), *o as f64))))
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Design-authored face overrides, without imported STEP paint. Linked designs carry
|
||||
/// these independently so an inherited body look does not hide their face overrides.
|
||||
pub fn authored_face_colors(&self, b: &crate::ModelBody) -> Vec<(usize, Look)> {
|
||||
let mut out = Vec::new();
|
||||
self.apply_face_overrides(b, &mut out);
|
||||
out
|
||||
}
|
||||
|
||||
fn apply_face_overrides(&self, b: &crate::ModelBody, out: &mut Vec<(usize, Look)>) {
|
||||
let mut out: Vec<(usize, Look)> = match b.body.paint() {
|
||||
Some(p) => p.faces.iter().map(|f| (f.face, Look::custom(f.color.map(to8), f.opacity as f64))).collect(),
|
||||
// Carried from an earlier version of the body: by face name (worked out only here:
|
||||
// the viewport asks every body for its face colours on every change).
|
||||
None => match &b.imported {
|
||||
Some(imp) => crate::naming::face_names(b)
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, n)| imp.faces.get(crate::naming::strip_piece(n)).map(|(c, o)| (i, Look::custom(c.map(to8), *o as f64))))
|
||||
.collect(),
|
||||
None => Vec::new(),
|
||||
},
|
||||
};
|
||||
for f in self.appearances.faces.iter().filter(|f| f.body == b.definition_name()) {
|
||||
let (p, look) = (crate::apply_point(&b.source_transform, f.point), f.look.clone());
|
||||
if f.strict_name {
|
||||
@@ -442,6 +255,7 @@ impl Document {
|
||||
out.push((i, look));
|
||||
}
|
||||
}
|
||||
out
|
||||
}
|
||||
}
|
||||
|
||||
@@ -525,209 +339,6 @@ mod tests {
|
||||
assert!(!mixed.differs_only_in_appearance(&before), "a material change is not appearance-only");
|
||||
}
|
||||
|
||||
fn imported_box() -> crate::ModelBody {
|
||||
let body = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap();
|
||||
let faces = body
|
||||
.faces(0.01)
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|face| solidcraft_kernel::FacePaint { face: face.index, color: [1., 0., 0.], opacity: 0.5 })
|
||||
.collect();
|
||||
crate::ModelBody::new("Imported".into(), body.with_paint(Some(solidcraft_kernel::Paint { opacity: 1., faces })), 1)
|
||||
}
|
||||
|
||||
fn check_imported_appearance_precedence(body: &crate::ModelBody) {
|
||||
let mut doc = Document::default();
|
||||
let face_count = body.body.faces(0.01).unwrap().len();
|
||||
let imported = doc.face_colors(body);
|
||||
assert_eq!(imported.len(), face_count);
|
||||
assert!(imported.iter().all(|(_, look)| *look == Look::custom([255, 0, 0], 0.5)));
|
||||
|
||||
let body_look = Look::custom([0, 0, 255], 0.8);
|
||||
doc.appearances.bodies.insert(body.definition_name().into(), body_look.clone());
|
||||
assert_eq!(doc.model_body_look(body), Some(body_look.clone()));
|
||||
assert_eq!(doc.model_body_opacity(body), body_look.opacity);
|
||||
assert!(doc.face_colors(body).is_empty(), "all faces must inherit the assigned body appearance");
|
||||
|
||||
let face_look = Look::custom([0, 255, 0], 1.);
|
||||
let name = crate::naming::face_names(body)[0].clone();
|
||||
doc.appearances.faces.push(FaceLook {
|
||||
body: body.definition_name().into(),
|
||||
point: Vec3::ZERO,
|
||||
look: face_look.clone(),
|
||||
name: Some(name),
|
||||
strict_name: false,
|
||||
certificate: None,
|
||||
});
|
||||
assert_eq!(doc.face_colors(body), vec![(0, face_look.clone())]);
|
||||
|
||||
doc.appearances.bodies.clear();
|
||||
let restored = doc.face_colors(body);
|
||||
assert_eq!(restored.len(), face_count);
|
||||
assert!(restored.iter().all(|(face, look)| *look == if *face == 0 { face_look.clone() } else { Look::custom([255, 0, 0], 0.5) }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linked_paint_retains_imported_faces_without_baking_authored_overrides() {
|
||||
let mut state = crate::ModelState::default();
|
||||
state.bodies.push(imported_box());
|
||||
let body = &state.bodies[0];
|
||||
let mut doc = Document::default();
|
||||
doc.appearances.faces.push(FaceLook {
|
||||
body: body.name.clone(),
|
||||
point: Vec3::ZERO,
|
||||
look: Look::custom([0, 255, 0], 1.0),
|
||||
name: Some(crate::naming::face_names(body)[0].clone()),
|
||||
strict_name: false,
|
||||
certificate: None,
|
||||
});
|
||||
let exported = doc.painted(&state);
|
||||
assert!(exported.bodies[0].body.paint().unwrap().faces.iter().any(|face| face.color == [0., 1., 0.]));
|
||||
let linked = doc.painted_for_link(&state);
|
||||
let faces = &linked.bodies[0].body.paint().unwrap().faces;
|
||||
assert_eq!(faces.len(), body.body.faces(0.01).unwrap().len());
|
||||
assert!(faces.iter().all(|face| face.color == [1., 0., 0.] && face.opacity == 0.5));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn body_appearance_overrides_imported_face_paint() {
|
||||
check_imported_appearance_precedence(&imported_box());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn body_appearance_overrides_carried_imported_face_paint() {
|
||||
let mut body = imported_box();
|
||||
body.imported = ImportedLook::of(&body);
|
||||
body.body = body.body.clone().with_paint(None);
|
||||
body.source_name = Some(body.name.clone());
|
||||
body.name = "Occurrence".into();
|
||||
check_imported_appearance_precedence(&body);
|
||||
}
|
||||
|
||||
fn chain_fixture() -> (Document, crate::ModelState) {
|
||||
let mut doc = Document::new("Appearance chain");
|
||||
doc.components.push(crate::Component { id: 1, name: "Part".into(), ..Default::default() });
|
||||
doc.body_components.insert("B".into(), 1);
|
||||
let mut state = crate::ModelState::default();
|
||||
state.bodies.push(crate::ModelBody::new("B".into(), solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap(), 1));
|
||||
(doc, state)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_chain_agrees_with_display_and_definition_identity() {
|
||||
let (mut doc, mut state) = chain_fixture();
|
||||
let body_look = library("ABS - Red").unwrap();
|
||||
let face_look = library("Steel - Satin PBR").unwrap();
|
||||
doc.appearances.bodies.insert("B".into(), body_look.clone());
|
||||
doc.appearances.components.insert(1, library("ABS - Blue").unwrap());
|
||||
doc.materials.insert("B".into(), "Brass".into());
|
||||
let body = &state.bodies[0];
|
||||
let spot = crate::naming::face_spots(body)[0].unwrap().point;
|
||||
let face_name = crate::naming::face_names(body)[0].clone();
|
||||
doc.appearances.faces.push(FaceLook {
|
||||
body: "B".into(),
|
||||
point: spot,
|
||||
look: face_look.clone(),
|
||||
name: Some(face_name),
|
||||
strict_name: false,
|
||||
certificate: None,
|
||||
});
|
||||
let chain = doc.appearance_chain(&state, AppearanceTarget::Face { body: "B", face: 0 }).unwrap();
|
||||
assert_eq!(chain.active(), Some("face"));
|
||||
assert_eq!(chain.resolved(), Some(&face_look));
|
||||
assert_eq!(doc.model_face_look(body, 0).as_ref(), chain.resolved());
|
||||
let chain = doc.appearance_chain(&state, AppearanceTarget::Body("B")).unwrap();
|
||||
assert_eq!(chain.active(), Some("body"));
|
||||
assert_eq!(doc.model_body_look(body).as_ref(), chain.resolved());
|
||||
assert_eq!(doc.body_look("B", 1).as_ref(), chain.resolved());
|
||||
assert_eq!(chain.component.as_ref().unwrap().name, "ABS - Blue");
|
||||
assert_eq!(chain.material.as_ref().unwrap().name, "Brass");
|
||||
// A computed placement resolves the definition's assignments, not its label.
|
||||
state.bodies[0].name = "Occurrence/B".into();
|
||||
state.bodies[0].source_name = Some("B".into());
|
||||
state.bodies[0].component = 1;
|
||||
let chain = doc.appearance_chain(&state, AppearanceTarget::Body("Occurrence/B")).unwrap();
|
||||
assert_eq!(chain.body, Some(body_look));
|
||||
assert_eq!(chain.material.as_ref().unwrap().name, "Brass");
|
||||
assert!(doc.appearance_chain(&state, AppearanceTarget::Face { body: "Occurrence/B", face: usize::MAX }).is_err());
|
||||
assert!(doc.appearance_chain(&state, AppearanceTarget::Body("missing")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_chain_component_material_is_common_or_absent_and_imports_stay_fallbacks() {
|
||||
let (mut doc, mut state) = chain_fixture();
|
||||
state.bodies[0].imported = Some(std::sync::Arc::new(ImportedLook { color: Some([0.2, 0.4, 0.6]), ..Default::default() }));
|
||||
let body = doc.appearance_chain(&state, AppearanceTarget::Body("B")).unwrap();
|
||||
assert_eq!(body.active(), None);
|
||||
assert!(doc.model_display_look(&state.bodies[0]).is_some());
|
||||
doc.materials.insert("B".into(), "Brass".into());
|
||||
assert_eq!(doc.appearance_chain(&state, AppearanceTarget::Component(1)).unwrap().active(), Some("material"));
|
||||
let mut second = state.bodies[0].clone();
|
||||
second.name = "C".into();
|
||||
doc.body_components.insert("C".into(), 1);
|
||||
state.bodies.push(second);
|
||||
assert_eq!(doc.appearance_chain(&state, AppearanceTarget::Component(1)).unwrap().active(), None);
|
||||
doc.materials.insert("C".into(), "Brass".into());
|
||||
assert_eq!(doc.appearance_chain(&state, AppearanceTarget::Component(1)).unwrap().active(), Some("material"));
|
||||
doc.materials.insert("C".into(), "Steel".into());
|
||||
assert_eq!(doc.appearance_chain(&state, AppearanceTarget::Component(1)).unwrap().material, None);
|
||||
doc.appearances.components.insert(1, library("ABS - Blue").unwrap());
|
||||
assert_eq!(doc.appearance_chain(&state, AppearanceTarget::Component(1)).unwrap().active(), Some("component"));
|
||||
state.bodies.clear();
|
||||
assert_eq!(doc.appearance_chain(&state, AppearanceTarget::Component(1)).unwrap().material, None);
|
||||
assert!(doc.appearance_chain(&state, AppearanceTarget::Component(0)).is_err());
|
||||
assert!(doc.appearance_chain(&state, AppearanceTarget::Component(99)).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_replacement_validates_values_and_design_budget_without_mutation() {
|
||||
let from = library("ABS - Red").unwrap();
|
||||
let to = library("ABS - Blue").unwrap();
|
||||
let mut appearances = Appearances::default();
|
||||
appearances.bodies.insert("B".into(), from.clone());
|
||||
for opacity in [f64::NAN, f64::INFINITY, f64::NEG_INFINITY, -1., 2.] {
|
||||
let mut invalid = to.clone();
|
||||
invalid.opacity = opacity;
|
||||
assert!(appearances.replacing(&from, &invalid, None).is_err());
|
||||
assert!(appearances.replacing(&invalid, &to, None).is_err());
|
||||
assert_eq!(appearances.bodies["B"], from);
|
||||
}
|
||||
let mut invalid = library("Steel - Satin PBR").unwrap();
|
||||
invalid.opacity = 0.5;
|
||||
assert!(appearances.replacing(&from, &invalid, None).is_err());
|
||||
let (changed, count) = appearances.replacing(&from, &to, None).unwrap().unwrap();
|
||||
assert_eq!(count, 1);
|
||||
assert_eq!(changed.bodies["B"], to);
|
||||
assert_eq!(appearances.bodies["B"], from);
|
||||
assert!(appearances.replacing(&from, &from, None).unwrap().is_none());
|
||||
for i in 0..100_000 {
|
||||
appearances.components.insert(i, from.clone());
|
||||
}
|
||||
assert!(appearances.replacing(&from, &to, None).is_err());
|
||||
let (mut doc, state) = chain_fixture();
|
||||
doc.appearances = appearances;
|
||||
assert!(doc.appearance_chain(&state, AppearanceTarget::Body("B")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn appearance_chain_of_ten_thousand_body_component_has_four_steps() {
|
||||
let (mut doc, mut state) = chain_fixture();
|
||||
let mut body = state.bodies[0].clone();
|
||||
body.source_name = Some("B".into());
|
||||
body.component = 1;
|
||||
state.bodies = (0..10_000)
|
||||
.map(|i| {
|
||||
let mut b = body.clone();
|
||||
b.name = format!("Placed {i}");
|
||||
b
|
||||
})
|
||||
.collect();
|
||||
doc.materials.insert("B".into(), "Brass".into());
|
||||
let chain = doc.appearance_chain(&state, AppearanceTarget::Component(1)).unwrap();
|
||||
assert_eq!(chain.steps().len(), 4);
|
||||
assert_eq!(chain.active(), Some("material"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn library_and_materials() {
|
||||
assert_eq!(library("abs - red").map(|l| l.color), Some([196, 40, 40]));
|
||||
|
||||
@@ -58,22 +58,6 @@ impl Document {
|
||||
|
||||
let occurrences: BTreeSet<u64> =
|
||||
self.occurrences.iter().filter(|o| inside.contains(&o.component) || inside.contains(&o.parent)).map(|o| o.id).collect();
|
||||
let joints = self.remove_occurrences(&occurrences);
|
||||
|
||||
let bodies: BTreeSet<String> = self.body_components.iter().filter(|(_, c)| inside.contains(*c)).map(|(name, _)| name.clone()).collect();
|
||||
self.body_components.retain(|_, c| !inside.contains(c));
|
||||
self.body_offsets.retain(|name, _| !bodies.contains(name));
|
||||
self.components.retain(|c| !inside.contains(&c.id));
|
||||
self.parts.retain(|c, _| !inside.contains(c));
|
||||
self.links.retain(|c, _| !inside.contains(c));
|
||||
self.remove_configuration_components(&components);
|
||||
Ok(RemovedTree { components, features, deleted, occurrences: occurrences.into_iter().collect(), joints: joints.into_iter().collect() })
|
||||
}
|
||||
|
||||
/// Remove these occurrences and every assembly record that refers to them: the joints with
|
||||
/// either side on one (with those joints' motion links and motion keys), the joint origins
|
||||
/// on them, their places in contact sets and their exploded moves. Returns the joints removed.
|
||||
pub fn remove_occurrences(&mut self, occurrences: &BTreeSet<u64>) -> BTreeSet<u64> {
|
||||
let joints: BTreeSet<u64> = self
|
||||
.assembly
|
||||
.joints
|
||||
@@ -95,6 +79,14 @@ impl Document {
|
||||
view.moves.retain(|(o, _)| !occurrences.contains(o));
|
||||
}
|
||||
self.occurrences.retain(|o| !occurrences.contains(&o.id));
|
||||
joints
|
||||
|
||||
let bodies: BTreeSet<String> = self.body_components.iter().filter(|(_, c)| inside.contains(*c)).map(|(name, _)| name.clone()).collect();
|
||||
self.body_components.retain(|_, c| !inside.contains(c));
|
||||
self.body_offsets.retain(|name, _| !bodies.contains(name));
|
||||
self.components.retain(|c| !inside.contains(&c.id));
|
||||
self.parts.retain(|c, _| !inside.contains(c));
|
||||
self.links.retain(|c, _| !inside.contains(c));
|
||||
self.remove_configuration_components(&components);
|
||||
Ok(RemovedTree { components, features, deleted, occurrences: occurrences.into_iter().collect(), joints: joints.into_iter().collect() })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -693,11 +693,6 @@ pub enum FeatureKind {
|
||||
height: Expr,
|
||||
#[serde(default)]
|
||||
operation: Operation,
|
||||
/// Up to two straight edges of the face it stands on (#6380), as a Single hole's
|
||||
/// references: the base centre sits at each one's distance from its edge, in the face's
|
||||
/// plane, and follows the edges when the part changes. `base` keeps where it was placed.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
references: Vec<HoleReference>,
|
||||
},
|
||||
Sphere {
|
||||
#[serde(with = "point_expr")]
|
||||
@@ -2617,42 +2612,13 @@ pub struct StandardPart {
|
||||
/// A component whose contents come from another design file (Insert Design): where that file
|
||||
/// is, and the Import feature in the component that holds its bodies (Update Links replaces
|
||||
/// them; Break Link keeps them and forgets the file).
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct DesignLink {
|
||||
/// The design file: relative to this design's folder once this design is saved, else as
|
||||
/// given (`/` separates folders).
|
||||
pub path: String,
|
||||
/// The Import feature holding the linked design's bodies.
|
||||
pub feature: u64,
|
||||
/// Last inherited values, by Import body slot. Differing assembly assignments are
|
||||
/// local overrides and survive a source update. Older links start with no baseline.
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub inherited: Vec<LinkedBodyProperties>,
|
||||
}
|
||||
|
||||
/// The values last inherited by a linked body, not another editable material library.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
|
||||
pub struct LinkedBodyProperties {
|
||||
pub material: String,
|
||||
/// SHA-256 of the inherited custom definition, for equality only. The complete
|
||||
/// engineering card stays in user_materials, not duplicated once per linked body.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub material_fingerprint: Option<String>,
|
||||
/// Explicit body/component look only: material fallback must remain a fallback.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub appearance: Option<crate::appearance::Look>,
|
||||
/// Last inherited authored face overrides. None marks links saved before face
|
||||
/// inheritance; Some(empty) records that the source has no authored face overrides.
|
||||
#[serde(default, skip_serializing_if = "Option::is_none")]
|
||||
pub faces: Option<Vec<crate::appearance::FaceLook>>,
|
||||
}
|
||||
|
||||
impl LinkedBodyProperties {
|
||||
pub fn fingerprint(material: &UserMaterial) -> Result<String> {
|
||||
use sha2::{Digest, Sha256};
|
||||
let bytes = serde_json::to_vec(material).map_err(|e| DocError::Invalid(e.to_string()))?;
|
||||
Ok(format!("{:x}", Sha256::digest(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
/// A name the design's counter hands out (`Body1`).
|
||||
@@ -3160,22 +3126,14 @@ impl Document {
|
||||
/// The model with each body carrying the colour, opacity and face colours it is shown with
|
||||
/// (for export).
|
||||
pub fn painted(&self, state: &crate::ModelState) -> crate::ModelState {
|
||||
self.painted_with_faces(state, true)
|
||||
}
|
||||
|
||||
/// Linked designs carry authored face overrides as editable properties. Their STEP
|
||||
/// geometry keeps imported paint, without baking a second copy of those overrides.
|
||||
pub fn painted_for_link(&self, state: &crate::ModelState) -> crate::ModelState {
|
||||
self.painted_with_faces(state, false)
|
||||
}
|
||||
|
||||
fn painted_with_faces(&self, state: &crate::ModelState, authored_faces: bool) -> crate::ModelState {
|
||||
let mut st = state.clone();
|
||||
for b in &mut st.bodies {
|
||||
let c = self.body_color(b);
|
||||
let face_colors = if authored_faces { self.face_colors(b) } else { self.imported_face_colors(b) };
|
||||
let faces: Vec<solidcraft_kernel::FacePaint> =
|
||||
face_colors.into_iter().map(|(face, l)| solidcraft_kernel::FacePaint { face, color: l.rgb(), opacity: l.opacity as f32 }).collect();
|
||||
let faces: Vec<solidcraft_kernel::FacePaint> = self
|
||||
.face_colors(b)
|
||||
.into_iter()
|
||||
.map(|(face, l)| solidcraft_kernel::FacePaint { face, color: l.rgb(), opacity: l.opacity as f32 })
|
||||
.collect();
|
||||
let opacity = self.model_body_opacity(b) as f32;
|
||||
let paint = solidcraft_kernel::Paint { opacity, faces };
|
||||
if c != b.body.color() || b.body.paint() != Some(&paint) {
|
||||
@@ -3547,14 +3505,11 @@ impl Document {
|
||||
let id = f.id;
|
||||
let insertion_group = self.marker.and_then(|marker| self.timeline_group_at_insertion(marker));
|
||||
match self.marker {
|
||||
// A marker right after the last feature moves on past a feature added there, or the
|
||||
// new feature would be added rolled back (#7741).
|
||||
Some(marker) => {
|
||||
let position = marker.min(self.features.len());
|
||||
self.features.insert(position, f);
|
||||
self.marker = Some(position + 1);
|
||||
Some(m) if m < self.features.len() => {
|
||||
self.features.insert(m, f);
|
||||
self.marker = Some(m + 1);
|
||||
}
|
||||
None => self.features.push(f),
|
||||
_ => self.features.push(f),
|
||||
}
|
||||
if let Some(group_id) = insertion_group {
|
||||
let positions: std::collections::BTreeMap<u64, usize> =
|
||||
|
||||