Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8e2b0cd60d | |||
| afd81380ab | |||
| 3a5195b0a0 | |||
| ee6b85605a | |||
| aa1fe28ddf | |||
| 5b0eb08473 | |||
| 9df8837d43 | |||
| 409ab1ed45 | |||
| 5e03fe2f4b | |||
| 37a586dd61 | |||
| a79f40a711 | |||
| 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,9 +1,14 @@
|
||||
# cargo-mutants (docs/mutation-testing.md). Mutants are judged by the black-box suites that will
|
||||
# outlive the kernel backend: the reference tests (tests/oracle, through the oracle_cases test).
|
||||
# The kernel's own unit tests are left out on purpose: a mutant only they catch is a behaviour the
|
||||
# outlive the kernel backend: the reference tests (tests/oracle, through the oracle_cases test)
|
||||
# and the pull-request sample of the robustness goldens (every 8th seed, as ci.yml). The
|
||||
# kernel's own unit tests are left out on purpose: a mutant only they catch is a behaviour the
|
||||
# backend-neutral suites would miss when the kernel is swapped.
|
||||
profile = "mutants"
|
||||
additional_cargo_test_args = [
|
||||
"--test", "oracle_cases", "--", "--include-ignored",
|
||||
"--test", "oracle_cases", "--lib", "--", "--include-ignored",
|
||||
"oracle_suite",
|
||||
"robustness_goldens::seed_00", "robustness_goldens::seed_08", "robustness_goldens::seed_16",
|
||||
"robustness_goldens::seed_24", "robustness_goldens::seed_32", "robustness_goldens::seed_40",
|
||||
"robustness_goldens::seed_48", "robustness_goldens::seed_56", "robustness_goldens::seed_64",
|
||||
"robustness_goldens::seed_72", "robustness_goldens::seed_80", "robustness_goldens::seed_88",
|
||||
]
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json
|
||||
reviews:
|
||||
auto_review:
|
||||
# Dependabot bumps are checked by CI; skip CodeRabbit reviews on them.
|
||||
ignore_usernames:
|
||||
- "dependabot[bot]"
|
||||
path_instructions:
|
||||
- path: "**"
|
||||
instructions: |
|
||||
Project rule: don't change anyone's existing defaults unless the change is purely additive and belongs in the application.
|
||||
Flag any change to something a user already relies on: a default setting or preference value, what a command or tool does when used the usual way, the UI's default layout or behaviour, a keyboard or mouse shortcut, or the result an existing saved design rebuilds to.
|
||||
Don't flag purely additive changes: a new command, a new setting whose default keeps today's behaviour, or a new option that appears only when someone chooses it. Don't flag bug fixes that only turn a crash or an error into the result the feature always promised.
|
||||
When you flag one, start the comment with "Changes a default:", say what a user sees before and after, whether a setting keeps the old behaviour, and whether the PR description's `Changes a default:` line says so.
|
||||
pre_merge_checks:
|
||||
custom_checks:
|
||||
- name: "Existing defaults unchanged"
|
||||
mode: "warning"
|
||||
instructions: |
|
||||
Pass if the pull request changes no default a user already relies on (default setting or preference values, what a command or tool does when used the usual way, the UI's default layout or behaviour, shortcuts, or the result an existing saved design rebuilds to), or if its changes are purely additive.
|
||||
Fail otherwise, naming each changed default and what a user sees before and after. Also fail if the description says `Changes a default: no` while the code changes one.
|
||||
@@ -12,6 +12,7 @@ not (
|
||||
(package(solidcraft-engine) & (
|
||||
test(=tests::hostile_params_never_panic)
|
||||
| test(/^tests::robustness::/)
|
||||
| test(/^tests::robustness_goldens::seed_/)
|
||||
| test(=pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen)
|
||||
| test(=pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs)
|
||||
| test(=cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall)
|
||||
@@ -43,12 +44,13 @@ threads-required = 12
|
||||
priority = 100
|
||||
|
||||
# The slowest tests start first, so a shard does not end with one of them running alone on a
|
||||
# core while the others sit idle. Medians from the CI test timings (the junit artifacts), slowest
|
||||
# first; a test over ~100 s belongs here.
|
||||
# core while the others sit idle (seed_81 alone takes about 20 minutes under CI load). Medians
|
||||
# from the CI test timings (the junit artifacts), slowest first; a test over ~100 s belongs here.
|
||||
[[profile.ci.overrides]]
|
||||
filter = """
|
||||
(package(solidcraft-engine) & (
|
||||
test(/^tests::robustness::/)
|
||||
test(/^tests::robustness_goldens::seed_(81|45|20|24|95|02|57|40|18|48|32|46|85|61)$/)
|
||||
| test(/^tests::robustness::/)
|
||||
| test(=tests::hostile_params_never_panic)
|
||||
| test(=cmd::clearance::tests::distances_are_exact_on_edges_and_curved_faces)
|
||||
| test(=cmd::clipboard::tests::a_pasted_coil_joins_the_pasted_body)
|
||||
@@ -90,13 +92,14 @@ threads-required = "num-cpus"
|
||||
priority = 100
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
filter = "package(solidcraft-kernel) & test(=tspline::tests::hostile_tsplines_never_panic)"
|
||||
filter = "(package(solidcraft-engine) & test(=tests::robustness_goldens::seed_81)) | (package(solidcraft-kernel) & test(=tspline::tests::hostile_tsplines_never_panic))"
|
||||
priority = 99
|
||||
|
||||
[[profile.local-ci.overrides]]
|
||||
filter = """
|
||||
(package(solidcraft-engine) & (
|
||||
test(/^tests::robustness::/)
|
||||
test(/^tests::robustness_goldens::seed_(81|45|20|24|95|02|57|40|18|48|32|46|85|61)$/)
|
||||
| test(/^tests::robustness::/)
|
||||
| test(=tests::hostile_params_never_panic)
|
||||
| test(=cmd::clearance::tests::distances_are_exact_on_edges_and_curved_faces)
|
||||
| test(=cmd::clipboard::tests::a_pasted_coil_joins_the_pasted_body)
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
<!-- Title: which part of the app changes and what a user now sees or can do, clear to someone who hasn't seen your chat, the issue or the code. -->
|
||||
|
||||
## Change
|
||||
|
||||
Start with what this is, in plain words someone new to the project would understand: what it does and who it's for. Don't assume anyone knows the name of your tool, project or feature. Then describe the problem and resulting behavior. Link the issue when applicable.
|
||||
|
||||
@@ -33,8 +33,6 @@
|
||||
# `ci` job still runs and reports the commit's real result (see the `ci` job): the merge queue
|
||||
# reads `ci` from the newest run of a commit, so a run without it would block a PR that passed.
|
||||
# Cross-compilation is opt-in through Run workflow; release builds remain in release.yml.
|
||||
# The release gate (release-gate.yml) calls this workflow (workflow_call) as a Run workflow with
|
||||
# cross-compilation on.
|
||||
# Every Linux job runs on Blacksmith's Ubuntu 24.04 runners (blacksmith-<N>vcpu-ubuntu-2404), sized
|
||||
# per job: 32 vCPU for the tests, 16 for Clippy, the GPU path tests, the archive build (nightly) and the merge queue's, 8 for compiles, doc and reference tests, 4 for the light checks, 2 for Select checks and ci.
|
||||
# The Windows shader job runs on Blacksmith's Windows Server 2025 runners (blacksmith-8vcpu-windows-2025);
|
||||
@@ -52,14 +50,10 @@ on:
|
||||
# only (other labels are skipped, see the top of this file).
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review, labeled]
|
||||
# The kernel-smlib branch (ADR 0002) runs no CI for now (owner, 2026-10-21). Drop this
|
||||
# line before the branch merges back into main.
|
||||
branches-ignore: [kernel-smlib]
|
||||
# The merge queue: each queued group runs the full suite before it lands on main.
|
||||
merge_group:
|
||||
push:
|
||||
# develop: the SMLib kernel's trunk until it merges into main (its PRs land there).
|
||||
branches: [main, develop]
|
||||
branches: [main]
|
||||
schedule:
|
||||
- cron: "17 6 * * *"
|
||||
workflow_dispatch:
|
||||
@@ -68,14 +62,6 @@ on:
|
||||
description: "Also check WebAssembly and RISC-V (before a release)"
|
||||
type: boolean
|
||||
default: false
|
||||
# The release gate (release-gate.yml): the event stays the caller's (workflow_dispatch), so a
|
||||
# call runs exactly what Run workflow runs.
|
||||
workflow_call:
|
||||
inputs:
|
||||
cross_compile:
|
||||
description: "Also check WebAssembly and RISC-V (before a release)"
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -131,6 +117,7 @@ jobs:
|
||||
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.
|
||||
gpu: ${{ steps.paths.outputs.gpu }}
|
||||
@@ -169,6 +156,7 @@ jobs:
|
||||
run: |
|
||||
native=false
|
||||
kernel=false
|
||||
goldens=false
|
||||
windows=false
|
||||
gpu_paths=false
|
||||
deps=false
|
||||
@@ -250,12 +238,12 @@ jobs:
|
||||
if [ "${tested:-0}" -gt 0 ]; then
|
||||
echo "The merge queue already tested $PUSH_SHA: repository checks only."
|
||||
elif tested=$(curl -fsS -H "Authorization: Bearer $GH_TOKEN" -H "Accept: application/vnd.github+json" \
|
||||
"https://api.github.com/repos/$REPO/actions/workflows/ci.yml/runs?event=push&branch=$GITHUB_REF_NAME&status=success&per_page=1" \
|
||||
"https://api.github.com/repos/$REPO/actions/workflows/ci.yml/runs?event=push&branch=main&status=success&per_page=1" \
|
||||
| grep -o '"head_sha": *"[0-9a-f]*"' | head -n 1 | grep -o '[0-9a-f]\{40\}') \
|
||||
&& [ -n "$tested" ] && git merge-base --is-ancestor "$tested" "$PUSH_SHA" \
|
||||
&& git diff --no-renames --name-only -z "$tested" "$PUSH_SHA" > "$RUNNER_TEMP/ci-paths" \
|
||||
&& select_packages "$RUNNER_TEMP/ci-paths" && [ "$full" != true ] && [ -z "$packages" ]; then
|
||||
# Nothing a build reads changed since the last push to this branch whose run passed (the
|
||||
# Nothing a build reads changed since the last push to main whose run passed (the
|
||||
# push before may never have run: a newer push cancels a waiting one).
|
||||
echo "$tested..$PUSH_SHA changes no code: repository checks only."
|
||||
else
|
||||
@@ -294,6 +282,13 @@ jobs:
|
||||
git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/ci-paths"
|
||||
while IFS= read -r -d '' path; do
|
||||
case "$path" in crates/kernel/*|vendor/truck-*) kernel=true ;; esac
|
||||
# What a pull request re-checks the goldens for: the kernel, truck and the goldens
|
||||
# themselves (a re-record is checked before it is queued). The goldens are frozen, so
|
||||
# on any other change they would only re-check unchanged results; the merge queue,
|
||||
# main and the nightly run every seed before anything lands.
|
||||
case "$path" in
|
||||
crates/kernel/*|vendor/truck-*|tests/robustness/*) goldens=true ;;
|
||||
esac
|
||||
done < "$RUNNER_TEMP/ci-paths"
|
||||
# Computed as for a merge group, then turned off below (the fast lane).
|
||||
windows_paths "$RUNNER_TEMP/ci-paths" "$BASE_SHA...$HEAD_SHA"
|
||||
@@ -305,6 +300,10 @@ jobs:
|
||||
# selected means no code changed.
|
||||
select_packages "$RUNNER_TEMP/ci-paths"
|
||||
if [ "$full" = true ] || [ -n "$packages" ]; then native=true; fi
|
||||
# The goldens are solidcraft-engine tests: its tests must be in the archive.
|
||||
if [ "$goldens" = true ] && [ "$full" != true ]; then
|
||||
case " $packages " in *" solidcraft-engine "*) ;; *) packages="$packages solidcraft-engine" ;; esac
|
||||
fi
|
||||
fi
|
||||
# The reference tests replay recipes through solidcraft-cli: needed when it, or anything
|
||||
# it depends on, is selected.
|
||||
@@ -319,12 +318,13 @@ jobs:
|
||||
doctests=$native
|
||||
if [ "$EVENT_NAME" = pull_request ]; then windows=false; oracle=false; doctests=false; fi
|
||||
# Until a maintainer adds approved-for-ci, a pull request gets a quick compile instead of
|
||||
# Clippy, the tests and the doc tests.
|
||||
# Clippy, the tests, the doc tests and the goldens.
|
||||
quick=false
|
||||
if [ "$EVENT_NAME" = pull_request ] && [ "$APPROVED" != true ]; then
|
||||
quick=$native
|
||||
native=false
|
||||
oracle=false
|
||||
goldens=false
|
||||
fi
|
||||
# A push that only merges main into the change (Merge branch 'main', Update branch) when
|
||||
# the change itself already passed: nothing new to test here, and main tests the merged
|
||||
@@ -337,6 +337,7 @@ jobs:
|
||||
if [ -n "$carried" ]; then
|
||||
echo "Only a merge of main into a change that passed in $carried: repository checks only."
|
||||
native=false
|
||||
goldens=false
|
||||
fi
|
||||
fi
|
||||
# The GPU path tests (their own job, about 56 runner-minutes) are for render changes only:
|
||||
@@ -362,6 +363,7 @@ jobs:
|
||||
echo "native=$native"
|
||||
echo "gpu=$gpu"
|
||||
echo "kernel=$kernel"
|
||||
echo "goldens=$goldens"
|
||||
echo "windows=$windows"
|
||||
echo "oracle=$oracle"
|
||||
echo "doctests=$doctests"
|
||||
@@ -387,7 +389,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: repo-checks
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Format
|
||||
run: cargo fmt --all -- --check
|
||||
- name: Asset attribution
|
||||
@@ -407,10 +409,6 @@ jobs:
|
||||
# New UI text must reach every complete catalog; report it here, before review (#8542).
|
||||
- name: Translations in the complete catalogs
|
||||
run: cargo xtask i18n --check
|
||||
- name: Model decisions use exact geometry, not meshes
|
||||
run: |
|
||||
cargo xtask mesh-decisions
|
||||
cargo test -p xtask mesh_decisions --locked
|
||||
- name: Agent router and skills cite real files
|
||||
run: |
|
||||
cargo xtask skills
|
||||
@@ -441,7 +439,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: quick-compile
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Cargo check
|
||||
run: cargo check --workspace --all-targets --locked
|
||||
|
||||
@@ -460,7 +458,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: native-clippy
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Clippy
|
||||
env:
|
||||
FULL: ${{ needs.changes.outputs.full }}
|
||||
@@ -503,7 +501,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: native-tests
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -574,7 +572,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: native-tests
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -585,8 +583,13 @@ jobs:
|
||||
- name: Run tests
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
GOLDENS: ${{ needs.changes.outputs.goldens }}
|
||||
FULL: ${{ needs.changes.outputs.full }}
|
||||
PACKAGES: ${{ needs.changes.outputs.packages }}
|
||||
# The robustness golden seeds a pull request skips: each takes one to twenty minutes under
|
||||
# CI load, together about four fifths of the goldens' time. Pushes to main, the merge
|
||||
# queue and the nightly run every seed.
|
||||
SLOW_SEEDS: 81|45|20|24|95|02|57|40|18|48|32|46|85|61|77|34|14
|
||||
run: |
|
||||
# Pull requests skip the heavy tests (default-filter in .config/nextest.toml); `default()`
|
||||
# below is that filter, so a pull request still runs only what it ran before.
|
||||
@@ -600,23 +603,29 @@ jobs:
|
||||
# The boolean property tests draw new random inputs every run; they run only in the
|
||||
# nightly property-tests job, so a known kernel bug cannot turn main red at random.
|
||||
base='not (package(solidcraft-kernel) & binary(boolean_props))'
|
||||
elif [ "$GOLDENS" = true ]; then
|
||||
# A pull request that can change a robustness golden (tests/robustness/goldens.json)
|
||||
# also checks every seed but the slow ones, in the same run so they share its cores.
|
||||
base="(default() & not (binary(action_matrix) & test(=action_matrix_sample))) | (package(solidcraft-engine) & test(/^tests::robustness_goldens::/) & not test(/^tests::robustness_goldens::seed_($SLOW_SEEDS)\$/))"
|
||||
fi
|
||||
junit=${CARGO_TARGET_DIR:-target}/nextest/ci/junit.xml
|
||||
rm -f "$junit"
|
||||
# Pull requests test the affected packages (scripts/ci/affected.py).
|
||||
scope=(--workspace)
|
||||
if [ "$FULL" != true ]; then scope=(); for p in $PACKAGES; do scope+=(-p "$p"); done; fi
|
||||
status=0
|
||||
# The GPU path tracer's tests run in their own job (GPU path tests): one at a time, they
|
||||
# took about 36 of this job's 45 minutes.
|
||||
# A failed test does not fail the step by itself: the tests known to fail today
|
||||
# (tests/KNOWN_FAILING_TESTS) may fail, any other fails it, and so does a listed test
|
||||
# that passes. nextest's exit code 100 is "tests failed"; any other is its own error
|
||||
# (a build error, a bad filter) and fails the step at once.
|
||||
status=0
|
||||
cargo nextest run "${scope[@]}" --cargo-profile ci --locked --profile ci --no-tests=pass \
|
||||
--ignore-default-filter -E "($base) & not (package(=solidcraft-gpu) & test(/^path::/))" || status=$?
|
||||
if [ "$status" -ne 0 ] && [ "$status" -ne 100 ]; then exit "$status"; fi
|
||||
cargo xtask known-failing --junit "$junit"
|
||||
--ignore-default-filter -E "($base) & not (package(=solidcraft-gpu) & test(/^path::/))" || status=1
|
||||
if [ "$status" != 0 ]; then
|
||||
# A failed golden gets the hint to re-record it (junit.xml lists the failures).
|
||||
if [ -f "$junit" ] && tr '\n' ' ' < "$junit" | grep -q '<testcase name="tests::robustness_goldens::[^"]*"[^>]*>[[:space:]]*<failure'; then
|
||||
echo "::error::A robustness golden changed. If the new result is intended, re-record it in this pull request and say why:"
|
||||
echo "::error::ROBUSTNESS_GOLDENS_BLESS=\"why it changed\" cargo test --profile ci -p solidcraft-engine --lib robustness_goldens:: (see docs/robustness.md)"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
- name: Compiler cache statistics
|
||||
if: always()
|
||||
run: sccache --show-stats
|
||||
@@ -690,7 +699,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: native-gpu
|
||||
save-if: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') && matrix.shard == 1 }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' && matrix.shard == 1 }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -705,14 +714,8 @@ jobs:
|
||||
# None of these is in the default filter (the heavy tests), so pull requests, the merge
|
||||
# queue, main and nightly all run every one of them, as before. `count` deals the tests
|
||||
# out in turn (27 each); `hash` split them 41/40/27 across three shards.
|
||||
# Graded against tests/KNOWN_FAILING_TESTS, as in Native Linux tests.
|
||||
junit=${CARGO_TARGET_DIR:-target}/nextest/ci/junit.xml
|
||||
rm -f "$junit"
|
||||
status=0
|
||||
cargo nextest run -p solidcraft-gpu --cargo-profile ci --locked --profile ci --no-tests=pass \
|
||||
--ignore-default-filter -E 'test(/^path::/)' --partition "count:$SHARD/4" || status=$?
|
||||
if [ "$status" -ne 0 ] && [ "$status" -ne 100 ]; then exit "$status"; fi
|
||||
cargo xtask known-failing --junit "$junit"
|
||||
--ignore-default-filter -E 'test(/^path::/)' --partition "count:$SHARD/4"
|
||||
- name: Compiler cache statistics
|
||||
if: always()
|
||||
run: sccache --show-stats
|
||||
@@ -846,7 +849,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: doc-tests
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -904,7 +907,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: oracle
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -943,7 +946,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: windows-fxc
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
# One test at a time: WARP's shader JIT crashes now and then (a null read in
|
||||
# d3d10warp.dll's JIT optimizer, about 1 run in 60) when several devices compile compute
|
||||
# pipelines at once, which the parallel tests do. Minidumps showed it in WARP itself, not
|
||||
@@ -967,7 +970,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: windows-startup
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Backend order and probing on Windows
|
||||
run: cargo test -p solidcraft --profile ci --locked -- startup::tests native_crash::tests native_frame::tests
|
||||
- name: Build the app
|
||||
@@ -1104,7 +1107,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: wasm
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- run: cargo xtask wasm
|
||||
|
||||
riscv64-gates:
|
||||
@@ -1126,7 +1129,7 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: riscv64
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Install cross-compilation dependencies
|
||||
run: |
|
||||
# Ubuntu's regular archive serves amd64; RISC-V packages live in ports.
|
||||
@@ -1142,7 +1145,7 @@ jobs:
|
||||
EOF
|
||||
sudo dpkg --add-architecture riscv64
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y gcc-riscv64-linux-gnu g++-riscv64-linux-gnu libasound2-dev:riscv64 libxkbcommon-dev:riscv64 \
|
||||
sudo apt-get install -y gcc-riscv64-linux-gnu libasound2-dev:riscv64 libxkbcommon-dev:riscv64 \
|
||||
libwayland-dev:riscv64 libx11-dev:riscv64 libxrandr-dev:riscv64 libxi-dev:riscv64 libgl1-mesa-dev:riscv64
|
||||
- name: Cargo check on RISC-V
|
||||
env:
|
||||
|
||||
@@ -1,104 +0,0 @@
|
||||
# The fillet rig as a regression gate: a pull request that touches fillet code (the paths below)
|
||||
# runs every case in tests/kernel-cases/fillets/ through one release build of
|
||||
# solidcraft-cli, and fails on a regression (a case that fails and
|
||||
# tests/kernel-cases/fillets/KNOWN_FAILING does not list) or on a listed case that now passes
|
||||
# (it is fixed: remove its line, so the list only shrinks). See tests/kernel-cases/README.md.
|
||||
#
|
||||
# GitHub filters a workflow by whole files, so the fillet parts of crates/doc/src/eval*.rs and
|
||||
# crates/engine/src/cmd/features.rs select it whenever those files change. Pushes to main and
|
||||
# develop that touch the same paths run it too and save the compiler cache (a Blacksmith sticky
|
||||
# disk) that pull requests then mount read-only.
|
||||
name: Fillet rig
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- "crates/kernel/src/blend*.rs"
|
||||
- "crates/kernel/src/fillet*.rs"
|
||||
- "crates/kernel/src/*corner*.rs"
|
||||
- "crates/kernel/src/continuity*.rs"
|
||||
- "crates/smlib/**"
|
||||
- "crates/doc/src/eval*.rs"
|
||||
- "crates/engine/src/cmd/features.rs"
|
||||
- "tests/kernel-cases/fillets/**"
|
||||
# The rig itself: the grading, the runner and this workflow.
|
||||
- "apps/solidcraft-cli/src/kernel_cases.rs"
|
||||
- "xtask/src/kernel_cases.rs"
|
||||
- ".github/workflows/fillet-rig.yml"
|
||||
push:
|
||||
branches: [main, develop]
|
||||
# The same paths as pull_request.
|
||||
paths:
|
||||
- "crates/kernel/src/blend*.rs"
|
||||
- "crates/kernel/src/fillet*.rs"
|
||||
- "crates/kernel/src/*corner*.rs"
|
||||
- "crates/kernel/src/continuity*.rs"
|
||||
- "crates/smlib/**"
|
||||
- "crates/doc/src/eval*.rs"
|
||||
- "crates/engine/src/cmd/features.rs"
|
||||
- "tests/kernel-cases/fillets/**"
|
||||
# The rig itself: the grading, the runner and this workflow.
|
||||
- "apps/solidcraft-cli/src/kernel_cases.rs"
|
||||
- "xtask/src/kernel_cases.rs"
|
||||
- ".github/workflows/fillet-rig.yml"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: fillet-rig-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
fillets:
|
||||
name: Fillet rig
|
||||
# Draft pull requests run no CI, as in ci.yml.
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_DIR: /home/runner/.cache/sccache
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Size parallel compiles to the job's memory
|
||||
run: scripts/ci/build-jobs.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
# Only main and develop commit the cache; pull requests and forks mount a clone and never
|
||||
# write back.
|
||||
- name: Mount the compiler cache
|
||||
uses: useblacksmith/stickydisk@3f6be1451e3cad893b778bc93495c80452a5da26 # v1.7.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
key: ${{ github.repository }}-sccache-fillet-rig
|
||||
path: /home/runner/.cache/sccache
|
||||
commit: ${{ (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') && 'on-change' || 'false' }}
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: fillet-rig
|
||||
save-if: ${{ github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
version: v0.16.0
|
||||
- name: Build the CLI (release)
|
||||
run: cargo build --release --locked -p solidcraft-cli
|
||||
- name: Run the fillet cases against the known-failing list
|
||||
run: >-
|
||||
cargo run -q --locked -p xtask -- kernel-cases --area fillets
|
||||
--cli target/release/solidcraft-cli --out "$RUNNER_TEMP/fillet-rig"
|
||||
--known-failing tests/kernel-cases/fillets/KNOWN_FAILING
|
||||
- name: Keep the results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: fillet-rig-results
|
||||
path: ${{ runner.temp }}/fillet-rig/results.json
|
||||
if-no-files-found: ignore
|
||||
@@ -21,8 +21,6 @@ concurrency:
|
||||
jobs:
|
||||
publish:
|
||||
name: Request the combined docs, web, and Flatpak deployment
|
||||
# A pre-release stays off the Flatpak feed: the feed's one branch is what `flatpak update` offers.
|
||||
if: github.event_name == 'workflow_dispatch' || !github.event.release.prerelease
|
||||
runs-on: ubuntu-24.04
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
|
||||
@@ -1,238 +0,0 @@
|
||||
# The kernel rig and the complexity ladder as a regression gate. A plan job maps a pull request's
|
||||
# changed files to the rig areas they can affect (scripts/ci/kernel_rig_plan.py): an area runs
|
||||
# when a file its tests/kernel-cases/<area>/PATHS lists changes, or one of its cases; every area
|
||||
# runs when the shared core in tests/kernel-cases/PATHS-ALL changes, and when the solidcore pin in
|
||||
# the root Cargo.toml and Cargo.lock moves (the plan reads their diff); the ladder runs whenever any
|
||||
# area does. One job then builds solidcraft-cli once (release), checks that every
|
||||
# registry command has a case or an exemption (tests/kernel-cases/EXEMPT), and hands the CLI to a
|
||||
# matrix of shards as an artifact, one per selected area plus the ladder, in parallel, so the
|
||||
# wall time is the build plus the slowest shard. See tests/kernel-cases/README.md.
|
||||
#
|
||||
# - An area fails on a regression (a case that fails and its tests/kernel-cases/<area>/KNOWN_FAILING
|
||||
# does not list) or on a listed case that passes (it is fixed: remove its line in the same pull
|
||||
# request, so the list only shrinks).
|
||||
# - The ladder fails on any case whose status or counts differ from
|
||||
# tests/kernel-cases/ladder/golden.json; a right change is re-recorded (`--record "why"`).
|
||||
#
|
||||
# The trigger paths are whole folders that hold every area's PATHS (a self-test,
|
||||
# scripts/ci/test_kernel_rig_plan.py, checks they do), so a pull request that touches none of
|
||||
# them (the UI, docs, packaging) starts no runner at all, and a new area needs no edit here: the
|
||||
# plan finds the areas from the folders. Pushes to main and develop on the same paths, merge groups
|
||||
# (once the queue is on), the release gate (release-gate.yml, workflow_call) and a run by hand run
|
||||
# every area; those pushes also commit the compiler cache (a Blacksmith sticky disk) that every
|
||||
# other run mounts read-only.
|
||||
name: Kernel rig
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, ready_for_review]
|
||||
paths:
|
||||
- "crates/kernel/**"
|
||||
- "crates/geom/**"
|
||||
- "crates/smlib/**"
|
||||
- "crates/step/**"
|
||||
- "crates/io/src/**"
|
||||
- "crates/doc/src/**"
|
||||
- "crates/engine/src/**"
|
||||
- "tests/kernel-cases/**"
|
||||
# The solidcore pin (the plan runs every area only when its lines change).
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
# The rig itself: the grading, the runner, the selection and this workflow.
|
||||
- "apps/solidcraft-cli/src/kernel_cases.rs"
|
||||
- "xtask/src/kernel_cases.rs"
|
||||
- "xtask/src/kernel_ladder.rs"
|
||||
- "scripts/ci/kernel_rig_plan.py"
|
||||
- "scripts/ci/test_kernel_rig_plan.py"
|
||||
- ".github/workflows/kernel-rig.yml"
|
||||
push:
|
||||
branches: [main, develop]
|
||||
# The same paths as pull_request.
|
||||
paths:
|
||||
- "crates/kernel/**"
|
||||
- "crates/geom/**"
|
||||
- "crates/smlib/**"
|
||||
- "crates/step/**"
|
||||
- "crates/io/src/**"
|
||||
- "crates/doc/src/**"
|
||||
- "crates/engine/src/**"
|
||||
- "tests/kernel-cases/**"
|
||||
# The solidcore pin (the plan runs every area only when its lines change).
|
||||
- "Cargo.toml"
|
||||
- "Cargo.lock"
|
||||
# The rig itself: the grading, the runner, the selection and this workflow.
|
||||
- "apps/solidcraft-cli/src/kernel_cases.rs"
|
||||
- "xtask/src/kernel_cases.rs"
|
||||
- "xtask/src/kernel_ladder.rs"
|
||||
- "scripts/ci/kernel_rig_plan.py"
|
||||
- "scripts/ci/test_kernel_rig_plan.py"
|
||||
- ".github/workflows/kernel-rig.yml"
|
||||
merge_group:
|
||||
workflow_call:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
# A new push to a pull request replaces its old run.
|
||||
group: kernel-rig-${{ github.event_name }}-${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
env:
|
||||
CARGO_TERM_COLOR: always
|
||||
|
||||
jobs:
|
||||
plan:
|
||||
name: Select the rig areas
|
||||
# Draft pull requests run no CI, as in ci.yml.
|
||||
if: github.event_name != 'pull_request' || !github.event.pull_request.draft
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
timeout-minutes: 5
|
||||
outputs:
|
||||
# The shard matrix (JSON list of areas and the ladder; [] when nothing is selected).
|
||||
shards: ${{ steps.plan.outputs.shards }}
|
||||
# Whether the build job, and with it the coverage check, runs.
|
||||
build: ${{ steps.plan.outputs.build }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
# A pull request's diff needs its base's history.
|
||||
fetch-depth: ${{ github.event_name == 'pull_request' && '0' || '1' }}
|
||||
- name: Selection self-test
|
||||
run: python3 -I scripts/ci/test_kernel_rig_plan.py
|
||||
- id: plan
|
||||
name: Map the changed files to areas
|
||||
env:
|
||||
EVENT_NAME: ${{ github.event_name }}
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
run: |
|
||||
# Pull requests run the areas their diff reaches; pushes, merge groups, the release
|
||||
# gate and a run by hand run every area.
|
||||
if [ "$EVENT_NAME" = pull_request ]; then
|
||||
git diff --no-renames --name-only -z "$BASE_SHA...$HEAD_SHA" > "$RUNNER_TEMP/rig-paths"
|
||||
git diff --no-renames "$BASE_SHA...$HEAD_SHA" -- Cargo.toml Cargo.lock > "$RUNNER_TEMP/rig-pin.diff"
|
||||
python3 -I scripts/ci/kernel_rig_plan.py --paths "$RUNNER_TEMP/rig-paths" --pin-diff "$RUNNER_TEMP/rig-pin.diff" \
|
||||
--github-output "$GITHUB_OUTPUT"
|
||||
else
|
||||
python3 -I scripts/ci/kernel_rig_plan.py --all --github-output "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
build:
|
||||
name: Build the CLI
|
||||
needs: plan
|
||||
if: needs.plan.outputs.build == 'true'
|
||||
runs-on: blacksmith-8vcpu-ubuntu-2404
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_DIR: /home/runner/.cache/sccache
|
||||
SCCACHE_CACHE_SIZE: 10G
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Size parallel compiles to the job's memory
|
||||
run: scripts/ci/build-jobs.sh
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
# Only pushes to main and develop commit the cache; pull requests, merge groups and forks
|
||||
# mount a clone and never write back.
|
||||
- name: Mount the compiler cache
|
||||
uses: useblacksmith/stickydisk@3f6be1451e3cad893b778bc93495c80452a5da26 # v1.7.2
|
||||
continue-on-error: true
|
||||
with:
|
||||
key: ${{ github.repository }}-sccache-kernel-rig
|
||||
path: /home/runner/.cache/sccache
|
||||
commit: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') && 'on-change' || 'false' }}
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: kernel-rig
|
||||
save-if: ${{ github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/develop') }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
version: v0.16.0
|
||||
- name: Build the CLI (release) and the rig's runner
|
||||
run: |
|
||||
cargo build --release --locked -p solidcraft-cli
|
||||
cargo build --locked -p xtask
|
||||
# A tar keeps the executable bits, which an artifact drops. xtask finds the repository from
|
||||
# the path it was built in (CARGO_MANIFEST_DIR), which is the same checkout path in every
|
||||
# job of this workflow.
|
||||
# Every registry command is used by a case or listed in tests/kernel-cases/EXEMPT.
|
||||
- name: Every command has a case
|
||||
run: target/debug/xtask kernel-cases --coverage --cli target/release/solidcraft-cli
|
||||
# A coverage-only run (the registry or the cases changed, no area selected) runs no shard.
|
||||
- name: Pack the binaries
|
||||
if: needs.plan.outputs.shards != '[]'
|
||||
run: tar -cf "$RUNNER_TEMP/kernel-rig-bin.tar" -C target release/solidcraft-cli debug/xtask
|
||||
- uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
if: needs.plan.outputs.shards != '[]'
|
||||
with:
|
||||
name: kernel-rig-bin
|
||||
path: ${{ runner.temp }}/kernel-rig-bin.tar
|
||||
retention-days: 1
|
||||
- name: Compiler cache statistics
|
||||
if: always()
|
||||
run: sccache --show-stats
|
||||
|
||||
shard:
|
||||
name: Kernel rig (${{ matrix.shard }})
|
||||
needs: [plan, build]
|
||||
if: needs.plan.outputs.shards != '[]'
|
||||
runs-on: blacksmith-4vcpu-ubuntu-2404
|
||||
timeout-minutes: 30
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
# The areas of tests/kernel-cases/ the plan selected, and the ladder. On the build box
|
||||
# (aarch64, 4 cases at a time) the slowest areas took about 1 minute (booleans, 212 s of
|
||||
# cases with one of 84 s; threads, 161 s) and the ladder, one case at a time, 92 s: no area
|
||||
# needs splitting.
|
||||
shard: ${{ fromJSON(needs.plan.outputs.shards) }}
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: kernel-rig-bin
|
||||
path: ${{ runner.temp }}
|
||||
- name: Unpack the binaries
|
||||
run: mkdir -p target && tar -xf "$RUNNER_TEMP/kernel-rig-bin.tar" -C target
|
||||
- name: Run the cases against the known-failing list (the ladder against its golden)
|
||||
env:
|
||||
SHARD: ${{ matrix.shard }}
|
||||
run: |
|
||||
if [ "$SHARD" = ladder ]; then
|
||||
set -- --ladder --gate
|
||||
else
|
||||
set -- --area "$SHARD" --known-failing "tests/kernel-cases/$SHARD/KNOWN_FAILING"
|
||||
fi
|
||||
target/debug/xtask kernel-cases "$@" --cli target/release/solidcraft-cli --out "$RUNNER_TEMP/kernel-rig"
|
||||
- name: Keep the results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: kernel-rig-results-${{ matrix.shard }}
|
||||
path: ${{ runner.temp }}/kernel-rig/**/results.json
|
||||
if-no-files-found: ignore
|
||||
|
||||
# One check for the whole gate, for branch protection and the merge queue.
|
||||
kernel-rig:
|
||||
name: kernel-rig
|
||||
needs: [plan, build, shard]
|
||||
if: always() && (github.event_name != 'pull_request' || !github.event.pull_request.draft)
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
steps:
|
||||
# A skipped build or shard counts as passed only when the plan selected none.
|
||||
- name: Every selected job passed
|
||||
env:
|
||||
PLAN: ${{ needs.plan.result }}
|
||||
WANT_BUILD: ${{ needs.plan.outputs.build }}
|
||||
WANT_SHARDS: ${{ needs.plan.outputs.shards }}
|
||||
BUILD: ${{ needs.build.result }}
|
||||
SHARDS: ${{ needs.shard.result }}
|
||||
run: |
|
||||
echo "plan=$PLAN build=$BUILD shards=$SHARDS selected=$WANT_SHARDS"
|
||||
test "$PLAN" = success
|
||||
if [ "$WANT_BUILD" = true ]; then test "$BUILD" = success; else test "$BUILD" = skipped; fi
|
||||
if [ "$WANT_SHARDS" != '[]' ]; then test "$SHARDS" = success; else test "$SHARDS" = skipped; fi
|
||||
@@ -1,6 +1,5 @@
|
||||
# The documentation site on GitHub Pages: the book (docs/book) at https://docs.solidcraft.io/
|
||||
# and the web app at /app/ (the latest release), /app-rc/ (the newest release candidate) and
|
||||
# /app-edge/ (this commit). Needs Settings → Pages → Source = GitHub Actions; skipped while the
|
||||
# and the bundled web app at /app/. Needs Settings → Pages → Source = GitHub Actions; skipped while the
|
||||
# repository is private.
|
||||
name: Pages
|
||||
|
||||
@@ -67,10 +66,7 @@ jobs:
|
||||
mkdir -p site
|
||||
cp -r target/book/. site/
|
||||
bash docs/book/seo.sh site # canonical links, robots.txt, sitemap.xml
|
||||
- name: Add the web app channels (stable and release candidate from their releases, edge from this build)
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: bash packaging/web-channels.sh site dist/web
|
||||
cp -r dist/web site/app
|
||||
- uses: actions/configure-pages@v5
|
||||
id: pages
|
||||
- name: Install Flatpak repository tools
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
# The release gate: everything, in full and in parallel, on one branch's head, before that branch
|
||||
# replaces main as a release (develop, rebased onto main). Run it by hand:
|
||||
# gh workflow run release-gate.yml --ref develop
|
||||
# It calls the workflows that own each check instead of copying their jobs:
|
||||
# - ci.yml as Run workflow with cross-compilation: every native suite in full (not scoped), the
|
||||
# reference tests, the doc tests, the GPU path tests, the action matrix (all 16 shards) and its
|
||||
# determinism check, the property tests, the repository checks (web-offline among them),
|
||||
# WebAssembly and RISC-V, Windows D3D12 FXC and Windows app startup;
|
||||
# - kernel-rig.yml: every rig area against its KNOWN_FAILING and the ladder against its golden;
|
||||
# - release.yml as a dry run (draft false, packages_only): the Linux, Flatpak, Snap, FreeBSD,
|
||||
# macOS, Windows and web packages with their smoke tests and package checks, unsigned (no
|
||||
# secrets are passed, so no signing secret or SignPath quota is used) and without the release
|
||||
# job (checksums, update signing, publishing).
|
||||
# The gate passes only when all three pass. Before main is replaced, tag the old main
|
||||
# (main-before-develop-<date>) and push the tag.
|
||||
name: Release gate
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: release-gate-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
uses: ./.github/workflows/ci.yml
|
||||
permissions:
|
||||
contents: read
|
||||
# Select checks reads this commit's other runs.
|
||||
actions: read
|
||||
with:
|
||||
cross_compile: true
|
||||
|
||||
kernel-rig:
|
||||
name: Kernel rig
|
||||
uses: ./.github/workflows/kernel-rig.yml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
packages:
|
||||
name: Packages
|
||||
uses: ./.github/workflows/release.yml
|
||||
# No secrets: the signing steps skip without them. GitHub checks a called workflow's job
|
||||
# permissions before the run, skipped jobs included, so the release job's contents: write must
|
||||
# be granted here; packages_only skips that job, and no step that runs writes to the
|
||||
# repository. actions: read is the Windows job's.
|
||||
permissions:
|
||||
contents: write
|
||||
actions: read
|
||||
with:
|
||||
draft: false
|
||||
packages_only: true
|
||||
|
||||
release-gate:
|
||||
name: release-gate
|
||||
needs: [ci, kernel-rig, packages]
|
||||
if: always()
|
||||
runs-on: blacksmith-2vcpu-ubuntu-2404
|
||||
steps:
|
||||
- name: Every part passed
|
||||
env:
|
||||
CI: ${{ needs.ci.result }}
|
||||
KERNEL_RIG: ${{ needs.kernel-rig.result }}
|
||||
PACKAGES: ${{ needs.packages.result }}
|
||||
run: |
|
||||
echo "ci=$CI kernel-rig=$KERNEL_RIG packages=$PACKAGES"
|
||||
test "$CI" = success
|
||||
test "$KERNEL_RIG" = success
|
||||
test "$PACKAGES" = success
|
||||
@@ -21,8 +21,6 @@
|
||||
# releases/latest/download/<name> links in the README keep working across releases.
|
||||
#
|
||||
# A dry run: dispatch with draft=false (the default). Only the release branch may create drafts.
|
||||
# The release gate (release-gate.yml) calls it as such a dry run, without secrets (unsigned) and
|
||||
# without the release job (packages_only).
|
||||
# A dry run from any branch also test-signs the Windows files when SIGNPATH_API_TOKEN exists:
|
||||
# gh workflow run release.yml --ref <branch> -f draft=false [-f signpath_signing_policy=test-signing]
|
||||
name: Release
|
||||
@@ -43,25 +41,6 @@ on:
|
||||
description: "SignPath signing policy for the Windows files (empty: the repository variable, else test-signing)"
|
||||
type: string
|
||||
default: ""
|
||||
# The release gate (release-gate.yml): a dry run (draft false) of every package and its checks.
|
||||
# The event stays the caller's (workflow_dispatch), so a call runs as a dispatched dry run does.
|
||||
# The gate passes no secrets, so nothing is signed (the signing steps skip without their
|
||||
# secrets, as on a fork), and packages_only skips the release job (checksums, update signing,
|
||||
# publishing).
|
||||
workflow_call:
|
||||
inputs:
|
||||
packages_only:
|
||||
description: "Build and check every package only: no release job"
|
||||
type: boolean
|
||||
default: false
|
||||
draft:
|
||||
description: "Create/update a draft (release branch only; off: build artifacts only)"
|
||||
type: boolean
|
||||
default: false
|
||||
signpath_signing_policy:
|
||||
description: "SignPath signing policy for the Windows files (empty: the repository variable, else test-signing)"
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -234,8 +213,6 @@ jobs:
|
||||
if: needs.version.outputs.build == 'true'
|
||||
# makepkg only repackages the tarball's prebuilt binaries, so both architectures build here.
|
||||
runs-on: ubuntu-24.04
|
||||
# A pre-release or nightly without an Arch package is still worth having; a stable release isn't.
|
||||
continue-on-error: ${{ contains(needs.version.outputs.version, '-') }}
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -252,29 +229,10 @@ jobs:
|
||||
# makepkg in an archlinux:base-devel container. The package is named after the tarball's
|
||||
# .desktop file (io.tangencylabs.solidcraft) and written as
|
||||
# dist/release/solidcraft-<version>-linux-<arch>.pkg.tar.zst.
|
||||
# An Arch version can't hold a hyphen, and the action reads the version from the tarball's name
|
||||
# (a hyphenated one comes out as 0.0.0): 0.40.0-rc.1 goes in as 0.40.0rc.1, which pacman sorts
|
||||
# before 0.40.0, and the package keeps the release's name.
|
||||
- id: pkgver
|
||||
name: Arch version
|
||||
run: |
|
||||
pkgver="${VERSION//-/}"
|
||||
echo "pkgver=$pkgver" >>"$GITHUB_OUTPUT"
|
||||
if [ "$pkgver" != "$VERSION" ]; then
|
||||
cp "dist/linux/solidcraft-$VERSION-linux-$ARCH.tar.gz" "dist/linux/solidcraft-$pkgver-linux-$ARCH.tar.gz"
|
||||
fi
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
- uses: storytold/craft-common-actions/actions/arch-package@fa5ef18780cf5e7d5d3b8de068de56cffc83cac5 # main
|
||||
with:
|
||||
tarball: dist/linux/solidcraft-${{ steps.pkgver.outputs.pkgver }}-linux-${{ matrix.arch }}.tar.gz
|
||||
tarball: dist/linux/solidcraft-${{ env.VERSION }}-linux-${{ matrix.arch }}.tar.gz
|
||||
arch: ${{ matrix.arch }}
|
||||
- name: The package under the release's version
|
||||
if: steps.pkgver.outputs.pkgver != env.VERSION
|
||||
env:
|
||||
ARCH: ${{ matrix.arch }}
|
||||
PKGVER: ${{ steps.pkgver.outputs.pkgver }}
|
||||
run: sudo mv "dist/release/solidcraft-$PKGVER-linux-$ARCH.pkg.tar.zst" "dist/release/solidcraft-$VERSION-linux-$ARCH.pkg.tar.zst"
|
||||
- name: Check the package
|
||||
env:
|
||||
PKG: dist/release/solidcraft-${{ env.VERSION }}-linux-${{ matrix.arch }}.pkg.tar.zst
|
||||
@@ -654,8 +612,7 @@ jobs:
|
||||
release:
|
||||
name: GitHub Release or dry-run artifacts
|
||||
needs: [version, linux, flatpak, arch, snap, freebsd, macos, windows, web]
|
||||
# Not for the release gate (packages_only): it builds and checks the packages, nothing more.
|
||||
if: needs.version.outputs.build == 'true' && !inputs.packages_only
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
@@ -10,8 +10,6 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
|
||||
- No GPL/LGPL/AGPL code, not even reading it (OpenCASCADE, FreeCAD, SolveSpace, CadQuery, LibreCAD, QCAD): `cargo xtask licences --check`.
|
||||
- Every asset is original or openly licensed with a row in ATTRIBUTION.md; read `assets/AGENTS.md` before icon work: `cargo xtask assets`, `cargo xtask icons`.
|
||||
- Never crash: no unwrap/expect/panic/unsafe in non-test code (unsafe allowlist in crash-safety), errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
|
||||
- Model decisions (what an operation does, which bodies it touches, which face or edge a point or name means, depths, nearness) use exact geometry; a mesh is for display, export, previews and mesh bodies (`xtask/mesh-decisions.txt` only shrinks): `cargo xtask mesh-decisions`.
|
||||
- New kernel cases go to solidcore: `cargo xtask lists`.
|
||||
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
|
||||
- Layering (geom, sketch/kernel/render, doc, io, engine, ui-egui, apps); only `crates/kernel` uses truck: `cargo xtask layers`.
|
||||
- One outer UI container, one camera path (`navigation::handle`), colours from theme tokens: `cargo xtask ui-containers`.
|
||||
@@ -24,7 +22,6 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
|
||||
- A changed default keeps the old behaviour behind a preference and fills the PR's `Changes a default:` line, and no second PR for an issue a lower-numbered one closes: `pr-checks.yml`.
|
||||
- A regression test goes in the area's existing test file, never a per-bug file (#3226).
|
||||
- Look at open PRs first, claim with a draft PR (lowest number wins), one topic per PR, never force-push `main`, no pushes just to catch up with `main`: `skills/solidcraft-contributing/SKILL.md`.
|
||||
- A PR's title and first sentence make sense to someone who never saw your chat or the issue: `skills/solidcraft-contributing/SKILL.md`.
|
||||
- Show observable changes with real screenshots or a recording, captured yourself.
|
||||
- Run `cargo xtask ci` before every commit, without switching branches while it runs. `cargo xtask skills` keeps this file and `skills/` honest.
|
||||
|
||||
|
||||
@@ -103,22 +103,6 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| tests/step-conformance/tiny_pin.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/torus_ring_nurbs.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/step-conformance/washer.step | step-conformance contributors | generated from formulas by https://github.com/dan335/step-conformance (corpus/generated, 96f30bb); import regression fixture | CC0-1.0 |
|
||||
| tests/kernel-cases/import/1721-closed-bspline-tube-face/input.step | step-conformance contributors | copy of tests/step-conformance/cylinder_nurbs.step (generated from formulas by https://github.com/dan335/step-conformance, corpus/generated, 96f30bb); kernel regression case for tangency-labs/solidcraft#1721 | CC0-1.0 |
|
||||
| tests/kernel-cases/import/2043-outer-bound-opposing-surface-sense/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#2043 | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/258-cone-point-vertex/input.step | step-conformance contributors | copy of tests/step-conformance/cone_point_vertex.step (generated from formulas by https://github.com/dan335/step-conformance, corpus/generated, 96f30bb); kernel regression case for tangency-labs/solidcraft#258 | CC0-1.0 |
|
||||
| tests/kernel-cases/import/258-cone-to-point/input.step | step-conformance contributors | copy of tests/step-conformance/cone_point.step (generated from formulas by https://github.com/dan335/step-conformance, corpus/generated, 96f30bb); kernel regression case for tangency-labs/solidcraft#258 | CC0-1.0 |
|
||||
| tests/kernel-cases/import/258-tiny-pin/input.step | step-conformance contributors | copy of tests/step-conformance/tiny_pin.step (generated from formulas by https://github.com/dan335/step-conformance, corpus/generated, 96f30bb); kernel regression case for tangency-labs/solidcraft#258 | CC0-1.0 |
|
||||
| tests/kernel-cases/import/3098-bspline-edges-over-tiny-knot-span/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#3098 | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/3208-extruded-ellipse-face-over-parameter-seam/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#3208 | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/3352-vertex-off-its-edge-curve/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#3352 | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/3730-void-oriented-shell-false/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#3730 | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/3880-cylinder-seam-edge/input.step | step-conformance contributors | copy of tests/step-conformance/cylinder_seam.step (generated from formulas by https://github.com/dan335/step-conformance, corpus/generated, 96f30bb); kernel regression case for tangency-labs/solidcraft#3880 | CC0-1.0 |
|
||||
| tests/kernel-cases/import/3880-nurbs-hemisphere-dome/input.step | step-conformance contributors | copy of tests/step-conformance/dome_nurbs.step (generated from formulas by https://github.com/dan335/step-conformance, corpus/generated, 96f30bb); kernel regression case for tangency-labs/solidcraft#3880 | CC0-1.0 |
|
||||
| tests/kernel-cases/import/3963-closed-spline-edge-vertex-past-start/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#3963 | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/4005-cylinder-written-as-near-zero-cone/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#4005 | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/4006-plane-written-as-cone-of-nearly-right-angle/input.step | SolidCraft contributors | synthetic STEP file written by hand from ISO 10303-21 for the kernel regression case of tangency-labs/solidcraft#4006 | MIT OR Apache-2.0 |
|
||||
| tests/ssi-corpus/cases/*.json.gz, tests/ssi-corpus/LICENSE-MIT, tests/ssi-corpus/LICENSE-APACHE | ssi-corpus contributors (jyviko) | 119 surface/surface intersection cases with exact ground truth from https://github.com/jyviko/ssi-corpus (cases/data, b04c8a6), gzip-compressed, unchanged; SSI scorer fixtures (tests/ssi-corpus/README.md) | MIT OR Apache-2.0 |
|
||||
| tests/ssi-corpus/cases/y23_*.json.gz | Yang, Jia and Yan (paper authors: published surfaces, figures and topology); ssi-corpus contributors (reconstructions and truth) | 25 cases built from Yang, Jia & Yan 2023, "Topology Guaranteed B-Spline Surface/Surface Intersection", ACM Transactions on Graphics 42(6), Article 211; each cites the paper in its `source` field | CC-BY 4.0 (paper); MIT OR Apache-2.0 (case files) |
|
||||
| tests/step-regressions/rod_doubled_edge.step | SolidCraft contributors | written by SolidCraft from the commands of #4598 (a bored rod moved 100 mm and exported); import regression fixture | MIT OR Apache-2.0 |
|
||||
| tests/oracle/17-loft/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/17-loft/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/23-bracket-multifeature/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/23-bracket-multifeature/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
@@ -245,18 +229,6 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/flat/plus.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/pan.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/perspective.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_appearance.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_assistant.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_design.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_general.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_graphics.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_layout.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_mouse3d.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_navigation.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_plugins.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_shortcuts.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_sketch.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/prefs_tools.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/redo.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/save.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
@@ -317,6 +289,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/model/circle_ttt.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/coil.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/colored_components.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/colored_components_off.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/combine.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/component.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
|
||||
@@ -782,23 +755,6 @@ Original SolidCraft drawings, Open for owner review; no runtime assignments.
|
||||
| docs/design/preferences/figures/view-shadow.light.svg | SolidCraft contributors | original SolidCraft artwork drawn for the Preferences redesign (tracker #9863), extracted from the design board as a standalone SVG | MIT OR Apache-2.0 |
|
||||
| `assets/locales/pending/sketch-offset-instances.json` | SolidCraft contributors | Original offset instance-count label and translations | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/flat/settings_profile.svg | SolidCraft contributors | single-colour application/navigation icon master (64 grid), original analytic head and shoulder geometry for settings profiles | MIT OR Apache-2.0 |
|
||||
| tests/kernel-cases/import/4561-convert-box-mesh-with-a-zero-area-triangle/box-degenerate.stl | SolidCraft contributors | synthetic ASCII STL written by hand: a box with one zero-area triangle, for the kernel regression case of tangency-labs/solidcraft#4561 | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/theme-blur-ui.json | SolidCraft contributors | Original blur controls, notices and translations | MIT OR Apache-2.0 |
|
||||
| crates/ui-egui/tests/fixtures/theme-blur.json | SolidCraft contributors | Original theme for blur option scenarios and manager review | MIT OR Apache-2.0 |
|
||||
| assets/locales/ka.json | David Machakhelidze (devius) | Original Georgian translation of SolidCraft's English catalog, written with Claude Code from the meaning of each English string; terminology and doubtful strings decided by a native Georgian speaker; no commercial CAD text used | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark.svg | SolidCraft contributors | static SolidCraft mark: the five faces and gradients of assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg (the final frame of assets/styles/web/loading-logo.svg), cropped to the mark's bounds; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-mono-black.svg | SolidCraft contributors | one-colour (black) version of assets/branding/logo/solidcraft-mark.svg, same paths; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-mono-white.svg | SolidCraft contributors | one-colour (white) version of assets/branding/logo/solidcraft-mark.svg, same paths; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-512.png | SolidCraft contributors | transparent 512 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-1024.png | SolidCraft contributors | transparent 1024 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-mark-2048.png | SolidCraft contributors | transparent 2048 px render of assets/branding/logo/solidcraft-mark.svg (cairosvg); original artwork | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-wordmark.svg | SolidCraft contributors | the name SolidCraft set like the loading logo's wordmark (bold, +0.02 em tracking, #15499e) in IBM Plex Sans Bold from assets/fonts/ibm-plex-sans (SIL OFL 1.1, IBM Corp.), outlined to paths; no font file is embedded | MIT OR Apache-2.0 |
|
||||
| assets/branding/logo/solidcraft-lockup-horizontal.svg | SolidCraft contributors | assets/branding/logo/solidcraft-mark.svg beside assets/branding/logo/solidcraft-wordmark.svg, in the proportions of assets/branding/web/source/og.png; original artwork | MIT OR Apache-2.0 |
|
||||
| assets/styles/ui/themes/onyx.json | SolidCraft contributors | original Onyx stone-grey palette, option overlays and semantic icon-role mapping; existing SolidCraft artwork is unchanged | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/ui-tour/window.png | SolidCraft contributors | screenshot of SolidCraft 0.29.0 itself (`solidcraft --sample`, default dark theme, software renderer, 1280x800) with original numbered callouts (digits set in DejaVu Sans Bold), captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| docs/book/src/media/ui-tour/navigate.mp4 | SolidCraft contributors | original screen recording of the actual SolidCraft 0.29.0 English desktop UI (`--sample`, default dark theme, software renderer) orbiting, panning and zooming, with the original drawn pointer of examples/demo/overlay.py and labels set in DejaVu Sans Bold, captured with examples/demo/doc_shots.py | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/sketch-tool-hints.json | carlymx | Original sketch tool hints and engine error translations | MIT OR Apache-2.0 |
|
||||
| assets/locales/pending/primitive-pick-field-tips.json | SolidCraft contributors | Original primitive pick-field tooltip translations | MIT OR Apache-2.0 |
|
||||
|
||||
## Code adapted from sibling projects
|
||||
|
||||
@@ -838,19 +794,10 @@ in THIRD-PARTY-LICENSES.txt; no images or artwork from that project are copied.
|
||||
The glTF test files are built by the tests themselves (crates/io/src/gltf_in_tests.rs,
|
||||
crates/engine/src/tests/gltf_import.rs); no third-party models are committed.
|
||||
|
||||
## Geometry kernel crates
|
||||
|
||||
| Crate | Used for | Licence |
|
||||
|---|---|---|
|
||||
| solidcore (https://github.com/tangency-labs/solidcore, pinned by rev in Cargo.toml) | the Rust port of NVIDIA's SMLib: the B-rep kernel behind crates/smlib | Apache-2.0 (its NOTICE credits NVIDIA's USD BRep) |
|
||||
|
||||
Linked as a crate; no code from it is copied into SolidCraft. Its licence text and NOTICE go
|
||||
into THIRD-PARTY-LICENSES.txt when `main` regenerates it.
|
||||
|
||||
## FreeCAD and Rhino import library
|
||||
|
||||
FreeCAD `.FCStd` and Rhino `.3dm` files are read with [cadmpeg](https://github.com/cadmpeg/cadmpeg)
|
||||
0.6.7 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
|
||||
0.6.3 (Apache-2.0), linked as the crates `cadmpeg-core`, `cadmpeg-ir`, `cadmpeg-registry`,
|
||||
`cadmpeg-container`, `cadmpeg-codec-freecad`, `cadmpeg-codec-rhino` and `cadmpeg-codec-step`
|
||||
in native and browser builds (`crates/io/src/native_cad.rs`). Only those open-format codecs are built: no
|
||||
default features, so cadmpeg's codecs for commercial CAD formats and its ShapeManager/ACIS
|
||||
@@ -862,13 +809,17 @@ copied into SolidCraft.
|
||||
| SolidCraft file | Original contribution | Licence |
|
||||
|---|---|---|
|
||||
| crates/ui-egui/src/viewport.rs (`nearest_quarter_turn`, `axis_view_orbit`) | N3MI-DG's [PR #101](https://github.com/bherbruck/solidcraft/pull/101): [minimal TOP/BOTTOM turns](https://github.com/bherbruck/solidcraft/commit/c28e780f52490784ef5886f094b9d3f6d10c3e3f) and [drag-to-orbit](https://github.com/bherbruck/solidcraft/commit/f30b30be6ecc4e2e6544a851243137c01a354934), adapted to the axis gizmo | MIT OR Apache-2.0 |
|
||||
| crates/smlib/src/core/offset.rs (`draft`: the tilted planes and `SmBrep::LocalOperation`, the tail of the capi's `draft_faces`) | ablasik's `draft` in `crates/smlib/src/ffi/rs_ops2.rs` on [ablasik/solidcraft `app/solidcore-backend-on-develop`](https://github.com/ablasik/solidcraft/blob/9c7e8e31565e1823f0591b58e2967351c32af13f/crates/smlib/src/ffi/rs_ops2.rs), adapted to `core::offset`'s errors and checks | MIT OR Apache-2.0 |
|
||||
|
||||
## Vendored third-party code
|
||||
|
||||
| Directory | Source | Licence | Changes |
|
||||
|---|---|---|---|
|
||||
| vendor/accesskit_macos | accesskit_macos 0.26.3, https://github.com/AccessKit/accesskit (The AccessKit contributors) | MIT OR Apache-2.0 | the subclassing adapter and its tests from 0.27.2: dropping the adapter keeps AppKit's key-value observers (vendor/README.md) |
|
||||
| vendor/truck-meshalgo | truck-meshalgo 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | meshing: bounded parameter searches, collapsed-boundary projection, subdivision and provisional fixed-grid previews (vendor/README.md) |
|
||||
| vendor/truck-stepio | truck-stepio 0.3.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | STEP writer: intersection-curve surfaces written at unique ids (vendor/README.md) |
|
||||
| vendor/truck-shapeops | truck-shapeops 0.4.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | robust meshing and nearest-parameter fallback in booleans; degenerate intersection-curve rejection; face and edge history of booleans; trace macro; corner choice at vertices several intersection curves end on; reuse of an operand's meshes (vendor/README.md) |
|
||||
| vendor/truck-geometry | truck-geometry 0.5.0, https://github.com/ricosjp/truck (RICOS Co. Ltd.) | Apache-2.0 | intersection curves divided from their neighbours' surface parameters; surface second derivatives (vendor/README.md) |
|
||||
| vendor/matext4cgmath | matext4cgmath 0.1.0, https://github.com/ricosjp/matext4cgmath (RICOS Co. Ltd.) | Apache-2.0 | root refinement stops after 100 Newton steps (vendor/README.md) |
|
||||
|
||||
## Vendored renderer compatibility fix
|
||||
|
||||
|
||||
@@ -88,8 +88,6 @@ The [architecture page](docs/architecture.md) has the layer table. The short ver
|
||||
|
||||
- `crates/geom`: vectors, planes, meshes, measures.
|
||||
- `crates/sketch`: our own constraint solver, plus profile finding.
|
||||
- `crates/dynamics`: multibody dynamics: rigid bodies, joint constraints, gravity and springs,
|
||||
integrated in time (RFC #9719). It depends only on `geom`.
|
||||
- `crates/kernel`: the B-rep kernel boundary. It wraps [truck](https://github.com/ricosjp/truck),
|
||||
and it's the only crate allowed to touch truck. Fillets, chamfers, shell/offset, Delete Face,
|
||||
Split Face, STEP/IGES read and write, and face provenance are our own code here. Patched
|
||||
@@ -163,23 +161,9 @@ the most complete description of the house style.
|
||||
| UI scenarios | `crates/ui-egui/tests/scenarios/*.json` | part of `cargo test` |
|
||||
| Reference tests | `cargo xtask oracle` | `tests/oracle/` (see below) |
|
||||
|
||||
The reference for what the kernel builds is the kernel-cases ladder (`tests/kernel-cases/ladder/`,
|
||||
`cargo xtask kernel-cases --ladder`, [docs/kernel-ladder.md](docs/kernel-ladder.md)).
|
||||
|
||||
**Kernel cases go to solidcore.** A case that checks a kernel operation (a boolean, a blend, a
|
||||
shell, a sweep, an import's geometry) is a new entry in solidcore's corpus,
|
||||
`crates/solidcore/tests/kernel_cases/<area>/` in
|
||||
[tangency-labs/solidcore](https://github.com/tangency-labs/solidcore), with an answer in closed
|
||||
form or exact. Draft it as a rig case here, export it with
|
||||
`cargo xtask kernel-cases --area <area> --export DIR`, add `DIR/<area>/<case>.json` and its
|
||||
`.bodies.json.gz` there, and delete the draft; or write the entry by hand as that folder's README
|
||||
describes. `tests/kernel-cases/` takes no new cases: its `CASES` list only shrinks
|
||||
(`cargo xtask lists`). App-level cases (what a command does, the UI, product rules) stay in
|
||||
SolidCraft, as tests in their area's existing test file or UI scenarios.
|
||||
|
||||
The naming corpus checks persistent names: every name a corpus design stores still finds a face or
|
||||
an edge, and two builds name its faces and edges alike
|
||||
(`cargo test --profile ci -p solidcraft-cli --test naming_corpus`).
|
||||
A geometry change that changes the robustness goldens re-records them in the same pull request:
|
||||
`ROBUSTNESS_GOLDENS_BLESS="why it changed" cargo test --profile ci -p solidcraft-engine --lib robustness_goldens::`
|
||||
(see [docs/robustness.md](docs/robustness.md)); CI runs every seed on pull requests that touch geometry.
|
||||
[docs/mutation-testing.md](docs/mutation-testing.md) shows how to find kernel behaviour these
|
||||
black-box suites miss.
|
||||
|
||||
@@ -238,7 +222,8 @@ Pull requests skip a few heavy tests (one to twelve minutes each), which run in
|
||||
Check that validation after merging. They are listed in
|
||||
`.config/nextest.toml` and the [testing page](docs/book/src/dev/testing.md):
|
||||
|
||||
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`,
|
||||
- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness` and
|
||||
the seeds of `tests::robustness_goldens` (`seed_00` … `seed_95`; pull requests that change the kernel, truck or the goldens run all but the slowest, `SLOW_SEEDS` in `ci.yml`),
|
||||
`pattern_suppress_tests::enclosure_arc_extinction_keeps_a_real_sharp_inner_lip_after_resize_and_reopen`,
|
||||
`pattern_suppress_tests::enclosure_datums_follow_wall_height_and_board_depth_with_material_proofs`,
|
||||
`cmd::plastic::tests::enclosure_snap_fit_follows_resized_front_wall`, the `atomic_commands` contract tests
|
||||
@@ -258,8 +243,7 @@ the surface itself). Expected values are either worked out in closed form
|
||||
(`tools/oracle-analytic/`) or computed by building the same recipe in FreeCAD, run as a separate
|
||||
program (`tools/freecad-oracle/`); each file says which. The generated
|
||||
[reference test report](docs/oracle.md) has the current case count and results. The reference-test
|
||||
CI job runs `cargo xtask oracle` during full validation, and on pull requests that reach
|
||||
`solidcraft-cli`. A case that
|
||||
CI job runs `cargo xtask oracle` during full validation; pull-request runs skip it. A case that
|
||||
fails because of a known bug names its issue in `expected.json` (`"known_failure": "#123"`);
|
||||
never change an expected value to match SolidCraft's output.
|
||||
|
||||
@@ -355,15 +339,6 @@ and a fresh CI run:
|
||||
PRs in groups by area, so PRs that touch the same code meet a conflict at most once.
|
||||
4. Reference the issue (`Fixes #12`). In the PR, say what you tested and how. For geometry work, include the measured numbers (and
|
||||
the expected ones).
|
||||
5. Write the PR title and first sentence for someone who hasn't seen your chat, the issue or the
|
||||
code. Maintainers pick what to review from the title, and it becomes the commit on `main`. Say
|
||||
which part of the app changes and what a user now sees or can do, and name things rather than
|
||||
leaning on "there", "it" or "this".
|
||||
- Unclear: `Send to Slicer: pick one print face per body there, and show print faces only while it is open`
|
||||
- Clear: `3D printing: choose the face each body rests on in the Send to Slicer window, not the face menu`
|
||||
|
||||
Use plain, literal English in PR text: one idea per sentence, active voice, and one word for one
|
||||
thing. This helps everyone, including readers whose first language isn't English.
|
||||
|
||||
Commenting `/claim` assigns you, adds the `in progress` label, and replies with its automatic cleanup
|
||||
deadline. It frees a claim with no linked PR after 72 hours, or a claim with a PR after 14 days
|
||||
@@ -474,16 +449,6 @@ the same area), first resolve the command registry and source conflicts, then ru
|
||||
The generator tests cover stable ordering and additions that leave existing lines
|
||||
unchanged. The initial switch to sorted rows is a one-time larger diff.
|
||||
|
||||
## Pictures in the user guide
|
||||
|
||||
The user guide's screenshots and short clips (`docs/book/src/media/`) are captured from the real
|
||||
app by `examples/demo/doc_shots.py`, one function per figure. Each run starts a fresh app with its
|
||||
own settings, so your preferences never show. When a change makes a figure out of date, run
|
||||
`python3 examples/demo/doc_shots.py <page or shot>` (it needs Xvfb, ffmpeg, Pillow and a release
|
||||
build) and commit the new file. A new figure is a new function there, an ATTRIBUTION row in
|
||||
`attribution/pending/`, and a `<video>` (clips) or image link on the page. Keep a still under
|
||||
about 250 KB and a clip under about 1 MB.
|
||||
|
||||
## Licence
|
||||
|
||||
By contributing you agree that your contribution is licensed as MIT OR Apache-2.0, like the rest
|
||||
|
||||
@@ -186,6 +186,15 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "approx"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arboard"
|
||||
version = "3.6.1"
|
||||
@@ -206,6 +215,12 @@ dependencies = [
|
||||
"x11rb",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "array-macro"
|
||||
version = "2.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "220a2c618ab466efe41d0eace94dfeff1c35e3aa47891bdb95e1c0fefffd3c99"
|
||||
|
||||
[[package]]
|
||||
name = "arrayref"
|
||||
version = "0.3.9"
|
||||
@@ -518,6 +533,15 @@ dependencies = [
|
||||
"piper",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "branches"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f11502672c5570f77f6bdf573332483f8475bab6a7fda00f1fae8ddb5a6245c0"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bumpalo"
|
||||
version = "3.20.3"
|
||||
@@ -564,9 +588,9 @@ checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04"
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-freecad"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6fe0ed1935ace2c203b87107a064818dec2bb2ec2f250c4ad121767f3da75d1b"
|
||||
checksum = "3c4a8fa61748cbb3ce80838b00a8cebfe553460583941f5aba6e2b97c16b4d37"
|
||||
dependencies = [
|
||||
"cadmpeg-container",
|
||||
"cadmpeg-core",
|
||||
@@ -580,9 +604,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-rhino"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd2b76bd1d9301c949fbd731fd22cff9d37d10d1f481c754b557c2187bd0171"
|
||||
checksum = "d7af41440d4c1d69e88e612de02cc3c9bad79d03429531e640ac924fef53c6bf"
|
||||
dependencies = [
|
||||
"cadmpeg-container",
|
||||
"cadmpeg-core",
|
||||
@@ -599,9 +623,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-codec-step"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f3cbce33ed681a28b62d8b01ae64cca2b01f11f31b9a3e5e2455b9ffde915712"
|
||||
checksum = "35e1b6d17cf26b31f59f2edab8ef5c29d2a33216c581fd913f012ad4869bd559"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cadmpeg-container",
|
||||
@@ -616,9 +640,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-container"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "03f0d1c22b875294c5fbf4a4738aec8f8c0a4ad035eb28ece404f1dec21f371a"
|
||||
checksum = "5ab7bcef48ac7480b4917820272d85cd2c9533e17904e15b0e4d22f19a6045bd"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"crc32fast",
|
||||
@@ -628,9 +652,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-core"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "795bdc6e2a10ecc0627138874c17e2948a3925cd22c6c6b0c2e4f6621d92f89c"
|
||||
checksum = "e86bb7a40521832504bb1f5953a1c706b2c52aad73bf6d84e1b35e5cb0beae3f"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"memchr",
|
||||
@@ -643,9 +667,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-ir"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5592036516aaf2574b159a86a3d1ae872dc245261de86e4c0ce6773fed00a249"
|
||||
checksum = "b570a1a8c579e407a9fd7bb10fa1900f8827f1d578b8fcb4ea2a056b5f323c72"
|
||||
dependencies = [
|
||||
"base64",
|
||||
"cadmpeg-core",
|
||||
@@ -658,9 +682,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "cadmpeg-registry"
|
||||
version = "0.6.7"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fc163233d5f664337267192848505854de6aeed4843181adf87f58baddcee9a0"
|
||||
checksum = "b2be23469a9bca0b684f1811118fffb581ebd32ded08e6cc8b0716a41f8c1377"
|
||||
dependencies = [
|
||||
"cadmpeg-codec-freecad",
|
||||
"cadmpeg-codec-rhino",
|
||||
@@ -765,6 +789,17 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cgmath"
|
||||
version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"num-traits",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chacha20"
|
||||
version = "0.10.2"
|
||||
@@ -787,6 +822,19 @@ dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "chrono"
|
||||
version = "0.4.45"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327"
|
||||
dependencies = [
|
||||
"iana-time-zone",
|
||||
"js-sys",
|
||||
"num-traits",
|
||||
"wasm-bindgen",
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cipher"
|
||||
version = "0.4.4"
|
||||
@@ -857,6 +905,12 @@ version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c"
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e"
|
||||
|
||||
[[package]]
|
||||
name = "core-foundation"
|
||||
version = "0.9.4"
|
||||
@@ -1034,6 +1088,19 @@ dependencies = [
|
||||
"zeroize",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "derive_more"
|
||||
version = "0.99.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"rustc_version",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
@@ -1180,7 +1247,7 @@ dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"emath",
|
||||
"epaint",
|
||||
"itertools",
|
||||
"itertools 0.15.0",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"profiling",
|
||||
@@ -1598,8 +1665,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1914,6 +1983,30 @@ dependencies = [
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone"
|
||||
version = "0.1.65"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470"
|
||||
dependencies = [
|
||||
"android_system_properties",
|
||||
"core-foundation-sys",
|
||||
"iana-time-zone-haiku",
|
||||
"js-sys",
|
||||
"log",
|
||||
"wasm-bindgen",
|
||||
"windows-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iana-time-zone-haiku"
|
||||
version = "0.1.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "icu_collections"
|
||||
version = "2.3.0"
|
||||
@@ -2073,6 +2166,15 @@ dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
|
||||
dependencies = [
|
||||
"either",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itertools"
|
||||
version = "0.15.0"
|
||||
@@ -2167,6 +2269,17 @@ dependencies = [
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "katexit"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ccfb0b7ce7938f84a5ecbdca5d0a991e46bc9d6d078934ad5e92c5270fe547db"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "keyboard-types"
|
||||
version = "0.8.3"
|
||||
@@ -2317,6 +2430,15 @@ version = "0.4.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6"
|
||||
|
||||
[[package]]
|
||||
name = "matext4cgmath"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cgmath",
|
||||
"katexit",
|
||||
"num-complex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.8.3"
|
||||
@@ -3192,6 +3314,30 @@ dependencies = [
|
||||
"toml_edit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 1.0.109",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.107"
|
||||
@@ -3364,6 +3510,15 @@ dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rclite"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e09665c494a9de2bd230e1aedc0c2fb20481a51f28f7db84d0c9bcfe2fe537b2"
|
||||
dependencies = [
|
||||
"branches",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "read-fonts"
|
||||
version = "0.41.0"
|
||||
@@ -4051,27 +4206,9 @@ dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcore"
|
||||
version = "1.0.1"
|
||||
source = "git+https://github.com/tangency-labs/solidcore?rev=8ddce901671f1cebd9214a31d21895d725e886c8#8ddce901671f1cebd9214a31d21895d725e886c8"
|
||||
dependencies = [
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcore-record"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/tangency-labs/solidcore?rev=f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f#f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"eframe",
|
||||
@@ -4102,32 +4239,29 @@ dependencies = [
|
||||
"ureq",
|
||||
"windows-sys 0.61.2",
|
||||
"winit",
|
||||
"winreg",
|
||||
"x11rb",
|
||||
"zbus 5.19.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-3dx-client"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-cli"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-mcp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-derive"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4136,37 +4270,24 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-doc"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-derive",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-render",
|
||||
"solidcraft-sketch",
|
||||
"thiserror 2.0.21",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-dynamics"
|
||||
version = "0.40.0-rc.2"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-engine"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4174,12 +4295,10 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_path_to_error",
|
||||
"solidcore-record",
|
||||
"solidcraft-doc",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-render",
|
||||
"solidcraft-sketch",
|
||||
"thiserror 2.0.21",
|
||||
@@ -4189,17 +4308,16 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-geom"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"thiserror 2.0.21",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-gpu"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4208,7 +4326,6 @@ dependencies = [
|
||||
"robust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
@@ -4220,7 +4337,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-io"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"cadmpeg-ir",
|
||||
@@ -4235,11 +4352,9 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-doc",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-testkit",
|
||||
"thiserror 2.0.21",
|
||||
"zip",
|
||||
@@ -4247,26 +4362,27 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-kernel"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"proptest",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-smlib",
|
||||
"solidcraft-step",
|
||||
"spade",
|
||||
"thiserror 2.0.21",
|
||||
"truck-meshalgo",
|
||||
"truck-modeling",
|
||||
"truck-shapeops",
|
||||
"truck-stepio",
|
||||
"truck-topology",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-mcp"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"serde_json",
|
||||
@@ -4274,27 +4390,16 @@ dependencies = [
|
||||
"solidcraft-gpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-profile"
|
||||
version = "0.40.0-rc.2"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-render"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"image",
|
||||
"moxcms",
|
||||
"png",
|
||||
"robust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"web-time",
|
||||
"weezl",
|
||||
@@ -4302,54 +4407,31 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-sketch"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"harfrust",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"thiserror 2.0.21",
|
||||
"ttf-parser",
|
||||
"unicode-bidi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-smlib"
|
||||
version = "0.40.0-rc.2"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"log",
|
||||
"serde_json",
|
||||
"solidcore",
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-space-mouse"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-step"
|
||||
version = "0.40.0-rc.2"
|
||||
dependencies = [
|
||||
"solidcore-record",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-testkit"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-egui"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"egui",
|
||||
@@ -4364,7 +4446,6 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"solidcore-record",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-space-mouse",
|
||||
@@ -4381,7 +4462,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-web"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -4442,6 +4523,16 @@ version = "2.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "1.0.109"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.119"
|
||||
@@ -4691,6 +4782,144 @@ dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-base"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c279de9e92e5dc20a188deb0bb9a4bd421a6a185e57e03e19025e84a36c5a05"
|
||||
dependencies = [
|
||||
"cgmath",
|
||||
"matext4cgmath",
|
||||
"rustc-hash 2.1.3",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-derivers"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "421ff1c5a303aed64e295d9b30e8424ee7a2fa251617a59ae4acfd078496009f"
|
||||
dependencies = [
|
||||
"proc-macro-error",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.119",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-geometry"
|
||||
version = "0.5.0"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"truck-base",
|
||||
"truck-geotrait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-geotrait"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "97a31fe98d5a0589ad20576260e214af23dea21a8d125da81ad3bbcb666f411f"
|
||||
dependencies = [
|
||||
"getrandom 0.2.17",
|
||||
"rand 0.8.8",
|
||||
"thiserror 1.0.69",
|
||||
"truck-base",
|
||||
"truck-derivers",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-meshalgo"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"array-macro",
|
||||
"derive_more",
|
||||
"itertools 0.13.0",
|
||||
"rayon",
|
||||
"rustc-hash 2.1.3",
|
||||
"spade",
|
||||
"truck-base",
|
||||
"truck-geometry",
|
||||
"truck-modeling",
|
||||
"truck-polymesh",
|
||||
"truck-topology",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-modeling"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8082c2dfac8c2732a014d81807fb5d24dbac0d72ee4de2a705b7fbc05fa05b18"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"rustc-hash 2.1.3",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"truck-base",
|
||||
"truck-geometry",
|
||||
"truck-geotrait",
|
||||
"truck-polymesh",
|
||||
"truck-topology",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-polymesh"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8f22def310af4a89c37beb4839bbf702b302d2c112ea29553759f0e16622fbf"
|
||||
dependencies = [
|
||||
"array-macro",
|
||||
"bytemuck",
|
||||
"itertools 0.13.0",
|
||||
"rustc-hash 2.1.3",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"truck-base",
|
||||
"truck-geotrait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-shapeops"
|
||||
version = "0.4.0"
|
||||
dependencies = [
|
||||
"derive_more",
|
||||
"itertools 0.13.0",
|
||||
"rustc-hash 2.1.3",
|
||||
"truck-base",
|
||||
"truck-geometry",
|
||||
"truck-geotrait",
|
||||
"truck-meshalgo",
|
||||
"truck-topology",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-stepio"
|
||||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"truck-geometry",
|
||||
"truck-modeling",
|
||||
"truck-polymesh",
|
||||
"truck-topology",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "truck-topology"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bf62a1610405c5e39ab5eb6948f326fc1641689734a6b8207a80792133fe0e78"
|
||||
dependencies = [
|
||||
"parking_lot",
|
||||
"rayon",
|
||||
"rclite",
|
||||
"rustc-hash 2.1.3",
|
||||
"serde",
|
||||
"thiserror 1.0.69",
|
||||
"truck-base",
|
||||
"truck-geotrait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@@ -5788,11 +6017,9 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
dependencies = [
|
||||
"flate2",
|
||||
"proc-macro2",
|
||||
"quick-xml",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
|
||||
@@ -5,7 +5,7 @@ members = ["crates/*", "apps/*", "xtask"]
|
||||
exclude = ["vendor"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.40.0-rc.2"
|
||||
version = "0.30.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
@@ -16,13 +16,9 @@ repository = "https://github.com/bherbruck/solidcraft"
|
||||
robust = "1.2"
|
||||
# internal
|
||||
solidcraft-geom = { path = "crates/geom" }
|
||||
solidcraft-profile = { path = "crates/profile" }
|
||||
solidcraft-sketch = { path = "crates/sketch" }
|
||||
solidcraft-kernel = { path = "crates/kernel" }
|
||||
solidcraft-smlib = { path = "crates/smlib" }
|
||||
solidcraft-step = { path = "crates/step" }
|
||||
solidcraft-render = { path = "crates/render" }
|
||||
solidcraft-dynamics = { path = "crates/dynamics" }
|
||||
solidcraft-gpu = { path = "crates/gpu" }
|
||||
solidcraft-doc = { path = "crates/doc" }
|
||||
solidcraft-io = { path = "crates/io" }
|
||||
@@ -32,14 +28,6 @@ solidcraft-space-mouse = { path = "crates/space-mouse" }
|
||||
solidcraft-mcp = { path = "crates/mcp" }
|
||||
solidcraft-derive = { path = "crates/derive" }
|
||||
solidcraft-3dx-client = { path = "crates/threedx-client" }
|
||||
# The Rust port of NVIDIA's SMLib (Apache-2.0), the B-rep kernel behind crates/smlib. Pinned;
|
||||
# move the rev on its own commit. For a
|
||||
# local checkout, add `[patch."https://github.com/tangency-labs/solidcore"] solidcore = { path =
|
||||
# "../solidcore/crates/solidcore" }` to a `.cargo/config.toml` outside the repository.
|
||||
solidcore = { git = "https://github.com/tangency-labs/solidcore", rev = "8ddce901671f1cebd9214a31d21895d725e886c8" }
|
||||
# Geometry as data (solidcore's layers §1, "Record"): the B-rep record, its structural validation
|
||||
# and the native body file. Pinned like solidcore; for a local checkout, patch it as above.
|
||||
solidcore-record = { git = "https://github.com/tangency-labs/solidcore", rev = "f5e8e9a29e0c9b7cf6c4040a5bc8878382b3c62f" }
|
||||
# test tooling: a dev-dependency only (xtask/src/layers.rs)
|
||||
solidcraft-testkit = { path = "crates/testkit" }
|
||||
|
||||
@@ -59,7 +47,14 @@ sha2 = "0.10"
|
||||
moxcms = { version = "0.8.1", default-features = false, features = ["extended_range"] }
|
||||
semver = "1"
|
||||
|
||||
# constrained Delaunay + Ruppert refinement for trimmed faces (MIT OR Apache-2.0).
|
||||
# geometry kernel (Apache-2.0; see plan/adr/0001-geometry-kernel.md). Only crates/kernel uses it.
|
||||
truck-modeling = "0.6"
|
||||
truck-shapeops = { version = "0.4", default-features = false }
|
||||
truck-meshalgo = { version = "0.4", default-features = false, features = ["tessellation", "analyzers"] }
|
||||
truck-stepio = { version = "0.3", default-features = false }
|
||||
truck-topology = "0.6"
|
||||
# constrained Delaunay + Ruppert refinement for trimmed faces (MIT OR Apache-2.0); already in the
|
||||
# tree through truck-meshalgo.
|
||||
spade = { version = "2.15", default-features = false, features = ["std"] }
|
||||
rayon = "1.12"
|
||||
|
||||
@@ -82,9 +77,9 @@ gethostname = "1"
|
||||
# Pinned: its IR and codec APIs change between minor versions. Open formats only: no default
|
||||
# features, so cadmpeg's codecs for commercial formats (f3d, sldprt, inventor, catia, creo, nx)
|
||||
# and its ShapeManager/ACIS `sat` codec are not compiled in. `step` is the writer we restate through.
|
||||
cadmpeg-core = "=0.6.7"
|
||||
cadmpeg-ir = "=0.6.7"
|
||||
cadmpeg-registry = { version = "=0.6.7", default-features = false, features = ["fcstd", "rhino", "step"] }
|
||||
cadmpeg-core = "=0.6.3"
|
||||
cadmpeg-ir = "=0.6.3"
|
||||
cadmpeg-registry = { version = "=0.6.3", default-features = false, features = ["fcstd", "rhino", "step"] }
|
||||
|
||||
# fonts (sketch text outlines; MIT OR Apache-2.0, pure Rust)
|
||||
ttf-parser = { version = "0.25", default-features = false, features = ["std", "variable-fonts"] }
|
||||
@@ -132,7 +127,9 @@ opt-level = 2
|
||||
opt-level = 1
|
||||
|
||||
# Tests and lints (`cargo xtask ci`, GitHub CI): quick to build, quick enough to run. Light
|
||||
# optimisation, line tables only (file:line in backtraces), no LTO, no debug assertions.
|
||||
# optimisation, line tables only (file:line in backtraces), no LTO. Debug assertions are off because
|
||||
# truck's debug-only checks reject valid geometry (seam edges, closed edges); that was the only
|
||||
# reason the tests used to build with --release, which cost most of CI's time.
|
||||
[profile.ci]
|
||||
inherits = "dev"
|
||||
opt-level = 1
|
||||
@@ -155,9 +152,14 @@ codegen-units = 16
|
||||
# binary). See docs/crash-reports.md.
|
||||
debug = "line-tables-only"
|
||||
|
||||
|
||||
# Vendored crates with SolidCraft fixes (see vendor/README.md and ATTRIBUTION.md).
|
||||
# truck-shapeops with SolidCraft fixes (Apache-2.0, see vendor/truck-shapeops and ATTRIBUTION.md).
|
||||
[patch.crates-io]
|
||||
truck-geometry = { path = "vendor/truck-geometry" }
|
||||
truck-shapeops = { path = "vendor/truck-shapeops" }
|
||||
truck-stepio = { path = "vendor/truck-stepio" }
|
||||
truck-meshalgo = { path = "vendor/truck-meshalgo" }
|
||||
matext4cgmath = { path = "vendor/matext4cgmath" }
|
||||
|
||||
# Keep OpenGL pipelines, resolve textures and CAD callbacks at the same safe sample count.
|
||||
egui-wgpu = { path = "vendor/egui-wgpu" }
|
||||
|
||||
|
||||
@@ -20,15 +20,11 @@ name = "solvecraft-cli"
|
||||
path = "src/alias.rs"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu", "profile"]
|
||||
default = ["raytrace-cpu"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
# The profiler, compiled in so any build can say where its time goes (`--profile`,
|
||||
# SOLIDCRAFT_PROFILE=1); it records nothing until asked.
|
||||
profile = ["solidcraft-engine/profile"]
|
||||
|
||||
[dependencies]
|
||||
solidcore-record = { workspace = true }
|
||||
solidcraft-engine = { workspace = true }
|
||||
solidcraft-gpu = { workspace = true }
|
||||
solidcraft-mcp = { workspace = true }
|
||||
@@ -38,5 +34,3 @@ serde_json = { workspace = true }
|
||||
[dev-dependencies]
|
||||
# Snapshot tests decode the pictures to compare them with a tolerance.
|
||||
image = { workspace = true }
|
||||
# Tests measure meshes at their own tolerances.
|
||||
solidcraft-kernel = { workspace = true }
|
||||
|
||||
@@ -184,7 +184,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
s.execute(id, &c["params"]).map_err(|e| format!("command {i} ({id}): {e}"))?;
|
||||
run.push(t.elapsed().as_secs_f64() * 1000.0);
|
||||
let t = std::time::Instant::now();
|
||||
s.world_state().map_err(|e| e.to_string())?.warm_meshes(|| true);
|
||||
s.world_state().warm_meshes(|| true);
|
||||
shown.push(t.elapsed().as_secs_f64() * 1000.0);
|
||||
if run.len() == 100 || i + 1 == commands.len() {
|
||||
let mean = |v: &[f64]| v.iter().sum::<f64>() / v.len().max(1) as f64;
|
||||
@@ -198,7 +198,7 @@ pub fn run(args: &[String]) -> Result<(), String> {
|
||||
shown.clear();
|
||||
}
|
||||
}
|
||||
let world = s.world_state().map_err(|e| e.to_string())?;
|
||||
let world = s.world_state();
|
||||
println!(
|
||||
"{}",
|
||||
json!({"step": "built", "ms": began.elapsed().as_secs_f64() * 1000.0, "commands": commands.len(), "features": s.doc.features.len(), "bodies": world.bodies.len()})
|
||||
@@ -236,7 +236,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
assert!(s.model.results.iter().all(|r| r.error.is_none()));
|
||||
let world = s.world_state().unwrap();
|
||||
let world = s.world_state();
|
||||
assert!(world.bodies.len() > 40, "{} bodies", world.bodies.len());
|
||||
assert!(world.bodies.iter().any(|b| b.name.contains("spoke")));
|
||||
}
|
||||
|
||||
@@ -40,6 +40,8 @@ pub fn one(args: &[String]) -> Result<(), String> {
|
||||
"steps": run.steps.iter().map(|s| s.kind).collect::<Vec<_>>(),
|
||||
"unsupported": run.unsupported,
|
||||
"rejected": run.rejected,
|
||||
"boolean_fallbacks": run.boolean_fallbacks,
|
||||
"boolean_reports_truncated": run.boolean_reports_truncated,
|
||||
"panics": PANICS.swap(0, std::sync::atomic::Ordering::SeqCst),
|
||||
});
|
||||
if run.outcome.is_bug() {
|
||||
@@ -189,9 +191,23 @@ fn summarise(results: &[Value], took: Duration) -> String {
|
||||
let mut boolean: BTreeMap<String, usize> = BTreeMap::new();
|
||||
let mut at_step: BTreeMap<String, usize> = BTreeMap::new();
|
||||
let mut unsupported: BTreeMap<String, usize> = BTreeMap::new();
|
||||
let mut fallbacks: BTreeMap<String, [u64; 2]> = BTreeMap::new();
|
||||
let mut truncated = [0usize; 2];
|
||||
let mut steps = 0;
|
||||
let (mut dp, mut bp) = (0, 0);
|
||||
for v in results {
|
||||
for (i, group) in ["design", "boolean"].into_iter().enumerate() {
|
||||
if v[group]["boolean_reports_truncated"].as_bool().unwrap_or(false) {
|
||||
truncated[i] += 1;
|
||||
}
|
||||
if let Some(counts) = v[group]["boolean_fallbacks"].as_object() {
|
||||
for (strategy, count) in counts {
|
||||
if let Some(total) = fallbacks.entry(strategy.clone()).or_default().get_mut(i) {
|
||||
*total = total.saturating_add(count.as_u64().unwrap_or(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
dp += usize::from(v["design"]["panics"].as_u64().unwrap_or(0) > 0);
|
||||
bp += usize::from(v["boolean_panics"].as_u64().unwrap_or(0) > 0);
|
||||
let k = if v.get("timeout").is_some() {
|
||||
@@ -230,6 +246,11 @@ fn summarise(results: &[Value], took: Duration) -> String {
|
||||
for (k, c) in &boolean {
|
||||
s += &format!("- {k}: {}\n", pct(*c));
|
||||
}
|
||||
s += "\nSuccessful Boolean retries (events, including nested operations):\n";
|
||||
for (strategy, [designs, checks]) in &fallbacks {
|
||||
s += &format!("- {strategy}: designs {designs}, boolean checks {checks}\n");
|
||||
}
|
||||
s += &format!("- Capped reports: designs {}, boolean checks {}\n", truncated[0], truncated[1]);
|
||||
s
|
||||
}
|
||||
|
||||
@@ -238,13 +259,15 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn report_counts_outcomes_and_timeouts() {
|
||||
fn report_counts_retry_events_separately_and_marks_incomplete_logs() {
|
||||
let results = [
|
||||
json!({"design":{"outcome":{"kind":"ok"}},"boolean":{"outcome":"ok"}}),
|
||||
json!({"design":{"outcome":{"kind":"ok"}},"boolean":{"outcome":"ok"}}),
|
||||
json!({"design":{"outcome":{"kind":"ok"},"boolean_fallbacks":{"shifted_operands":2}},"boolean":{"outcome":"ok","boolean_fallbacks":{"shifted_operands":3},"boolean_reports_truncated":true}}),
|
||||
json!({"design":{"outcome":{"kind":"ok"},"boolean_fallbacks":{"shifted_operands":1}},"boolean":{"outcome":"ok"}}),
|
||||
json!({"timeout":90}),
|
||||
];
|
||||
let text = summarise(&results, Duration::ZERO);
|
||||
assert!(text.contains("shifted_operands: designs 3, boolean checks 3"), "{text}");
|
||||
assert!(text.contains("Capped reports: designs 0, boolean checks 1"), "{text}");
|
||||
assert!(text.contains("timeout: 1 (33.3 %)"), "{text}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,299 +0,0 @@
|
||||
//! `kernel-cases --export=DIR`: one kernel rig case as a corpus entry for solidcore's rig. The
|
||||
//! case's commands run as the rig runs them while the kernel's operations are traced
|
||||
//! (`smlib_trace`); the entry holds the operation (or the chain of operations) that made the
|
||||
//! bodies the case ends with, each operand that came in as a record given as that record (the
|
||||
//! B-rep record, solidcore-record's JSON), the fix set, the case's expected values and the rig's
|
||||
//! own result. A case whose result no kernel operation alone accounts for is written too, with
|
||||
//! the reasons, so nothing is dropped silently.
|
||||
//!
|
||||
//! An entry's operation is expressible (`operation_expressible`) when the run failed in a kernel
|
||||
//! operation (the last one traced failed), or when every body the case ends with is the record a
|
||||
//! traced operation's output was written as, or an operand some traced operation took, as its
|
||||
//! record. The entry is wholly expressible (`expressible`) when, besides, every value it grades is
|
||||
//! the kernel's: none of `continuity`, `watertight`, `files`, `results`, `construction` or
|
||||
//! `flat_pattern` (SolidCraft computes them), no face, edge or vertex count that the rig's merging
|
||||
//! across analytic surfaces (`merged_topology`) changes, and no refusal graded on SolidCraft's
|
||||
//! wording of the kernel's error. Those are listed in `ungraded`.
|
||||
|
||||
use std::collections::{BTreeMap, BTreeSet, HashMap};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::brep::Brep;
|
||||
use solidcraft_engine::kernel::smlib_trace::{self, Event};
|
||||
|
||||
use crate::read_json;
|
||||
|
||||
/// What a traced case leaves for its entry: the trace of its commands, and the bodies it ends
|
||||
/// with by name or the engine's error.
|
||||
#[derive(Default)]
|
||||
pub struct Capture {
|
||||
pub events: Vec<Event>,
|
||||
pub ran: Option<Result<Vec<(String, Brep)>, String>>,
|
||||
}
|
||||
|
||||
/// The expected values SolidCraft computes, never the kernel.
|
||||
const GRADER_ONLY: [&str; 6] = ["continuity", "watertight", "files", "results", "construction", "flat_pattern"];
|
||||
|
||||
/// The case directory's area (its path under `tests/kernel-cases/`, a ladder case's tier under
|
||||
/// `ladder/`) and its id.
|
||||
fn area_and_id(dir: &str) -> Result<(String, String), String> {
|
||||
let p = Path::new(dir);
|
||||
let parts: Vec<String> = p.components().map(|c| c.as_os_str().to_string_lossy().to_string()).collect();
|
||||
let at = parts.iter().rposition(|c| c == "kernel-cases").ok_or_else(|| format!("{dir}: not under tests/kernel-cases"))?;
|
||||
let rest = parts.get(at + 1..).unwrap_or_default();
|
||||
match rest.split_last() {
|
||||
Some((id, area)) if !area.is_empty() => Ok((area.join("/"), id.clone())),
|
||||
_ => Err(format!("{dir}: no area")),
|
||||
}
|
||||
}
|
||||
|
||||
/// The case's line in its area's `KNOWN_FAILING`: why it fails today; `None` when not listed.
|
||||
fn known_failing(dir: &str, id: &str) -> Option<String> {
|
||||
let list = Path::new(dir).parent()?.join("KNOWN_FAILING");
|
||||
let text = std::fs::read_to_string(list).ok()?;
|
||||
text.lines().filter(|l| !l.trim_start().starts_with('#')).find_map(|l| {
|
||||
let (case, why) = l.split_once(char::is_whitespace).unwrap_or((l, ""));
|
||||
(case == id).then(|| why.trim().to_string())
|
||||
})
|
||||
}
|
||||
|
||||
/// The trace read as operations and the records that went in and came out.
|
||||
struct Trace<'a> {
|
||||
/// Each operation: its name, operands, parameters and outputs or error.
|
||||
calls: Vec<(&'static str, &'a [u64], &'a Value, &'a Result<Vec<u64>, String>)>,
|
||||
/// The record each body made from a record was made from.
|
||||
imports: HashMap<u64, (&'a Brep, bool, bool)>,
|
||||
/// The records bodies were written as, in order.
|
||||
exports: Vec<(u64, &'a Brep)>,
|
||||
/// The operation that made each body.
|
||||
producer: HashMap<u64, usize>,
|
||||
}
|
||||
|
||||
impl<'a> Trace<'a> {
|
||||
fn of(events: &'a [Event]) -> Trace<'a> {
|
||||
let mut t = Trace { calls: Vec::new(), imports: HashMap::new(), exports: Vec::new(), producer: HashMap::new() };
|
||||
for e in events {
|
||||
match e {
|
||||
Event::Import { id, record, trusted, keeping_splines } => {
|
||||
t.imports.insert(*id, (record, *trusted, *keeping_splines));
|
||||
}
|
||||
Event::Export { id, record } => t.exports.push((*id, record)),
|
||||
Event::Call { op, inputs, params, outputs } => {
|
||||
if let Ok(made) = outputs {
|
||||
for id in made {
|
||||
t.producer.insert(*id, t.calls.len());
|
||||
}
|
||||
}
|
||||
t.calls.push((*op, inputs, params, outputs));
|
||||
}
|
||||
}
|
||||
}
|
||||
t
|
||||
}
|
||||
|
||||
/// Bodies some operation took as an operand.
|
||||
fn operands(&self) -> BTreeSet<u64> {
|
||||
self.calls.iter().flat_map(|c| c.1.iter().copied()).collect()
|
||||
}
|
||||
}
|
||||
|
||||
/// The operations `ends` need, in the order they ran: each body's maker, and its operands' makers
|
||||
/// in turn, back to bodies made from records. Operands made by nothing traced are listed in
|
||||
/// `unknown`.
|
||||
fn chain(t: &Trace<'_>, ends: &[u64], unknown: &mut BTreeSet<u64>) -> Vec<usize> {
|
||||
let mut need = BTreeSet::new();
|
||||
let mut todo: Vec<u64> = ends.to_vec();
|
||||
while let Some(id) = todo.pop() {
|
||||
if t.imports.contains_key(&id) {
|
||||
continue;
|
||||
}
|
||||
match t.producer.get(&id) {
|
||||
Some(&i) => {
|
||||
if need.insert(i)
|
||||
&& let Some(c) = t.calls.get(i)
|
||||
{
|
||||
todo.extend(c.1.iter().copied());
|
||||
}
|
||||
}
|
||||
None => {
|
||||
unknown.insert(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
need.into_iter().collect()
|
||||
}
|
||||
|
||||
/// The entry of case `dir`, run with result `out`, into `root/<area>/<id>.json`, and the fix set
|
||||
/// into `root/fixes.json`.
|
||||
pub fn write(root: &Path, dir: &str, out: &Value, capture: Capture) -> Result<(), String> {
|
||||
let (area, id) = area_and_id(dir)?;
|
||||
let want = read_json(&format!("{dir}/expected.json"))?;
|
||||
let t = Trace::of(&capture.events);
|
||||
// Why the operation is not the kernel's alone, and what the entry cannot grade without
|
||||
// SolidCraft.
|
||||
let mut why_not: Vec<String> = Vec::new();
|
||||
let mut ungraded: Vec<String> = Vec::new();
|
||||
let has = |k: &str| want.get(k).is_some_and(|v| !v.is_null());
|
||||
|
||||
if let Some(missing) = want["missing"].as_str() {
|
||||
why_not.push(format!("SolidCraft has no command for it yet: {missing}"));
|
||||
}
|
||||
for k in GRADER_ONLY.iter().filter(|k| has(k)) {
|
||||
ungraded.push(format!("{k}: SolidCraft computes it"));
|
||||
}
|
||||
|
||||
let mut result: Vec<Value> = Vec::new();
|
||||
let mut ends: Vec<u64> = Vec::new();
|
||||
let mut failed_call: Option<usize> = None;
|
||||
let mut outcome = json!({"ran": true});
|
||||
match (&capture.ran, has("missing")) {
|
||||
(_, true) => {}
|
||||
(None, false) => why_not.push("the case did not run".into()),
|
||||
(Some(Err(e)), false) => {
|
||||
// The run stopped. It stopped in the kernel when the last operation that failed is
|
||||
// followed by no operation but copies (a copy or a tagged copy, which build nothing):
|
||||
// nothing was built after it, so the engine's error reports that failure in its own
|
||||
// words. An operation that ran after it means the engine went on and failed itself.
|
||||
outcome = json!({"ran": false, "engine_error": e});
|
||||
let last_failed = t.calls.iter().rposition(|c| c.3.is_err());
|
||||
let built_after = |i: usize| t.calls.iter().skip(i + 1).any(|c| !matches!(c.0, "copy" | "tagged"));
|
||||
match last_failed.filter(|&i| !built_after(i)).and_then(|i| t.calls.get(i).map(|c| (i, c))) {
|
||||
Some((i, c)) => {
|
||||
failed_call = Some(i);
|
||||
ends.extend(c.1.iter().copied());
|
||||
if let Err(k) = c.3 {
|
||||
outcome["kernel_error"] = json!(k);
|
||||
if let Some(why) = want["refusal"].as_str().filter(|w| !k.contains(*w)) {
|
||||
ungraded.push(format!("refusal {why:?}: SolidCraft's wording of the kernel's error"));
|
||||
}
|
||||
}
|
||||
}
|
||||
None if t.calls.is_empty() => why_not.push(format!("no kernel operation ran before the failure: {e}")),
|
||||
None => why_not.push(format!("the failure is SolidCraft's, not a kernel operation's: {e}")),
|
||||
}
|
||||
}
|
||||
(Some(Ok(world)), false) => {
|
||||
let operands = t.operands();
|
||||
let mut made = 0;
|
||||
for (name, record) in world {
|
||||
let from_op = t.exports.iter().rev().find(|(id, r)| *r == record && t.producer.contains_key(id)).map(|(id, _)| *id);
|
||||
let operand = t.imports.iter().filter(|(i, (r, _, _))| *r == record && operands.contains(i)).map(|(i, _)| *i).min();
|
||||
match (from_op, operand) {
|
||||
(Some(id), _) => {
|
||||
made += 1;
|
||||
ends.push(id);
|
||||
result.push(json!({"body": name, "made": format!("b{id}")}));
|
||||
}
|
||||
(None, Some(id)) => {
|
||||
ends.push(id);
|
||||
result.push(json!({"body": name, "operand": format!("b{id}")}));
|
||||
}
|
||||
(None, None) => why_not.push(format!(
|
||||
"body {name:?} is no kernel operation's output nor an operand: SolidCraft built it or changed the kernel's record"
|
||||
)),
|
||||
}
|
||||
}
|
||||
if world.is_empty() {
|
||||
why_not.push("the case ends with no body".into());
|
||||
} else if made == 0 && why_not.is_empty() {
|
||||
why_not.push("no kernel operation made a body the case ends with".into());
|
||||
}
|
||||
// Counts graded as SolidCraft merges them, where that differs from the records'.
|
||||
let raw = |k: &str| -> u64 {
|
||||
world
|
||||
.iter()
|
||||
.map(|(_, r)| match k {
|
||||
"faces" => r.faces.len(),
|
||||
"edges" => r.edges.len(),
|
||||
_ => r.vertices.len(),
|
||||
} as u64)
|
||||
.sum()
|
||||
};
|
||||
for k in ["faces", "edges", "vertices"] {
|
||||
if let Some(merged) = out["measured"][k].as_u64().filter(|&m| has(k) && m != raw(k)) {
|
||||
ungraded.push(format!("{k}: graded merged across analytic surfaces ({merged}), the records have {}", raw(k)));
|
||||
}
|
||||
}
|
||||
outcome["raw_counts"] = json!({"faces": raw("faces"), "edges": raw("edges"), "vertices": raw("vertices")});
|
||||
}
|
||||
}
|
||||
|
||||
let mut unknown = BTreeSet::new();
|
||||
let mut order = chain(&t, &ends, &mut unknown);
|
||||
if !unknown.is_empty() {
|
||||
why_not.push(format!("operands made by nothing traced: {unknown:?}"));
|
||||
}
|
||||
if let Some(i) = failed_call.filter(|i| !order.contains(i)) {
|
||||
order.push(i);
|
||||
}
|
||||
// Operand records: every body the chain takes, or the case ends with, that came in as a record.
|
||||
let mut bodies = BTreeMap::new();
|
||||
let given = |id: u64, bodies: &mut BTreeMap<String, Value>| {
|
||||
if let Some((record, trusted, splines)) = t.imports.get(&id) {
|
||||
bodies.insert(format!("b{id}"), json!({"trusted": trusted, "keeping_splines": splines, "record": record}));
|
||||
}
|
||||
};
|
||||
let mut calls: Vec<Value> = Vec::new();
|
||||
for (op, inputs, params, outputs) in order.iter().filter_map(|&i| t.calls.get(i)) {
|
||||
for &id in inputs.iter() {
|
||||
given(id, &mut bodies);
|
||||
}
|
||||
let mut c = json!({"op": op, "inputs": inputs.iter().map(|i| format!("b{i}")).collect::<Vec<_>>(), "params": params});
|
||||
match outputs {
|
||||
Ok(made) => c["outputs"] = json!(made.iter().map(|i| format!("b{i}")).collect::<Vec<_>>()),
|
||||
Err(e) => c["error"] = json!(e),
|
||||
}
|
||||
calls.push(c);
|
||||
}
|
||||
for r in &result {
|
||||
if let Some(id) = r["operand"].as_str().and_then(|b| b.strip_prefix('b')).and_then(|n| n.parse().ok()) {
|
||||
given(id, &mut bodies);
|
||||
}
|
||||
}
|
||||
let rig = json!({
|
||||
"status": out["status"], "error": out["error"], "refused_with": out["refused_with"], "measured": out["measured"],
|
||||
"known_failing": known_failing(dir, &id),
|
||||
});
|
||||
let operation_expressible = why_not.is_empty();
|
||||
let entry = json!({
|
||||
"case": id,
|
||||
"area": area,
|
||||
"sources": want["sources"],
|
||||
"what": want["what"],
|
||||
"expressible": operation_expressible && ungraded.is_empty(),
|
||||
"operation_expressible": operation_expressible,
|
||||
"why_not": why_not,
|
||||
"ungraded": ungraded,
|
||||
"expected": want,
|
||||
"rig": rig,
|
||||
"outcome": outcome,
|
||||
"calls": calls,
|
||||
"bodies": bodies,
|
||||
"result": result,
|
||||
"traced_ops": t.calls.iter().map(|c| c.0).collect::<Vec<_>>(),
|
||||
});
|
||||
let folder: PathBuf = root.join(&area);
|
||||
std::fs::create_dir_all(&folder).map_err(|e| format!("{}: {e}", folder.display()))?;
|
||||
let path = folder.join(format!("{id}.json"));
|
||||
let text = serde_json::to_string(&entry).map_err(|e| e.to_string())?;
|
||||
std::fs::write(&path, text).map_err(|e| format!("{}: {e}", path.display()))?;
|
||||
// Cases run in parallel processes: each writes its own file and renames it over the shared
|
||||
// one, so no reader sees a torn file (every process writes the same fix set).
|
||||
let fixes = serde_json::to_string_pretty(&smlib_trace::fixes()).map_err(|e| e.to_string())?;
|
||||
let own = root.join(format!("fixes.json.{}", std::process::id()));
|
||||
std::fs::write(&own, fixes).map_err(|e| format!("{}: {e}", own.display()))?;
|
||||
std::fs::rename(&own, root.join("fixes.json")).map_err(|e| format!("{}: {e}", root.display()))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn the_area_is_the_path_under_kernel_cases() {
|
||||
assert_eq!(area_and_id("tests/kernel-cases/booleans/1894-x").unwrap(), ("booleans".into(), "1894-x".into()));
|
||||
assert_eq!(area_and_id("/r/tests/kernel-cases/ladder/07-fillets/003-y").unwrap(), ("ladder/07-fillets".into(), "003-y".into()));
|
||||
assert!(area_and_id("/r/other/x").is_err());
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,12 @@
|
||||
//! solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N]
|
||||
//! [--timeout-s 300] [--json out.json] [--markdown out.md]
|
||||
//! the standard Assistant model test (#4851) on a running app
|
||||
//! solidcraft-cli snapshot <script|design> [--in design|part.step] --out shot.png [--width W] [--height H] [--view iso|front|top|…|NAME | --camera JSON] [--no-grid] [--no-axes] [--quality LEVEL]
|
||||
//! solidcraft-cli snapshot <script|design> [--in design|part.step] --out shot.png [--width W] [--height H] [--view iso|front|top|…|NAME | --camera JSON] [--no-grid] [--no-axes]
|
||||
//! [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]
|
||||
//! solidcraft-cli exec <command> [json-params] run one command on an empty design
|
||||
//! solidcraft-cli commands the command registry as JSON
|
||||
//! solidcraft-cli recipe <recipe.json> translate an oracle recipe to a script
|
||||
//! solidcraft-cli oracle <case-dir>... replay recipes and compare with expected.json
|
||||
//! solidcraft-cli kernel-cases [--json] [--dumps=DIR] [--validation=MODE] <case-dir>... the kernel regression rig (tests/kernel-cases)
|
||||
//! solidcraft-cli mcp [--headless] [--in design|script] [--connect HOST:PORT [--token T]] [--allow-path DIR]... [--allow-any-path]
|
||||
//! [--param-check off|warn|strict]
|
||||
//! MCP server on stdio (docs/mcp.md)
|
||||
@@ -34,12 +33,9 @@ mod bench_agent;
|
||||
mod bench_assistant;
|
||||
mod crashes;
|
||||
mod fuzz;
|
||||
mod kernel_cases;
|
||||
mod kernel_cases_export;
|
||||
mod logger;
|
||||
mod oracle;
|
||||
mod preview;
|
||||
mod profiling;
|
||||
mod recipe;
|
||||
mod render;
|
||||
mod spatial;
|
||||
@@ -62,19 +58,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] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n [--param-check off|warn|strict]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n (--profile also records where the time goes, SMLib's calls, kernel operations, features, commands and import stages,\n and prints the table to stderr: count, total, self time and longest per span; --profile-out FILE writes the spans\n as a Chrome trace for chrome://tracing or ui.perfetto.dev; SOLIDCRAFT_PROFILE=1 does the same for any command)\n";
|
||||
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.sc|part.step> [--in design.sc|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]\n [--param-check off|warn|strict]\n solidcraft-cli run --in <design.sc|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--param-check checks each step against its command's contract: warn reports on stderr, strict stops the run)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n";
|
||||
/// `eval`'s lines in the usage.
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.sc|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile and --profile-out as for run)\n";
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.sc|part.step> [--in design.sc|part.step] [--json-out FILE] (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";
|
||||
/// `script`'s lines in the usage.
|
||||
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.sc|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
|
||||
/// `bench-open`'s lines in the usage.
|
||||
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] [--profile] [--profile-out trace.json]\n (--profile and --profile-out as for run)\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.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";
|
||||
/// `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";
|
||||
/// `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] [--quality coarse|normal|fine|ultra]\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 --quality: the display quality the bodies are meshed at, as the app's setting (default normal))\n";
|
||||
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";
|
||||
/// `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.
|
||||
@@ -85,8 +81,6 @@ const USAGE_COMMANDS: &str = " solidcraft-cli commands\n";
|
||||
const USAGE_RECIPE: &str = " solidcraft-cli recipe <recipe.json>\n";
|
||||
/// `oracle`'s lines in the usage.
|
||||
const USAGE_ORACLE: &str = " solidcraft-cli oracle <case-dir>... [--json]\n";
|
||||
/// `kernel-cases`'s lines in the usage.
|
||||
const USAGE_KERNEL_CASES: &str = " solidcraft-cli kernel-cases <case-dir>... [--json] [--dumps=DIR] [--export=DIR] [--validation=each_step|each_feature|off]\n (the kernel regression rig, tests/kernel-cases, on SMLib; --dumps: each case's\n failing SMLib calls leave their inputs for replay in DIR/<area>-<case>; --export: each case's\n kernel operations, input records and expected values for solidcore's rig, in DIR/<area>/<case>.json; --validation: when SMLib's\n full check runs (diagnostics.validation), default each_feature)\n";
|
||||
/// `fuzz`'s lines in the usage.
|
||||
const USAGE_FUZZ: &str =
|
||||
" solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]\n";
|
||||
@@ -124,7 +118,6 @@ const COMMANDS: &[Command] = &[
|
||||
Command { name: "commands", usage: USAGE_COMMANDS, listed: true },
|
||||
Command { name: "recipe", usage: USAGE_RECIPE, listed: true },
|
||||
Command { name: "oracle", usage: USAGE_ORACLE, listed: true },
|
||||
Command { name: "kernel-cases", usage: USAGE_KERNEL_CASES, listed: true },
|
||||
Command { name: "fuzz", usage: USAGE_FUZZ, listed: true },
|
||||
Command { name: "fuzz-one", usage: USAGE_FUZZ_ONE, listed: false },
|
||||
Command { name: "mcp", usage: USAGE_MCP, listed: true },
|
||||
@@ -140,19 +133,6 @@ const COMMANDS: &[Command] = &[
|
||||
fn main() -> ExitCode {
|
||||
let mut args: Vec<String> = std::env::args().skip(1).collect();
|
||||
logger::init(&mut args);
|
||||
// `--profile` / `--profile-out` (run, eval, bench-open) or SOLIDCRAFT_PROFILE (any command):
|
||||
// record from here, and print the table when the command ends.
|
||||
let profiling = match args.first().map(String::as_str) {
|
||||
Some("run" | "eval" | "inspect" | "bench-open") => profiling::start(&args[1..]),
|
||||
_ => profiling::start(&[]),
|
||||
};
|
||||
let profiling = match profiling {
|
||||
Ok(p) => p,
|
||||
Err(e) => {
|
||||
eprintln!("error: {e}");
|
||||
return ExitCode::FAILURE;
|
||||
}
|
||||
};
|
||||
if let Err(e) = max_file_size(&mut args) {
|
||||
eprintln!("error: {e}");
|
||||
return ExitCode::FAILURE;
|
||||
@@ -190,7 +170,6 @@ fn main() -> ExitCode {
|
||||
recipe::to_script(&v).map(|s| println!("{}", pretty(&s)))
|
||||
}),
|
||||
Some("oracle") => oracle::run(&args[1..]),
|
||||
Some("kernel-cases") => kernel_cases::run(&args[1..]),
|
||||
Some("fuzz") => fuzz::run(&args[1..]),
|
||||
Some("fuzz-one") => fuzz::one(&args[1..]),
|
||||
Some("mcp") => {
|
||||
@@ -227,11 +206,6 @@ fn main() -> ExitCode {
|
||||
}
|
||||
_ => Err(usage()),
|
||||
};
|
||||
// The table also when the command failed: where it spent its time before failing.
|
||||
let r = match (r, profiling.map(profiling::Profiling::finish)) {
|
||||
(Ok(()), Some(Err(e))) => Err(e),
|
||||
(r, _) => r,
|
||||
};
|
||||
match r {
|
||||
Ok(()) => ExitCode::SUCCESS,
|
||||
Err(e) => {
|
||||
@@ -312,15 +286,8 @@ pub fn load(path: &str) -> Result<Session, String> {
|
||||
/// `load`, with scripts and recipes run at the parameter-check level `check`, which the
|
||||
/// session keeps.
|
||||
fn load_checked(path: &str, check: solidcraft_engine::schema::ParamCheck) -> Result<Session, String> {
|
||||
load_with(path, check, solidcraft_engine::kernel::Validation::default())
|
||||
}
|
||||
|
||||
/// `load_checked`, the features computed under `validation` (`diagnostics.validation`), which
|
||||
/// the session keeps.
|
||||
fn load_with(path: &str, check: solidcraft_engine::schema::ParamCheck, validation: solidcraft_engine::kernel::Validation) -> Result<Session, String> {
|
||||
let mut s = Session::default();
|
||||
s.param_check = check;
|
||||
s.set_validation(validation);
|
||||
if solidcraft_engine::io::is_step_path(path) || solidcraft_engine::io::is_mesh_path(path) || solidcraft_engine::io::is_point_cloud_path(path) {
|
||||
s.execute("doc.open", &json!({"path": path})).map_err(|e| e.to_string())?;
|
||||
return Ok(s);
|
||||
@@ -328,7 +295,7 @@ fn load_with(path: &str, check: solidcraft_engine::schema::ParamCheck, validatio
|
||||
let bytes = solidcraft_engine::io::limits::read(path, solidcraft_engine::io::limits::Kind::Design).map_err(|e| format!("{path}: {e}"))?;
|
||||
if solidcraft_engine::io::is_design_container(&bytes) {
|
||||
let doc = solidcraft_engine::io::read_design(&bytes).map_err(|e| format!("{path}: {e}"))?;
|
||||
let mut session = Session::opened_at_validating(doc, None, Some(path), validation);
|
||||
let mut session = Session::opened_at(doc, None, Some(path));
|
||||
session.param_check = check;
|
||||
return Ok(session);
|
||||
}
|
||||
@@ -392,16 +359,6 @@ fn positional(args: &[String]) -> Option<&String> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Is `path` a command script (or a recipe), not a design: JSON that is no saved design.
|
||||
fn is_script(path: &str) -> bool {
|
||||
let Ok(bytes) = solidcraft_engine::io::vfs::read(path, solidcraft_engine::io::MAX_DESIGN_BYTES) else { return false };
|
||||
if solidcraft_engine::io::is_design_container(&bytes) {
|
||||
return false;
|
||||
}
|
||||
serde_json::from_slice::<Value>(&bytes)
|
||||
.is_ok_and(|v| !v.get("format").and_then(Value::as_str).is_some_and(|f| solidcraft_engine::doc::format::version_of(f).is_some()))
|
||||
}
|
||||
|
||||
/// `--json-out FILE`: where the JSON a command prints goes instead of stdout.
|
||||
fn json_out(args: &[String]) -> Result<Option<&str>, String> {
|
||||
if !args.iter().any(|a| a == "--json-out") {
|
||||
@@ -438,31 +395,24 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
known_args(
|
||||
"run",
|
||||
args,
|
||||
&["--in", "--out", "--save", "--json-out", "--param-check", "--profile-out", "--validation"],
|
||||
&["--in", "--out", "--save", "--json-out", "--param-check"],
|
||||
&["--quiet", "--results", "--timings", "--profile", "--allow-errors"],
|
||||
true,
|
||||
)?;
|
||||
let validation = validation_flag("run", args)?;
|
||||
let base = flag(args, "--in");
|
||||
let path = positional(args);
|
||||
if path.is_none() && base.is_none() {
|
||||
return Err(usage());
|
||||
}
|
||||
let want_results = args.iter().any(|a| a == "--results");
|
||||
// `--profile` records the profiler's spans (the table goes to stderr, `profiling.rs`); for a
|
||||
// script it also times each command and splits out its kernel time, implying `--timings`.
|
||||
let script = path.is_some_and(|p| is_script(p));
|
||||
let profile = script && args.iter().any(|a| a == "--profile" || a == "--profile-out");
|
||||
// `--profile` times each command and splits out its kernel time; it implies `--timings`.
|
||||
let profile = args.iter().any(|a| a == "--profile");
|
||||
let timings = profile || args.iter().any(|a| a == "--timings");
|
||||
let (mut s, results) = if want_results || timings {
|
||||
// Collect every command's result or time (scripts only), then process requested files too.
|
||||
let mut s = match base {
|
||||
Some(b) => load_with(b, check, validation)?,
|
||||
None => {
|
||||
let mut s = Session::default();
|
||||
s.set_validation(validation);
|
||||
s
|
||||
}
|
||||
Some(b) => load_checked(b, check)?,
|
||||
None => Session::default(),
|
||||
};
|
||||
s.param_check = check;
|
||||
let needs = if want_results { "--results needs a script" } else { "--timings needs a script" };
|
||||
@@ -479,7 +429,7 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
};
|
||||
(s, Some(out))
|
||||
} else {
|
||||
(open_validating(base, path, check, validation)?, None)
|
||||
(open(base, path, check)?, None)
|
||||
};
|
||||
// A feature that fails leaves the bodies as they were before it (a parameter change can
|
||||
// break a feature without failing the command that made it): writing them would hand out
|
||||
@@ -506,38 +456,18 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
/// The session `run`, `eval` and `snapshot` start from: `--in` (a design or STEP file) with the
|
||||
/// positional script run on top of it, or either one alone, with scripts run at `check`.
|
||||
fn open(base: Option<&str>, path: Option<&String>, check: solidcraft_engine::schema::ParamCheck) -> Result<Session, String> {
|
||||
open_validating(base, path, check, solidcraft_engine::kernel::Validation::default())
|
||||
}
|
||||
|
||||
/// [`open`], the features computed under `validation`.
|
||||
fn open_validating(
|
||||
base: Option<&str>,
|
||||
path: Option<&String>,
|
||||
check: solidcraft_engine::schema::ParamCheck,
|
||||
validation: solidcraft_engine::kernel::Validation,
|
||||
) -> Result<Session, String> {
|
||||
let load = |p: &str| load_with(p, check, validation);
|
||||
match (base, path) {
|
||||
(Some(b), Some(p)) => {
|
||||
let mut s = load(b)?;
|
||||
let mut s = load_checked(b, check)?;
|
||||
run_script_warning(&mut s, p, &read_json(p)?)?;
|
||||
Ok(s)
|
||||
}
|
||||
(Some(b), None) => load(b),
|
||||
(None, Some(p)) => load(p),
|
||||
(Some(b), None) => load_checked(b, check),
|
||||
(None, Some(p)) => load_checked(p, check),
|
||||
(None, None) => Err(usage()),
|
||||
}
|
||||
}
|
||||
|
||||
/// `--validation MODE` (`diagnostics.validation`): the default, each_feature, when not given.
|
||||
fn validation_flag(cmd: &str, args: &[String]) -> Result<solidcraft_engine::kernel::Validation, String> {
|
||||
match flag(args, "--validation") {
|
||||
Some(m) => solidcraft_engine::kernel::Validation::parse(m)
|
||||
.ok_or_else(|| format!("{cmd}: --validation must be each_step, each_feature or off, not `{m}`")),
|
||||
None => Ok(solidcraft_engine::kernel::Validation::default()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Refuses an option `cmd` doesn't take, and one missing its value: a typo, or another
|
||||
/// command's option, would otherwise be ignored without a word. `positionals` says whether the
|
||||
/// command takes a word of its own (a file, a seed): `fuzz` takes none, and would start a run.
|
||||
@@ -575,7 +505,7 @@ fn timeline_ok(s: &Session) -> Result<(), String> {
|
||||
|
||||
fn cmd_eval(args: &[String]) -> Result<(), String> {
|
||||
let json_out = json_out(args)?;
|
||||
known_args("eval", args, &["--in", "--json-out", "--profile-out"], &["--profile"], true)?;
|
||||
known_args("eval", args, &["--in", "--json-out"], &[], true)?;
|
||||
let mut s = open(flag(args, "--in"), positional(args), solidcraft_engine::schema::ParamCheck::Off)?;
|
||||
let m = s.execute("inspect.measure", &json!({})).map_err(|e| e.to_string())?;
|
||||
let d = s.execute("document.inspect", &json!({})).map_err(|e| e.to_string())?;
|
||||
@@ -657,7 +587,7 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
||||
known_args(
|
||||
"snapshot",
|
||||
args,
|
||||
&["--in", "--out", "--width", "--height", "--view", "--camera", "--body", "--tool", "--section", "--quality"],
|
||||
&["--in", "--out", "--width", "--height", "--view", "--camera", "--body", "--tool", "--section"],
|
||||
&["--no-grid", "--no-axes"],
|
||||
true,
|
||||
)?;
|
||||
@@ -670,16 +600,6 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
||||
Some(x) => x.parse().ok().filter(|n| (16..=8192).contains(n)).ok_or_else(|| format!("{name} must be 16 to 8192 pixels, not `{x}`")),
|
||||
};
|
||||
let (w, h) = (size("--width", 1280)?, size("--height", 800)?);
|
||||
// The display quality the bodies are meshed at, as the app's Display Quality setting: set
|
||||
// before the design opens, so every display plan is made at it.
|
||||
if let Some(q) = flag(args, "--quality") {
|
||||
use solidcraft_engine::doc::display_quality::{DisplayQuality, set_current};
|
||||
let level = DisplayQuality::parse(q).ok_or_else(|| {
|
||||
let ids: Vec<&str> = DisplayQuality::ALL.iter().map(|q| q.id()).collect();
|
||||
format!("--quality must be one of {}, not `{q}`", ids.join(", "))
|
||||
})?;
|
||||
set_current(level);
|
||||
}
|
||||
let mut s = open(flag(args, "--in"), positional(args), solidcraft_engine::schema::ParamCheck::Off)?;
|
||||
// How the bodies are drawn and where the model is cut, through the commands the app's
|
||||
// Appearance dialog and Section Analysis run (`preview`).
|
||||
@@ -699,12 +619,9 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> {
|
||||
}
|
||||
(None, None) => {}
|
||||
}
|
||||
let cam = match s.camera {
|
||||
Some(cam) => cam,
|
||||
None => solidcraft_engine::view::home_camera(&s).map_err(|e| e.to_string())?,
|
||||
};
|
||||
let cam = s.camera.unwrap_or_else(|| solidcraft_engine::view::home_camera(&s));
|
||||
let guides = Guides { grid: !args.iter().any(|a| a == "--no-grid"), axes: !args.iter().any(|a| a == "--no-axes") };
|
||||
let scene = solidcraft_engine::view::scene_with_guides(&s, &cam, s.section, guides).map_err(|e| e.to_string())?;
|
||||
let scene = solidcraft_engine::view::scene_with_guides(&s, &cam, s.section, guides);
|
||||
let png = solidcraft_engine::capture::png(&scene, &cam, w, h)?;
|
||||
std::fs::write(out, &png.bytes).map_err(|e| format!("{out}: {e}"))?;
|
||||
let mut reply = json!({"path": out, "width": w, "height": h, "bytes": png.bytes.len(), "renderer": png.renderer});
|
||||
@@ -933,7 +850,7 @@ fn bench_ops(mut s: Session, script: &str) -> Result<Session, String> {
|
||||
let r = s.execute(id, p).map_err(|e| format!("{id}: {e}"))?;
|
||||
let ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
let t = std::time::Instant::now();
|
||||
s.world_state().map_err(|e| e.to_string())?.warm_meshes(|| true);
|
||||
s.world_state().warm_meshes(|| true);
|
||||
let view_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
println!("{}", json!({"step": "op", "command": id, "ms": ms, "view_ms": view_ms}));
|
||||
Ok(r)
|
||||
@@ -989,7 +906,7 @@ pub(crate) fn cmd_bench_open(args: &[String]) -> Result<(), String> {
|
||||
println!("{}", json!({"step": "feature", "id": r.id, "name": r.name, "ms": r.ms, "error": r.error}));
|
||||
}
|
||||
let t = std::time::Instant::now();
|
||||
let st = s.world_state().map_err(|e| e.to_string())?;
|
||||
let st = s.world_state();
|
||||
let place_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
// As the desktop app prepares a design it opened.
|
||||
st.warm_meshes(|| true);
|
||||
@@ -1059,7 +976,7 @@ pub(crate) fn cmd_bench_open(args: &[String]) -> Result<(), String> {
|
||||
let t = std::time::Instant::now();
|
||||
s.execute("parameters.change", &json!({"name": name, "expression": expr})).map_err(|e| e.to_string())?;
|
||||
let eval_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
let st = s.world_state().map_err(|e| e.to_string())?;
|
||||
let st = s.world_state();
|
||||
let t = std::time::Instant::now();
|
||||
st.warm_meshes(|| true);
|
||||
let mesh_ms = t.elapsed().as_secs_f64() * 1000.0;
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#![allow(clippy::needless_range_loop)]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
use crate::spatial::{self, Tri};
|
||||
use crate::{read_json, recipe};
|
||||
@@ -13,28 +13,28 @@ use crate::{read_json, recipe};
|
||||
/// Default relative tolerance for volume and area (our curved-face tessellation is within ~2e-4).
|
||||
pub const REL_TOL: f64 = 1e-3;
|
||||
/// Default tolerance for centre of mass and bounding box, as a fraction of the bbox diagonal.
|
||||
pub(crate) const ABS_FRAC: f64 = 1e-3;
|
||||
const ABS_FRAC: f64 = 1e-3;
|
||||
/// Chord tolerance used to triangulate our part for the surface-distance check (mm).
|
||||
const MESH_TOL: f64 = 0.01;
|
||||
/// Relative tolerance on the principal second moments of the two triangulations.
|
||||
const INERTIA_REL: f64 = 2e-3;
|
||||
|
||||
/// What SolidCraft built: totals over every body.
|
||||
pub(crate) struct Got {
|
||||
pub(crate) body_count: usize,
|
||||
pub(crate) volume: f64,
|
||||
pub(crate) area: f64,
|
||||
pub(crate) com: [f64; 3],
|
||||
pub(crate) bbox: ([f64; 3], [f64; 3]),
|
||||
pub(crate) faces: u64,
|
||||
struct Got {
|
||||
body_count: usize,
|
||||
volume: f64,
|
||||
area: f64,
|
||||
com: [f64; 3],
|
||||
bbox: ([f64; 3], [f64; 3]),
|
||||
faces: u64,
|
||||
}
|
||||
|
||||
pub(crate) fn v3(v: &Value) -> Option<[f64; 3]> {
|
||||
fn v3(v: &Value) -> Option<[f64; 3]> {
|
||||
let a = v.as_array()?;
|
||||
Some([a.first()?.as_f64()?, a.get(1)?.as_f64()?, a.get(2)?.as_f64()?])
|
||||
}
|
||||
|
||||
pub(crate) fn totals(m: &Value) -> Got {
|
||||
fn totals(m: &Value) -> Got {
|
||||
let bodies = m["bodies"].as_array().cloned().unwrap_or_default();
|
||||
let (mut volume, mut area, mut faces) = (0.0, 0.0, 0);
|
||||
let mut moment = [0.0; 3];
|
||||
@@ -57,21 +57,21 @@ pub(crate) fn totals(m: &Value) -> Got {
|
||||
Got { body_count: bodies.len(), volume, area, com, bbox: (lo, hi), faces }
|
||||
}
|
||||
|
||||
pub(crate) struct Checks {
|
||||
pub(crate) list: Vec<Value>,
|
||||
pub(crate) pass: bool,
|
||||
struct Checks {
|
||||
list: Vec<Value>,
|
||||
pass: bool,
|
||||
}
|
||||
|
||||
impl Checks {
|
||||
pub(crate) fn push(&mut self, what: &str, got: Value, want: Value, ok: bool) {
|
||||
fn push(&mut self, what: &str, got: Value, want: Value, ok: bool) {
|
||||
self.pass &= ok;
|
||||
self.list.push(json!({"check": what, "got": got, "want": want, "ok": ok}));
|
||||
}
|
||||
pub(crate) fn rel(&mut self, what: &str, g: f64, w: Option<f64>, rel: f64) {
|
||||
fn rel(&mut self, what: &str, g: f64, w: Option<f64>, rel: f64) {
|
||||
let ok = w.is_some_and(|w| g.is_finite() && (g - w).abs() <= rel * w.abs().max(1.0));
|
||||
self.push(what, json!(g), json!(w), ok);
|
||||
}
|
||||
pub(crate) fn vec(&mut self, what: &str, g: [f64; 3], w: Option<[f64; 3]>, tol: f64) {
|
||||
fn vec(&mut self, what: &str, g: [f64; 3], w: Option<[f64; 3]>, tol: f64) {
|
||||
let ok = w.is_some_and(|w| g.iter().zip(w).all(|(g, w)| g.is_finite() && (g - w).abs() <= tol));
|
||||
self.push(what, json!(g), json!(w), ok);
|
||||
}
|
||||
@@ -186,8 +186,8 @@ fn step_export_check(s: &solidcraft_engine::Session, volume: f64) -> (bool, Stri
|
||||
/// Our part as a triangle soup (curved faces refined onto the surface).
|
||||
fn our_mesh(s: &solidcraft_engine::Session) -> Result<Vec<Tri>, String> {
|
||||
let mut ours: Vec<Tri> = Vec::new();
|
||||
for b in &s.world_state().map_err(|e| e.to_string())?.bodies {
|
||||
let m = b.body.mesh(&solidcraft_engine::kernel::MeshTolerance::chord(MESH_TOL)).map_err(|e| e.to_string())?;
|
||||
for b in &s.world_state().bodies {
|
||||
let m = b.body.tessellate_with(MESH_TOL, true).map_err(|e| e.to_string())?;
|
||||
let p = |i: u32| m.positions.get(i as usize).map(|v: &Vec3| [v.x, v.y, v.z]);
|
||||
for t in &m.triangles {
|
||||
if let (Some(a), Some(b), Some(c)) = (p(t[0]), p(t[1]), p(t[2])) {
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
//! `--profile` and `--profile-out FILE` for `run`, `eval` and `bench-open` (and
|
||||
//! `SOLIDCRAFT_PROFILE=1` for any command): record where the time goes, the kernel's calls to the
|
||||
//! commands, print the table to stderr when the command ends (stdout stays the command's JSON)
|
||||
//! and write the spans as a Chrome trace for chrome://tracing or Perfetto.
|
||||
|
||||
use solidcraft_engine::profile;
|
||||
|
||||
/// Rows the table prints.
|
||||
const TOP: usize = 30;
|
||||
|
||||
/// A recording that prints its table when finished.
|
||||
pub struct Profiling {
|
||||
trace: Option<String>,
|
||||
}
|
||||
|
||||
/// Start recording when `args` (after the command) ask for it, or `SOLIDCRAFT_PROFILE` does.
|
||||
pub fn start(args: &[String]) -> Result<Option<Profiling>, String> {
|
||||
let trace = match args.iter().position(|a| a == "--profile-out") {
|
||||
Some(i) => match args.get(i + 1) {
|
||||
Some(f) if !f.starts_with("--") => Some(f.clone()),
|
||||
_ => return Err("--profile-out needs a FILE".into()),
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let asked = trace.is_some() || args.iter().any(|a| a == "--profile");
|
||||
if !asked && !solidcraft_engine::timing::profile_from_env() {
|
||||
return Ok(None);
|
||||
}
|
||||
if !profile::COMPILED {
|
||||
return Err("this solidcraft-cli was built without the profiler (its `profile` feature)".into());
|
||||
}
|
||||
profile::reset();
|
||||
profile::set_enabled(true);
|
||||
Ok(Some(Profiling { trace }))
|
||||
}
|
||||
|
||||
impl Profiling {
|
||||
/// Stop recording, print the table on stderr and write the trace.
|
||||
pub fn finish(self) -> Result<(), String> {
|
||||
profile::set_enabled(false);
|
||||
eprint!("{}", profile::report().table(TOP));
|
||||
if let Some(path) = self.trace {
|
||||
let mut bytes = Vec::new();
|
||||
profile::write_chrome_trace(&mut bytes).map_err(|e| format!("{path}: {e}"))?;
|
||||
std::fs::write(&path, bytes).map_err(|e| format!("{path}: {e}"))?;
|
||||
eprintln!("profile: trace written to {path} (open it in chrome://tracing or ui.perfetto.dev)");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -21,7 +21,6 @@
|
||||
//! Each design runs in a child process (this test binary, [`action_matrix_worker`]) so an action
|
||||
//! that hangs or aborts is recorded as such and the rest of the design still runs.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
use std::io::{BufRead, Write};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -29,10 +28,10 @@ use std::process::{Command, Stdio};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::kernel::{FaceSurface, face_surface};
|
||||
use solidcraft_engine::doc::{FaceAttachmentStatus, ModelBody, naming};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
/// One in this many actions runs on pull requests (by a stable hash of its key).
|
||||
const PR_SAMPLE: u64 = 40;
|
||||
@@ -197,7 +196,7 @@ fn round3(x: f64) -> f64 {
|
||||
|
||||
/// Every action on every entity of the design's final bodies, in a fixed order.
|
||||
fn actions(s: &Session) -> Vec<Action> {
|
||||
let st = s.world_state().unwrap();
|
||||
let st = s.world_state();
|
||||
let placed = s.placed_document();
|
||||
let paths: BTreeMap<String, Vec<u64>> =
|
||||
placed.body_frames_with_source(&s.model.state()).into_iter().map(|(world, _, _, path, _)| (world, path)).collect();
|
||||
@@ -1339,9 +1338,7 @@ fn a_fillet_or_chamfer_on_one_edge_ending_at_a_cylinder() {
|
||||
fn a_round_on_a_knuckle_cap_circle() {
|
||||
for (cmd, size, removed) in [("solid.fillet", "radius", (2.4, 2.8)), ("solid.chamfer", "distance", (5.6, 6.3))] {
|
||||
let mut s = load(&root().join("tests/oracle/63-hinge-knuckle/recipe.json")).unwrap();
|
||||
let volume = |s: &Session| {
|
||||
s.model.state().bodies.iter().map(|b| b.body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.001)).unwrap().measure().volume).sum::<f64>()
|
||||
};
|
||||
let volume = |s: &Session| s.model.state().bodies.iter().map(|b| b.body.tessellate(0.001).unwrap().measure().volume).sum::<f64>();
|
||||
let before = volume(&s);
|
||||
s.execute(cmd, &json!({"edges": [[40.0, 29.837_722, 6.449_490]], size: 0.728})).unwrap_or_else(|e| panic!("{cmd}: {e}"));
|
||||
check(&s, 0, &BTreeMap::new()).unwrap_or_else(|(kind, e)| panic!("{cmd}: {kind}: {e}"));
|
||||
@@ -1361,9 +1358,7 @@ fn a_round_on_a_knuckle_cap_circle() {
|
||||
#[test]
|
||||
fn a_hair_thin_hole_on_a_ledge_beside_a_loft_cut() {
|
||||
let mut s = load(&root().join("examples/regressions/loft_cut_beside_round_hole.json")).unwrap();
|
||||
let volume = |s: &Session| {
|
||||
s.model.state().bodies.iter().map(|b| b.body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.005)).unwrap().measure().volume).sum::<f64>()
|
||||
};
|
||||
let volume = |s: &Session| s.model.state().bodies.iter().map(|b| b.body.tessellate_with(0.005, true).unwrap().measure().volume).sum::<f64>();
|
||||
let before = volume(&s);
|
||||
s.execute("solid.hole", &json!({"diameter": 0.133, "position": [357.22, -150.993_333, 730.0]})).unwrap();
|
||||
check(&s, 0, &BTreeMap::new()).unwrap_or_else(|(kind, e)| panic!("{kind}: {e}"));
|
||||
@@ -1380,9 +1375,7 @@ fn a_hair_thin_hole_on_a_ledge_beside_a_loft_cut() {
|
||||
#[test]
|
||||
fn a_small_hole_through_the_wall_of_a_plastic_rest() {
|
||||
let mut s = load(&root().join("apps/solidcraft-cli/tests/corpus/modern-plastic.solidcraft")).unwrap();
|
||||
let volume = |s: &Session| {
|
||||
s.model.state().bodies.iter().map(|b| b.body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.002)).unwrap().measure().volume).sum::<f64>()
|
||||
};
|
||||
let volume = |s: &Session| s.model.state().bodies.iter().map(|b| b.body.tessellate_with(0.002, true).unwrap().measure().volume).sum::<f64>();
|
||||
let before = volume(&s);
|
||||
s.execute("solid.hole", &json!({"diameter": 0.8, "position": [63.069_841_5, 28.333_333, 4.0]})).unwrap();
|
||||
check(&s, 0, &BTreeMap::new()).unwrap_or_else(|(kind, e)| panic!("{kind}: {e}"));
|
||||
@@ -1397,9 +1390,7 @@ fn a_small_hole_through_the_wall_of_a_plastic_rest() {
|
||||
#[test]
|
||||
fn a_hole_square_to_a_45_degree_flange() {
|
||||
let mut s = load(&root().join("tests/oracle/70-sm-flange-45/recipe.json")).unwrap();
|
||||
let volume = |s: &Session| {
|
||||
s.model.state().bodies.iter().map(|b| b.body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.002)).unwrap().measure().volume).sum::<f64>()
|
||||
};
|
||||
let volume = |s: &Session| s.model.state().bodies.iter().map(|b| b.body.tessellate_with(0.002, true).unwrap().measure().volume).sum::<f64>();
|
||||
let before = volume(&s);
|
||||
s.execute("solid.hole", &json!({"diameter": 3.448, "position": [66.666_667, -4.511_845, 4.511_845]})).unwrap();
|
||||
check(&s, 0, &BTreeMap::new()).unwrap_or_else(|(kind, e)| panic!("{kind}: {e}"));
|
||||
@@ -1657,7 +1648,7 @@ impl TipConstruction {
|
||||
other => panic!("expected an axis, got {other:?}"),
|
||||
}
|
||||
}
|
||||
fn plane(&self) -> solidcore_record::Plane {
|
||||
fn plane(&self) -> solidcraft_engine::geom::Plane {
|
||||
match &self.0 {
|
||||
solidcraft_engine::doc::construct::ConstructGeom::Plane(p) => *p,
|
||||
other => panic!("expected a plane, got {other:?}"),
|
||||
@@ -1737,7 +1728,7 @@ fn the_ribbed_lid_builds_the_same_body_every_time() {
|
||||
st.bodies
|
||||
.iter()
|
||||
.map(|b| {
|
||||
let mesh = b.body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.1)).unwrap();
|
||||
let mesh = b.body.tessellate(0.1).unwrap();
|
||||
let bits = mesh.positions.iter().fold(0u64, |acc, p| {
|
||||
acc.rotate_left(7).wrapping_add(p.x.to_bits() ^ p.y.to_bits().rotate_left(21) ^ p.z.to_bits().rotate_left(42))
|
||||
});
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
use std::f64::consts::PI;
|
||||
|
||||
use serde_json::Value;
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
use solidcraft_engine::{Session, io, kernel};
|
||||
|
||||
fn assert_two_through_holes(session: &Session, stage: &str) {
|
||||
@@ -26,8 +26,8 @@ fn assert_two_through_holes(session: &Session, stage: &str) {
|
||||
let expected = 2.0 * PI * 4.0_f64.powi(2) * 6.0;
|
||||
assert!((removed - expected).abs() < expected * 1e-3, "{stage}: the two holes remove {removed} mm³, expected {expected} mm³");
|
||||
|
||||
let before_mesh = uncut.mesh(&solidcraft_kernel::MeshTolerance::chord(0.01)).unwrap();
|
||||
let after_mesh = bracket.mesh(&solidcraft_kernel::MeshTolerance::chord(0.01)).unwrap();
|
||||
let before_mesh = uncut.tessellate(0.01).unwrap();
|
||||
let after_mesh = bracket.tessellate(0.01).unwrap();
|
||||
let down = Vec3::new(0.0, 0.0, -1.0);
|
||||
for y in [-15.0, -45.0] {
|
||||
let center = Vec3::new(25.0, y, 1.0);
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
# Design file corpus
|
||||
|
||||
Designs saved by older SolidCraft builds. `tests/design_corpus.rs` opens each one with the
|
||||
current build and checks that it is upgraded to the current file format, that it evaluates
|
||||
(a feature may fail only by asking for a reference it lost to be picked again; what it rebuilds
|
||||
to is today's), and that damaged and hostile variants of it never panic. The `.expected.json`
|
||||
next to each is `solidcraft-cli eval` of that file by the build that saved it (bodies, volumes,
|
||||
areas, world bounding boxes), kept as a record of what that build made; no test compares with
|
||||
it.
|
||||
current build and checks that it evaluates as it did when saved (the `.expected.json` next to
|
||||
it is `solidcraft-cli eval` of that file by the build that saved it: bodies, volumes, areas,
|
||||
world bounding boxes), that it is upgraded to the current file format, and that damaged and
|
||||
hostile variants of it never panic.
|
||||
|
||||
| File | Saved by | Era |
|
||||
|---|---|---|
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
],
|
||||
"timeline": [
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 1,
|
||||
"ms": 0.131943,
|
||||
@@ -100,6 +104,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 2,
|
||||
"ms": 1.0812709999999999,
|
||||
@@ -110,6 +118,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 3,
|
||||
"ms": 0.450002,
|
||||
@@ -120,6 +132,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 4,
|
||||
"ms": 5.191514000000001,
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
],
|
||||
"timeline": [
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 1,
|
||||
"ms": 1.178957,
|
||||
@@ -64,6 +68,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 2,
|
||||
"ms": 83.24342399999999,
|
||||
@@ -74,6 +82,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 3,
|
||||
"ms": 0.150889,
|
||||
@@ -84,6 +96,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 4,
|
||||
"ms": 94.465481,
|
||||
@@ -94,6 +110,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 5,
|
||||
"ms": 50.426736,
|
||||
@@ -104,6 +124,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 6,
|
||||
"ms": 40.383005,
|
||||
@@ -114,6 +138,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 7,
|
||||
"ms": 59.041461000000005,
|
||||
@@ -124,6 +152,18 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [
|
||||
{
|
||||
"face_shift_mm": 0.0,
|
||||
"rotation_rad": 0.0,
|
||||
"strategy": "planar_fallback",
|
||||
"tolerance_mm": 0.0,
|
||||
"translation_mm": 0.0
|
||||
}
|
||||
],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 8,
|
||||
"ms": 562.5477970000001,
|
||||
@@ -131,9 +171,13 @@
|
||||
"rolled_back": false,
|
||||
"suppressed": false,
|
||||
"type": "BoxFeature",
|
||||
"warning": null
|
||||
"warning": "Boolean retry: exact planar fallback (no displacement)"
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 9,
|
||||
"ms": 89.56076,
|
||||
@@ -144,6 +188,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 10,
|
||||
"ms": 65.330634,
|
||||
@@ -154,6 +202,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 11,
|
||||
"ms": 97.229403,
|
||||
|
||||
@@ -99,6 +99,10 @@
|
||||
],
|
||||
"timeline": [
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 1,
|
||||
"ms": 0.204585,
|
||||
@@ -109,6 +113,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 2,
|
||||
"ms": 2.318662,
|
||||
@@ -119,6 +127,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 3,
|
||||
"ms": 0.109196,
|
||||
@@ -129,6 +141,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 4,
|
||||
"ms": 0.338859,
|
||||
@@ -139,6 +155,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 5,
|
||||
"ms": 0.312259,
|
||||
@@ -149,6 +169,10 @@
|
||||
"warning": null
|
||||
},
|
||||
{
|
||||
"boolean_diagnostics": {
|
||||
"events": [],
|
||||
"truncated": false
|
||||
},
|
||||
"error": null,
|
||||
"id": 6,
|
||||
"ms": 0.23144699999999999,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
//! Designs saved by older SolidCraft builds (tests/corpus, see its README) open, are upgraded to
|
||||
//! the current format and evaluate (what they rebuild to today is today's, not what the build
|
||||
//! that saved them made); damaged and hostile design files never panic.
|
||||
//! Designs saved by older SolidCraft builds (tests/corpus, see its README) open and evaluate as
|
||||
//! they did when they were saved; damaged and hostile design files never panic.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::{Value, json};
|
||||
@@ -23,24 +21,67 @@ fn open(path: &Path) -> Session {
|
||||
s
|
||||
}
|
||||
|
||||
/// Every corpus design opens, is upgraded to the current format, evaluates, and saves and reads
|
||||
/// back the same. A feature may fail only by asking for a reference it lost to be picked again
|
||||
/// (a name saved before format 14 finds nothing today).
|
||||
/// Differences that are deliberate behaviour changes made before the file format had versions
|
||||
/// (no migration can tell those files apart): (file, check, why).
|
||||
const KNOWN: [(&str, &str, &str); 2] = [
|
||||
(
|
||||
"m0-revolve-primitives",
|
||||
"Body1 bbox",
|
||||
"partial revolves turn toward the sketch normal since ac43365 (as most parametric CAD tools do); M0 turned them the other way",
|
||||
),
|
||||
("components-arm", "body_count", "Measure counted only the root component's bodies then; it measures every placed body now"),
|
||||
];
|
||||
|
||||
fn known(file: &str, check: &str) -> bool {
|
||||
KNOWN.iter().any(|(f, c, _)| *f == file && *c == check)
|
||||
}
|
||||
|
||||
/// Volumes and areas then came from coarser tessellation (and curved booleans and fillets have
|
||||
/// become exact since): the same part within this relative tolerance (the oracle's).
|
||||
const REL: f64 = 1e-3;
|
||||
|
||||
fn close(a: f64, b: f64, rel: f64) -> bool {
|
||||
(a - b).abs() <= rel * b.abs().max(1.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn old_designs_open_upgrade_and_evaluate() {
|
||||
fn old_designs_open_and_evaluate_as_saved() {
|
||||
let mut problems = Vec::new();
|
||||
for p in corpus() {
|
||||
let name = p.file_stem().unwrap_or_default().to_string_lossy().to_string();
|
||||
let want: Value = serde_json::from_slice(&std::fs::read(p.with_extension("expected.json")).unwrap()).unwrap();
|
||||
let mut s = open(&p);
|
||||
for r in &s.model.results {
|
||||
if let Some(e) = r.error.as_deref().filter(|e| solidcraft_engine::doc::reselect_parts(e).is_none()) {
|
||||
if let Some(e) = &r.error {
|
||||
problems.push(format!("{name}: feature {}: {e}", r.id));
|
||||
}
|
||||
}
|
||||
assert_eq!(s.doc.format, solidcraft_engine::doc::format::format_string(solidcraft_engine::doc::format::FORMAT), "{name}: upgraded");
|
||||
let got = s.execute("inspect.measure", &json!({})).unwrap();
|
||||
if got["body_count"].as_u64().unwrap_or(0) == 0 {
|
||||
problems.push(format!("{name}: no bodies"));
|
||||
if got["body_count"] != want["body_count"] && !known(&name, "body_count") {
|
||||
problems.push(format!("{name}: {} bodies, saved with {}", got["body_count"], want["body_count"]));
|
||||
}
|
||||
for w in want["bodies"].as_array().into_iter().flatten() {
|
||||
let bname = w["name"].as_str().unwrap_or_default();
|
||||
let Some(g) = got["bodies"].as_array().into_iter().flatten().find(|g| g["name"] == w["name"]) else {
|
||||
problems.push(format!("{name}: body {bname} missing"));
|
||||
continue;
|
||||
};
|
||||
for k in ["volume_mm3", "area_mm2"] {
|
||||
let (a, b) = (g[k].as_f64().unwrap_or(f64::NAN), w[k].as_f64().unwrap_or(f64::NAN));
|
||||
if !close(a, b, REL) {
|
||||
problems.push(format!("{name}: {bname} {k} {a} (saved {b})"));
|
||||
}
|
||||
}
|
||||
if known(&name, &format!("{bname} bbox")) {
|
||||
continue;
|
||||
}
|
||||
for (end, i) in [("min", 0), ("min", 1), ("min", 2), ("max", 0), ("max", 1), ("max", 2)] {
|
||||
let (a, b) = (g["bbox"][end][i].as_f64().unwrap_or(f64::NAN), w["bbox"][end][i].as_f64().unwrap_or(f64::NAN));
|
||||
if (a - b).abs() > 1e-3 * b.abs().max(1.0) {
|
||||
problems.push(format!("{name}: {bname} bbox {end}[{i}] {a} (saved {b})"));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Saved again, it reads back the same.
|
||||
let again = solidcraft_engine::doc::Document::from_json(&s.doc.to_json()).unwrap();
|
||||
@@ -53,48 +94,40 @@ fn old_designs_open_upgrade_and_evaluate() {
|
||||
}
|
||||
|
||||
/// Names saved before format 14 were worked out from geometry, pieces of a split face numbered by
|
||||
/// where their centres lay (`F2:top#2`). They open marked as such and find no face or edge today:
|
||||
/// each feature holding one fails, asking for its reference to be picked again, and the rest of
|
||||
/// the design builds.
|
||||
/// where their centres lay (`F2:top#2`). They keep naming the faces they named: marked as such,
|
||||
/// they are found as the build that saved them found them, without new notes.
|
||||
#[test]
|
||||
fn names_saved_before_exact_history_ask_for_their_faces_again() {
|
||||
fn names_saved_before_exact_history_find_the_faces_they_named() {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus/naming-split-pieces.solidcraft");
|
||||
let s = open(&path);
|
||||
let feature = |name: &str| s.doc.features.iter().find(|f| f.name == name).unwrap_or_else(|| panic!("{name}"));
|
||||
assert_eq!(feature("Round").edge_names, vec!["?F2:side:l2|F2:top#2".to_string()]);
|
||||
assert_eq!(feature("Bevel").edge_names, vec!["?F2:side:l4|F2:top#1".to_string()]);
|
||||
assert_eq!(feature("Raise").face_names, vec!["?F2:top#1".to_string()]);
|
||||
let asked = |name: &str| {
|
||||
let r = s.model.result(feature(name).id).unwrap_or_else(|| panic!("{name}"));
|
||||
r.error.as_deref().and_then(solidcraft_engine::doc::reselect_parts).map(|(f, reference)| (f.to_string(), reference.to_string()))
|
||||
};
|
||||
assert_eq!(asked("Round"), Some(("Round".into(), "?F2:side:l2|F2:top#2".into())));
|
||||
assert_eq!(asked("Bevel"), Some(("Bevel".into(), "?F2:side:l4|F2:top#1".into())));
|
||||
assert_eq!(asked("Raise"), Some(("Raise".into(), "?F2:top#1".into())));
|
||||
for r in s.model.results.iter().filter(|r| !["Round", "Bevel", "Raise"].contains(&r.name.as_str())) {
|
||||
assert!(r.error.is_none(), "{}: {:?}", r.name, r.error);
|
||||
for r in &s.model.results {
|
||||
assert!(r.error.is_none() && r.warning.is_none(), "{}: {:?} {:?}", r.name, r.error, r.warning);
|
||||
}
|
||||
assert!(!s.model.state().bodies.is_empty());
|
||||
// The raised piece is the left one, as saved (the stored point is on it).
|
||||
let state = s.model.state();
|
||||
let solid = |p: [f64; 3]| state.bodies.iter().any(|b| b.body.contains(solidcraft_engine::geom::Vec3::new(p[0], p[1], p[2])));
|
||||
assert!(solid([7.0, 15.0, 11.0]) && !solid([32.0, 15.0, 11.0]));
|
||||
}
|
||||
|
||||
/// Body names saved before format 14 named the pieces of a split body in the kernel's shell
|
||||
/// order: such a design opens with every feature marked `shell_piece_order` and evaluates, each
|
||||
/// body name it stores naming a piece. (Which piece takes which name is not promised to be as it
|
||||
/// was saved.) Its fillet and chamfer hold edge names saved then, which find nothing today: they
|
||||
/// ask for their edges to be picked again.
|
||||
/// Body names saved before format 14 name the pieces of a split body in the kernel's shell order.
|
||||
/// The features after the split that refer to a piece by its body name still find that piece:
|
||||
/// the far one stays `Body1` (chamfered), the near one `Body1 (1)` (filleted, coloured).
|
||||
#[test]
|
||||
fn body_names_saved_before_exact_history_open_marked_and_evaluate() {
|
||||
fn body_names_saved_before_exact_history_keep_their_pieces() {
|
||||
let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus/split-body-pieces.solidcraft");
|
||||
let s = open(&path);
|
||||
assert!(s.doc.features.iter().all(|f| f.shell_piece_order), "every saved feature keeps the shell order");
|
||||
for r in &s.model.results {
|
||||
let asked = r.error.as_deref().and_then(solidcraft_engine::doc::reselect_parts);
|
||||
assert!(r.error.is_none() || asked.is_some_and(|(_, reference)| reference.starts_with('?')), "{}: {:?}", r.name, r.error);
|
||||
assert!(r.error.is_none() && r.warning.is_none(), "{}: {:?} {:?}", r.name, r.error, r.warning);
|
||||
}
|
||||
let state = s.model.state();
|
||||
for name in ["Body1", "Body1 (1)"] {
|
||||
assert!(state.bodies.iter().any(|b| b.name == name), "{name}");
|
||||
}
|
||||
let piece = |name: &str| state.bodies.iter().find(|b| b.name == name).unwrap_or_else(|| panic!("{name}"));
|
||||
let solid = |name: &str, p: [f64; 3]| piece(name).body.contains(solidcraft_engine::geom::Vec3::new(p[0], p[1], p[2]));
|
||||
assert!(solid("Body1", [20.0, 24.0, 5.0]) && solid("Body1 (1)", [20.0, 6.0, 5.0]));
|
||||
assert!(s.doc.appearances.bodies.contains_key("Body1 (1)"));
|
||||
}
|
||||
|
||||
@@ -140,7 +173,6 @@ fn flat_face_attachments() -> Vec<(String, String, usize, Option<(u32, bool)>, O
|
||||
for p in corpus() {
|
||||
let name = p.file_stem().unwrap_or_default().to_string_lossy().to_string();
|
||||
let mut s = open(&p);
|
||||
let failed_on_open: Vec<u64> = s.model.results.iter().filter(|r| r.error.is_some()).map(|r| r.id).collect();
|
||||
let state = s.model.state();
|
||||
let placed = s.placed_document();
|
||||
let mut picks = Vec::new();
|
||||
@@ -148,7 +180,7 @@ fn flat_face_attachments() -> Vec<(String, String, usize, Option<(u32, bool)>, O
|
||||
let Some(body) = state.bodies.iter().find(|b| b.definition_name() == definition && placed.model_body_component(b) == component) else {
|
||||
continue;
|
||||
};
|
||||
let mesh = body.body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.01)).unwrap();
|
||||
let mesh = body.body.tessellate(0.01).unwrap();
|
||||
for index in 0..body.body.face_count() {
|
||||
if body.body.planar_face(index).is_none() {
|
||||
continue;
|
||||
@@ -192,8 +224,7 @@ fn flat_face_attachments() -> Vec<(String, String, usize, Option<(u32, bool)>, O
|
||||
let known = known_without_attachment(&name, &body, normal, distance);
|
||||
out.push((name.clone(), body, index, known, problem));
|
||||
}
|
||||
let failed: Vec<u64> = s.model.results.iter().filter(|r| r.error.is_some()).map(|r| r.id).collect();
|
||||
assert_eq!(failed, failed_on_open, "{name}: undo leaves the features that failed on opening, and only those, failed");
|
||||
assert!(s.model.results.iter().all(|r| r.error.is_none()), "{name}: left with errors after undo");
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ fn model(name: &str, s: &Session) -> Value {
|
||||
.iter()
|
||||
.map(|r| json!({"id": r.id, "name": r.name, "error": r.error, "warning": r.warning, "skipped": r.skipped, "state": state(name, &r.state)}))
|
||||
.collect();
|
||||
json!({"results": results, "world": state(name, &s.world_state().unwrap())})
|
||||
json!({"results": results, "world": state(name, &s.world_state())})
|
||||
}
|
||||
|
||||
/// The same edit on both: a parameter's value when the design has one, else a new body.
|
||||
@@ -289,60 +289,6 @@ fn undoing_an_edit_after_opening_reuses_the_saved_results() {
|
||||
let _ = std::fs::remove_file(format!("{}.bak", path.display()));
|
||||
}
|
||||
|
||||
/// A saved model holds the results of the body check mode it was evaluated under: opened under
|
||||
/// the same mode it is taken as it is, under another the design is evaluated again.
|
||||
#[test]
|
||||
fn a_model_saved_under_another_check_mode_is_evaluated_again() {
|
||||
let plate = solidcraft_engine::sample::samples().into_iter().find(|s| s.name == "Sample Plate").unwrap();
|
||||
let mut built = Session::default();
|
||||
for c in (plate.script)()["commands"].as_array().unwrap() {
|
||||
built.execute(c["command"].as_str().unwrap(), &c["params"]).unwrap();
|
||||
}
|
||||
let path = temp("mode");
|
||||
built.execute("file.save_as", &json!({"path": path.to_string_lossy()})).unwrap();
|
||||
let same = open(&path);
|
||||
assert_eq!(same.model.validation(), built.model.validation());
|
||||
assert_eq!(same.model.recomputed, 0, "taken from the file");
|
||||
let mut other = Session::default();
|
||||
other.set_validation(solidcraft_engine::kernel::Validation::EachStep);
|
||||
assert_ne!(other.validation(), built.model.validation());
|
||||
other.execute("doc.open", &json!({"path": path.to_string_lossy()})).unwrap();
|
||||
assert_eq!(other.validation(), solidcraft_engine::kernel::Validation::EachStep);
|
||||
assert!(other.model.recomputed > 0, "evaluated again under its own mode");
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let _ = std::fs::remove_file(format!("{}.bak", path.display()));
|
||||
}
|
||||
|
||||
/// A body made by rounds keeps, in the saved model, the rounds and the body they were made on: a
|
||||
/// later round at the corner where they meet blends them again with it whether the design was
|
||||
/// opened from its model or evaluated anew. Two edges of a 20 mm box rounded r2 (a mitre at the
|
||||
/// corner (20, 0, 20)), saved; opened, the upright edge there rounded r3: the corner all three
|
||||
/// rounded at once make (SMLib's setback corner, kernel `three_rounds_at_a_corner`), 15 faces and
|
||||
/// 7928.142563938 mm³, the same body as the design evaluated anew.
|
||||
#[test]
|
||||
fn a_later_round_after_opening_blends_the_saved_rounds_again() {
|
||||
let mut built = Session::default();
|
||||
built.execute("solid.box", &json!({"length": 20, "width": 20, "height": 20, "body_name": "Box"})).unwrap();
|
||||
built.execute("solid.fillet", &json!({"edges": [[20, 10, 20], [10, 0, 20]], "radius": 2})).unwrap();
|
||||
let path = temp("later-round");
|
||||
built.execute("file.save_as", &json!({"path": path.to_string_lossy()})).unwrap();
|
||||
let bytes = std::fs::read(&path).unwrap();
|
||||
let mut cached = open(&path);
|
||||
assert_eq!(cached.model.last_recomputed, 0, "opened without evaluating");
|
||||
let mut rebuilt = Session::new(solidcraft_engine::io::read_design(&bytes).unwrap());
|
||||
for s in [&mut cached, &mut rebuilt] {
|
||||
s.execute("solid.fillet", &json!({"edges": [[20, 0, 10]], "radius": 3})).unwrap();
|
||||
let st = s.model.state();
|
||||
let b = &st.body("Box").unwrap().body;
|
||||
let v = solidcraft_engine::kernel::measure(b).unwrap().volume;
|
||||
assert_eq!(b.face_count(), 15);
|
||||
assert!((v - 7928.142563938).abs() < 1e-6, "{v}");
|
||||
}
|
||||
assert_eq!(model("later round", &cached), model("later round", &rebuilt));
|
||||
let _ = std::fs::remove_file(&path);
|
||||
let _ = std::fs::remove_file(format!("{}.bak", path.display()));
|
||||
}
|
||||
|
||||
/// A design saved before body names were pinned (#6403) pins them as it opens, as its first
|
||||
/// command would, and opening it is no change. That command pinned them as part of itself, so
|
||||
/// undoing it went back to the unpinned design, which no evaluated result matched: the first
|
||||
|
||||
@@ -1,404 +0,0 @@
|
||||
//! Every design of the corpus loads, two builds of it name its faces and edges alike, and a
|
||||
//! name it stores that no longer finds its face or edge fails its feature. The stored names are
|
||||
//! the fillet and chamfer edges, the faces a feature picks, the face a sketch sits on and the
|
||||
//! sheet metal edges; one that finds nothing (`gone`, a sketch's face `lost`) must leave its
|
||||
//! feature failed (it asks for the reference to be picked again, never takes another face or
|
||||
//! edge), and a design that no longer loads fails the test. A build's names are each face's and
|
||||
//! edge's name with where it is (its area and centre, its length and middle); the second build
|
||||
//! must give the same, and its stored names must find the same faces and edges.
|
||||
//!
|
||||
//! Face and edge indices are left out: they are the kernel's order, which a rebuild need not
|
||||
//! keep (a fillet's or a boolean's faces come out in another order from one build to the next).
|
||||
//! How many faces a body has and what each is called is the kernel's business, not this test's.
|
||||
//!
|
||||
//! The corpus is the action matrix's (the old design files in `tests/corpus`, the examples, the
|
||||
//! demo and typical designs in `tests/action_matrix/designs`, the reference recipes), the sample
|
||||
//! designs, the engine's design fixtures and the older designs kept by the hole matrix, taper
|
||||
//! and projection tests.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::{FaceAttachmentStatus, Feature, FeatureKind, ModelBody, ModelState, PlaneRef, naming};
|
||||
|
||||
fn root() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
||||
}
|
||||
|
||||
/// Where a design comes from.
|
||||
enum Source {
|
||||
File(PathBuf),
|
||||
Sample(fn() -> Value),
|
||||
}
|
||||
|
||||
/// Every design of the corpus: (`<group>/<design>`, where it comes from).
|
||||
fn designs() -> Vec<(String, Source)> {
|
||||
let root = root();
|
||||
let mut out = Vec::new();
|
||||
let mut add = |prefix: &str, dir: PathBuf, ext: &str| {
|
||||
let Ok(rd) = std::fs::read_dir(&dir) else { return };
|
||||
let mut v: Vec<PathBuf> = rd.flatten().map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == ext)).collect();
|
||||
v.sort();
|
||||
for p in v {
|
||||
let stem = p.file_stem().unwrap_or_default().to_string_lossy().to_string();
|
||||
out.push((format!("{prefix}/{stem}"), Source::File(p)));
|
||||
}
|
||||
};
|
||||
add("corpus", root.join("apps/solidcraft-cli/tests/corpus"), "solidcraft");
|
||||
add("examples", root.join("examples"), "json");
|
||||
add("examples", root.join("examples/acceptance"), "json");
|
||||
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");
|
||||
add("fixtures", root.join("crates/engine/src/tests/fixtures"), "solidcraft");
|
||||
add("hole_matrix", root.join("tests/hole_matrix/old-0.9"), "json");
|
||||
add("taper", root.join("tests/taper/before-1014"), "json");
|
||||
add("projection", root.join("tests/projection/before-1508"), "json");
|
||||
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();
|
||||
for c in cases {
|
||||
out.push((format!("oracle/{}", c.file_name().unwrap_or_default().to_string_lossy()), Source::File(c.join("recipe.json"))));
|
||||
}
|
||||
for sample in solidcraft_engine::sample::samples() {
|
||||
let stem: String = sample.name.to_lowercase().split_whitespace().collect::<Vec<_>>().join("-");
|
||||
out.push((format!("samples/{stem}"), Source::Sample(sample.script)));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// Build a design: a design file (also a design saved as JSON) is opened, a script is run.
|
||||
fn load(source: &Source) -> Result<Session, String> {
|
||||
let mut s = Session::default();
|
||||
let script = match source {
|
||||
Source::Sample(script) => script(),
|
||||
Source::File(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);
|
||||
}
|
||||
Source::File(path) if path.file_name().is_some_and(|n| n == "recipe.json") => {
|
||||
let out = Command::new(env!("CARGO_BIN_EXE_solidcraft-cli")).arg("recipe").arg(path).output().map_err(|e| e.to_string())?;
|
||||
if !out.status.success() {
|
||||
return Err(format!("recipe: {}", String::from_utf8_lossy(&out.stderr).trim()));
|
||||
}
|
||||
serde_json::from_slice(&out.stdout).map_err(|e| e.to_string())?
|
||||
}
|
||||
Source::File(path) => {
|
||||
let v: Value = serde_json::from_slice(&std::fs::read(path).map_err(|e| e.to_string())?).map_err(|e| e.to_string())?;
|
||||
if v.get("features").is_some() {
|
||||
s.execute("doc.open", &json!({"path": path.to_string_lossy()})).map_err(|e| e.to_string())?;
|
||||
return Ok(s);
|
||||
}
|
||||
match v.get("steps").and_then(Value::as_array) {
|
||||
// The demo's steps: an MCP `execute` each.
|
||||
Some(steps) => {
|
||||
json!({"commands": steps.iter().map(|st| json!({"command": st["command"], "params": st["params"]})).collect::<Vec<_>>()})
|
||||
}
|
||||
None => v,
|
||||
}
|
||||
}
|
||||
};
|
||||
s.run_script(&script).map_err(|e| e.to_string())?;
|
||||
if s.active_sketch.is_some() {
|
||||
let _ = s.execute("sketch.finish", &json!({}));
|
||||
}
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
/// What decides a name beyond its text: `[geometric]` when it was worked out from geometry, not
|
||||
/// exact history.
|
||||
fn flags(list: &[(bool, &str)]) -> String {
|
||||
let set: Vec<&str> = list.iter().filter(|(on, _)| *on).map(|(_, f)| *f).collect();
|
||||
if set.is_empty() { String::new() } else { format!(" [{}]", set.join(", ")) }
|
||||
}
|
||||
|
||||
/// Where each named face and edge of a body is, by name, sorted: a face's area and centre, an
|
||||
/// edge's length and middle, from the body's mesh. Two builds that name their faces alike give
|
||||
/// the same list.
|
||||
fn placed_names(b: &ModelBody) -> Vec<(String, [f64; 4])> {
|
||||
if b.body.is_mesh() {
|
||||
return Vec::new();
|
||||
}
|
||||
let m = b.mesh();
|
||||
let faces = naming::face_names(b);
|
||||
let mut sums = vec![[0.0f64; 4]; faces.len()];
|
||||
for (t, f) in m.triangles.iter().zip(&m.tri_face) {
|
||||
let [p, q, r] = t.map(|i| m.positions.get(i as usize).copied().unwrap_or(Vec3::ZERO));
|
||||
let area = (q - p).cross(r - p).len() / 2.0;
|
||||
let c = (p + q + r) / 3.0;
|
||||
if let Some(s) = sums.get_mut(*f as usize) {
|
||||
*s = [s[0] + area, s[1] + area * c.x, s[2] + area * c.y, s[3] + area * c.z];
|
||||
}
|
||||
}
|
||||
let mut out: Vec<(String, [f64; 4])> = faces
|
||||
.iter()
|
||||
.zip(&sums)
|
||||
.map(|(n, s)| {
|
||||
let a = s[0].max(1e-300);
|
||||
(format!("{} face {n}", b.name), [s[0], s[1] / a, s[2] / a, s[3] / a])
|
||||
})
|
||||
.collect();
|
||||
for (n, line) in naming::edge_names(b).iter().zip(&m.edges) {
|
||||
let length: f64 = line.windows(2).map(|w| w[1].dist(w[0])).sum();
|
||||
let mid = line.iter().fold(Vec3::ZERO, |acc, p| acc + *p) / (line.len().max(1) as f64);
|
||||
out.push((format!("{} edge {n}", b.name), [length, mid.x, mid.y, mid.z]));
|
||||
}
|
||||
out.sort_by(|x, y| x.0.cmp(&y.0).then(x.1.partial_cmp(&y.1).unwrap_or(std::cmp::Ordering::Equal)));
|
||||
out
|
||||
}
|
||||
|
||||
fn face_label(st: &ModelState, body: usize, face: usize) -> String {
|
||||
match st.bodies.get(body) {
|
||||
Some(b) => format!("{} face {}", b.name, naming::face_names(b).get(face).map(String::as_str).unwrap_or("?")),
|
||||
None => format!("body #{body} face #{face}"),
|
||||
}
|
||||
}
|
||||
|
||||
/// Every name feature `f` stores, with what it finds in the model before it, as a feature
|
||||
/// rebuilding finds it, and whether it finds anything.
|
||||
fn record_refs(out: &mut Vec<(String, bool)>, s: &Session, f: &Feature) {
|
||||
let doc = &*s.doc;
|
||||
let who = format!("ref {} ({})", f.name, f.id);
|
||||
let before = s.model.state_before(f.id);
|
||||
// Faces a feature picks by point and stores by name (shell, draft, hole, …).
|
||||
let explicit_emboss = matches!(&f.kind, FeatureKind::Emboss { face: Some(_), .. });
|
||||
if f.face_names.iter().any(|n| !n.is_empty()) {
|
||||
let fst = solidcraft_engine::doc::face_point_state(doc, &before, &f.kind, f.component).unwrap();
|
||||
for (k, (p, n)) in f.kind.face_points().into_iter().zip(&f.face_names).enumerate() {
|
||||
if n.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let (found, ok) = if explicit_emboss {
|
||||
("not checked (a cylindrical emboss finds its face on its own body)".to_string(), true)
|
||||
} else {
|
||||
match naming::locate_face_on_body(&fst, f.kind.face_body(), n, p) {
|
||||
Some(hit) => (format!("{}{}", face_label(&fst, hit.body, hit.face), flags(&[(!hit.verified, "geometric")])), true),
|
||||
None => ("gone".into(), false),
|
||||
}
|
||||
};
|
||||
out.push((format!("{who} face {k} `{n}` -> {found}"), ok));
|
||||
}
|
||||
}
|
||||
// Edges a feature picks by point and stores by name.
|
||||
if f.edge_names.iter().any(|n| !n.is_empty()) {
|
||||
let edge = |out: &mut Vec<(String, bool)>, b: &ModelBody, k: usize, n: &str, p: Vec3| {
|
||||
let (found, ok) = match naming::locate_edge(b, n, p) {
|
||||
Ok(hit) => (
|
||||
format!(
|
||||
"{} edge {}{}",
|
||||
b.name,
|
||||
naming::edge_names(b).get(hit.edge).map(String::as_str).unwrap_or("?"),
|
||||
flags(&[(!hit.verified, "geometric")])
|
||||
),
|
||||
true,
|
||||
),
|
||||
Err(e) => (format!("gone ({e})"), false),
|
||||
};
|
||||
out.push((format!("{who} edge {k} `{n}` -> {found}"), ok));
|
||||
};
|
||||
let named = |k: usize| f.edge_names.get(k).filter(|n| !n.is_empty());
|
||||
match &f.kind {
|
||||
FeatureKind::Fillet { edges, body, .. } | FeatureKind::Chamfer { edges, body, .. } => {
|
||||
let pts: Vec<Vec3> = solidcraft_engine::doc::plain(edges);
|
||||
match solidcraft_engine::doc::blend_target(&before, body, &pts).ok().and_then(|i| before.bodies.get(i)) {
|
||||
Some(b) => {
|
||||
for (k, p) in pts.iter().enumerate() {
|
||||
if let Some(n) = named(k) {
|
||||
edge(out, b, k, n, *p);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => out.push((format!("{who} edges: no body to round"), false)),
|
||||
}
|
||||
}
|
||||
FeatureKind::SheetConvert { body, bends, .. } => match before.body(body) {
|
||||
Some(b) => {
|
||||
for (k, p) in solidcraft_engine::doc::plain::<Vec3, _>(bends).iter().enumerate() {
|
||||
if let Some(n) = named(k) {
|
||||
edge(out, b, k, n, *p);
|
||||
}
|
||||
}
|
||||
}
|
||||
None => out.push((format!("{who} bends: body `{body}` gone"), false)),
|
||||
},
|
||||
// Sheet metal edges are named by the sheet's panels, and found on its sheets.
|
||||
FeatureKind::SheetFlange { .. } | FeatureKind::SheetHem { .. } => {
|
||||
let pts: Vec<Vec3> = match &f.kind {
|
||||
FeatureKind::SheetFlange { edges, .. } => edges.iter().map(|e| e.point.0).collect(),
|
||||
FeatureKind::SheetHem { edges, .. } => solidcraft_engine::doc::plain(edges),
|
||||
_ => Vec::new(),
|
||||
};
|
||||
for (k, p) in pts.iter().enumerate() {
|
||||
let Some(n) = named(k) else { continue };
|
||||
let on: Vec<&str> = before.sheets.iter().filter(|sh| sh.edge_by_name(n, *p).is_some()).map(|sh| sh.body.as_str()).collect();
|
||||
let found = if on.is_empty() { "gone".to_string() } else { format!("sheet edge on {}", on.join(", ")) };
|
||||
out.push((format!("{who} sheet edge {k} `{n}` -> {found}"), !on.is_empty()));
|
||||
}
|
||||
}
|
||||
other => {
|
||||
for (k, n) in f.edge_names.iter().enumerate().filter(|(_, n)| !n.is_empty()) {
|
||||
out.push((format!("{who} edge {k} `{n}` -> not checked ({})", other.type_name()), true));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// The face a sketch sits on.
|
||||
if let FeatureKind::Sketch { plane: PlaneRef::Face { name, anchor, at, .. }, .. } = &f.kind {
|
||||
let mut ok = true;
|
||||
let status = match doc.sketch_face_attachment(f.id, &s.model) {
|
||||
FaceAttachmentStatus::Healthy { target, .. } => {
|
||||
let faces = match before.body(&target.body) {
|
||||
Some(b) => match doc.anchored_face_indices(&before, b, &target) {
|
||||
Ok((indices, note)) => {
|
||||
let names = naming::face_names(b);
|
||||
let mut list: Vec<&str> = indices.iter().map(|i| names.get(*i).map(String::as_str).unwrap_or("?")).collect();
|
||||
list.sort();
|
||||
format!("{} faces {}{}", b.name, list.join(", "), flags(&[(note.is_some(), "changed cap")]))
|
||||
}
|
||||
Err(e) => {
|
||||
ok = false;
|
||||
format!("no faces ({e})")
|
||||
}
|
||||
},
|
||||
None => {
|
||||
ok = false;
|
||||
format!("body `{}` gone", target.body)
|
||||
}
|
||||
};
|
||||
format!("healthy: {faces}")
|
||||
}
|
||||
FaceAttachmentStatus::Lost { message, .. } => {
|
||||
ok = false;
|
||||
format!("lost: {message}")
|
||||
}
|
||||
FaceAttachmentStatus::Legacy { .. } => match name {
|
||||
// The face its name finds, as the sketch finds it.
|
||||
Some(n) => {
|
||||
let fst = solidcraft_engine::doc::state_in_frame(doc, &before, f.component).unwrap();
|
||||
match naming::locate_face(&fst, n, *at) {
|
||||
Some(hit) => format!("by name: {}{}", face_label(&fst, hit.body, hit.face), flags(&[(!hit.verified, "geometric")])),
|
||||
None => {
|
||||
ok = false;
|
||||
"by name: gone".into()
|
||||
}
|
||||
}
|
||||
}
|
||||
None => "by its pick".into(),
|
||||
},
|
||||
FaceAttachmentStatus::Sheet { .. } => "sheet face".into(),
|
||||
FaceAttachmentStatus::Fixed { .. } => "fixed".into(),
|
||||
};
|
||||
let stored = match (anchor, name) {
|
||||
(Some(a), _) => {
|
||||
let kind =
|
||||
a.target.certificate.as_ref().and_then(|c| serde_json::to_value(c).ok()).and_then(|v| v["kind"].as_str().map(String::from));
|
||||
format!("`{}` on {} ({})", a.target.name, a.target.body, kind.unwrap_or_else(|| "no certificate".into()))
|
||||
}
|
||||
(None, Some(n)) => format!("`{n}`"),
|
||||
(None, None) => "by its pick".into(),
|
||||
};
|
||||
out.push((format!("{who} plane {stored} -> {status}"), ok));
|
||||
}
|
||||
}
|
||||
|
||||
/// A build of a design: where its faces and edges are by name, and what every name it stores
|
||||
/// finds.
|
||||
struct Build {
|
||||
names: Vec<(String, [f64; 4])>,
|
||||
refs: Vec<(String, bool)>,
|
||||
}
|
||||
|
||||
fn build(s: &Session) -> Build {
|
||||
let names = s.model.state().bodies.iter().flat_map(placed_names).collect();
|
||||
let mut refs = Vec::new();
|
||||
for f in s.doc.features.iter().filter(|f| !f.suppressed) {
|
||||
let from = refs.len();
|
||||
record_refs(&mut refs, s, f);
|
||||
// A name that finds nothing is sound when its feature fails: it asks for the reference
|
||||
// to be picked again.
|
||||
if s.model.result(f.id).is_some_and(|r| r.error.is_some()) {
|
||||
refs.iter_mut().skip(from).for_each(|(_, ok)| *ok = true);
|
||||
}
|
||||
}
|
||||
Build { names, refs }
|
||||
}
|
||||
|
||||
/// Where two builds name their faces and edges apart (at most `cap` of them): a name only one
|
||||
/// has, or a face or edge of that name somewhere else (1e-6 of its size, to the mesh's rounding).
|
||||
fn differences(a: &Build, b: &Build, cap: usize) -> Vec<String> {
|
||||
let mut out = Vec::new();
|
||||
if a.names.len() != b.names.len() {
|
||||
out.push(format!("{} named faces and edges, then {}", a.names.len(), b.names.len()));
|
||||
}
|
||||
for ((n, x), (m, y)) in a.names.iter().zip(&b.names) {
|
||||
let scale = x.iter().chain(y).fold(1.0f64, |s, v| s.max(v.abs()));
|
||||
if n != m || x.iter().zip(y).any(|(p, q)| (p - q).abs() > 1e-6 * scale) {
|
||||
out.push(format!("{n} at {x:?}, then {m} at {y:?}"));
|
||||
}
|
||||
}
|
||||
for ((r, _), (q, _)) in a.refs.iter().zip(&b.refs) {
|
||||
if r != q {
|
||||
out.push(format!("{r}, then {q}"));
|
||||
}
|
||||
}
|
||||
if a.refs.len() != b.refs.len() {
|
||||
out.push(format!("{} stored names, then {}", a.refs.len(), b.refs.len()));
|
||||
}
|
||||
out.truncate(cap);
|
||||
out
|
||||
}
|
||||
|
||||
/// Designs whose names differ from one build to the next today: (design, why). Their
|
||||
/// differences are printed, not failed on, until the cause is fixed; a stored name that finds
|
||||
/// nothing still fails.
|
||||
const UNSTABLE: [(&str, &str); 1] = [(
|
||||
"regressions/closed_sweep_torus_cut",
|
||||
"the halves of each swept wall lie level along every axis, so which is `#1` and which `#2` follows the order the kernel gives the faces in, which changes from one build to the next",
|
||||
)];
|
||||
|
||||
#[test]
|
||||
fn corpus_designs_name_alike_every_build_and_fail_where_a_reference_is_gone() {
|
||||
let designs = designs();
|
||||
let next = std::sync::atomic::AtomicUsize::new(0);
|
||||
let results = std::sync::Mutex::new(Vec::new());
|
||||
let workers = std::thread::available_parallelism().map_or(4, |n| n.get()).min(designs.len().max(1));
|
||||
std::thread::scope(|scope| {
|
||||
for _ in 0..workers {
|
||||
scope.spawn(|| {
|
||||
while let Some((name, source)) = designs.get(next.fetch_add(1, std::sync::atomic::Ordering::Relaxed)) {
|
||||
let got = load(source).and_then(|first| Ok((build(&first), build(&load(source)?))));
|
||||
results.lock().unwrap_or_else(|e| e.into_inner()).push((name.clone(), got));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
let mut results = results.into_inner().unwrap_or_else(|e| e.into_inner());
|
||||
results.sort_by(|a, b| a.0.cmp(&b.0));
|
||||
let mut problems = Vec::new();
|
||||
for (name, got) in &results {
|
||||
let (first, second) = match got {
|
||||
Ok(builds) => builds,
|
||||
Err(e) => {
|
||||
problems.push(format!("{name}: does not load: {e}"));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let lost: Vec<&str> = first.refs.iter().filter(|(_, ok)| !ok).map(|(r, _)| r.as_str()).collect();
|
||||
if !lost.is_empty() {
|
||||
problems.push(format!("{name}: stored names that find nothing, in features that build:\n {}", lost.join("\n ")));
|
||||
}
|
||||
let apart = differences(first, second, 40);
|
||||
if !apart.is_empty() {
|
||||
match UNSTABLE.iter().find(|(design, _)| design == name) {
|
||||
Some((_, why)) => eprintln!("{name} (not checked: {why}):\n {}", apart.join("\n ")),
|
||||
None => problems.push(format!("{name}: two builds name it apart (first, then second):\n {}", apart.join("\n "))),
|
||||
}
|
||||
}
|
||||
}
|
||||
assert!(problems.is_empty(), "{} of {} design(s) of the corpus:\n{}", problems.len(), results.len(), problems.join("\n"));
|
||||
}
|
||||
@@ -9,7 +9,6 @@ use std::path::{Path, PathBuf};
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::kernel::{FaceSurface, face_surface};
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
|
||||
fn root() -> PathBuf {
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("../..").canonicalize().unwrap()
|
||||
@@ -32,7 +31,7 @@ struct Topology {
|
||||
|
||||
/// Every solid body's topology, by name.
|
||||
fn topology(s: &Session) -> BTreeMap<String, Topology> {
|
||||
let st = s.world_state().unwrap();
|
||||
let st = s.world_state();
|
||||
st.bodies
|
||||
.iter()
|
||||
.filter(|b| !b.body.is_mesh())
|
||||
@@ -143,14 +142,16 @@ fn a_parameter_edit_rebuilds_like_a_fresh_build_and_reverts_exactly() {
|
||||
assert!(problems.is_empty(), "{}", problems.join("\n"));
|
||||
}
|
||||
|
||||
/// The README demo's Raspberry Pi case (`examples/demo/enclosure_ui_mcp.py --story pi`): after
|
||||
/// each feature, each outer side (the four walls and the bottom) is one face and the case is as
|
||||
/// tall as its height, and a height changed in Parameters rebuilds the same topology as one
|
||||
/// built at that height: (a) built at height 24, (b) built at 24 and changed to 32, (c) built at
|
||||
/// 32. The port cut-outs and the floor vents used to split the long wall (or the end wall at 24)
|
||||
/// into strips along the openings' tops and bottoms.
|
||||
/// The README demo's Raspberry Pi case (`examples/demo/enclosure_ui_mcp.py --story pi`): its
|
||||
/// face count after each feature, and each outer side (the four walls and the bottom) one face
|
||||
/// throughout: (a) built at height 24, (b) built at 24 and changed to 32 in Parameters, (c)
|
||||
/// built at 32. The port cut-outs and the floor vents used to split the long wall (or the end
|
||||
/// wall at 24) into strips along the openings' tops and bottoms.
|
||||
#[test]
|
||||
fn pi_case_sides_stay_whole_at_both_heights() {
|
||||
fn pi_case_faces_per_step_at_both_heights() {
|
||||
// Sketch1 (1) .. RectangularPattern2 (14): the faces once features 1..=k are built.
|
||||
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 at = |height: &str, file: &str| {
|
||||
@@ -173,13 +174,12 @@ fn pi_case_sides_stay_whole_at_both_heights() {
|
||||
for (case, s, h) in [("(a) at 24", a, 24.), ("(b) 24 changed to 32", b, 32.), ("(c) at 32", c, 32.)] {
|
||||
assert!(errors(&s).is_empty(), "{case}: features fail: {:?}", errors(&s));
|
||||
let mut steps = Vec::new();
|
||||
// Sketch1 (1) is no body; the box (2) .. RectangularPattern2 (14).
|
||||
for k in 2..=14 {
|
||||
for (k, want) in FACES {
|
||||
let mut t = s.scratch();
|
||||
t.execute("timeline.rollback", &json!({"position": k})).unwrap();
|
||||
let st = t.world_state().unwrap();
|
||||
let st = t.world_state();
|
||||
let body = st.bodies.iter().find(|b| b.name == "Body1").unwrap();
|
||||
assert!(body.body.validity().is_empty(), "{case}: after feature {k}: {:?}", body.body.validity());
|
||||
assert_eq!(body.body.face_count(), want, "{case}: faces after feature {k}");
|
||||
// Each outer side: the faces in its plane.
|
||||
for (n, d) in [([0., -1., 0.], 33.), ([0., 1., 0.], 33.), ([-1., 0., 0.], 47.5), ([1., 0., 0.], 47.5), ([0., 0., -1.], 0.)] {
|
||||
let on = (0..body.body.face_count())
|
||||
|
||||
@@ -465,11 +465,7 @@ fn snapshot_runs_its_script_on_the_in_design() {
|
||||
fn unknown_command_suggests_the_closest_id() {
|
||||
let output = cli().args(["exec", "file.open"]).output().unwrap();
|
||||
assert_eq!(output.status.code(), Some(1));
|
||||
assert!(
|
||||
String::from_utf8_lossy(&output.stderr).contains("did you mean `doc.open` or `panel.open`?"),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
assert!(String::from_utf8_lossy(&output.stderr).contains("did you mean `doc.open`?"), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
}
|
||||
|
||||
/// #1833: `--timings` reports each command's time instead of the measurement, `--profile` adds
|
||||
|
||||
@@ -65,10 +65,7 @@ fn allow_errors_writes_anyway_and_a_saved_design_keeps_failing() {
|
||||
let output = cli().arg("run").arg(dir.broken_script()).arg("--allow-errors").arg("--out").arg(&step).arg("--save").arg(&saved).output().unwrap();
|
||||
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
let measured: Value = serde_json::from_slice(&output.stdout).unwrap();
|
||||
// SMLib integrates the volume (a relative accuracy of 1e-4 asked for; a box comes out within
|
||||
// a few units in the last place), so the box's 4000 mm³ is checked to 1e-9, not to the bit.
|
||||
let volume = measured["total"]["volume_mm3"].as_f64().unwrap();
|
||||
assert!((volume - 4000.0).abs() < 4000.0 * 1e-9, "the box without its fillet: {volume}");
|
||||
assert_eq!(measured["total"]["volume_mm3"], json!(4000.0), "the box without its fillet");
|
||||
assert!(std::fs::metadata(&step).unwrap().len() > 0);
|
||||
// The saved design still holds the failing feature: running it fails too, `eval` reports it.
|
||||
let again = dir.0.join("again.step");
|
||||
|
||||
@@ -171,9 +171,9 @@ fn a_saved_view_is_its_camera_and_a_standard_view_is_unchanged() {
|
||||
let top = dir.snapshot(&script, "top.png", &["--view", "top"]);
|
||||
let mut s = Session::default();
|
||||
s.run_script(&serde_json::from_slice(&std::fs::read(&script).unwrap()).unwrap()).unwrap();
|
||||
let mut cam = solidcraft_engine::view::home_camera(&s).unwrap();
|
||||
let mut cam = solidcraft_engine::view::home_camera(&s);
|
||||
cam.set_view(solidcraft_engine::render::StandardView::Top);
|
||||
let scene = solidcraft_engine::view::scene(&s, &cam).unwrap();
|
||||
let scene = solidcraft_engine::view::scene(&s, &cam);
|
||||
assert_same(&top, &solidcraft_engine::capture::png(&scene, &cam, SIZE.0, SIZE.1).unwrap().bytes, "the home camera turned to the top");
|
||||
assert_same(&top, &dir.mcp(&script, "mcp-top.png", json!({"view": "top"})), "the MCP top view");
|
||||
assert_differs(&top, &saved, "top and the saved view");
|
||||
@@ -288,7 +288,7 @@ fn a_snapshot_shows_the_part_where_the_camera_puts_it() {
|
||||
let vp = c.view_proj(f64::from(SIZE.0) / f64::from(SIZE.1), 1000.0);
|
||||
let (mut elo, mut ehi) = ([f64::MAX; 2], [f64::MIN; 2]);
|
||||
for k in 0..8 {
|
||||
let p = solidcore_record::Vec3::new(
|
||||
let p = solidcraft_engine::geom::Vec3::new(
|
||||
corner[0] + size[0] * f64::from(k & 1),
|
||||
corner[1] + size[1] * f64::from((k >> 1) & 1),
|
||||
corner[2] + size[2] * f64::from((k >> 2) & 1),
|
||||
@@ -335,9 +335,9 @@ fn a_forced_cpu_snapshot_is_the_cpu_rasterizers_and_says_so() {
|
||||
assert!(stderr.contains("CPU rasterizer"), "{stderr}");
|
||||
let mut s = Session::default();
|
||||
s.run_script(&serde_json::from_slice(&std::fs::read(&script).unwrap()).unwrap()).unwrap();
|
||||
let mut cam = solidcraft_engine::view::home_camera(&s).unwrap();
|
||||
let mut cam = solidcraft_engine::view::home_camera(&s);
|
||||
cam.set_view(solidcraft_engine::render::StandardView::Top);
|
||||
let scene = solidcraft_engine::view::scene(&s, &cam).unwrap();
|
||||
let scene = solidcraft_engine::view::scene(&s, &cam);
|
||||
let cpu = solidcraft_engine::render::render_png(&scene, &cam, SIZE.0 as usize, SIZE.1 as usize).unwrap();
|
||||
assert_eq!(std::fs::read(dir.0.join("cpu.png")).unwrap(), cpu);
|
||||
}
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
//! vertices nothing else used, so an edge that is one straight line was drawn (and lit on hover)
|
||||
//! as many pieces, and a sketch on such a face projected every piece.
|
||||
|
||||
use solidcraft_engine::geom::prelude::*;
|
||||
use std::collections::HashMap;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use serde_json::json;
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::Session;
|
||||
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")
|
||||
@@ -49,11 +48,11 @@ fn key(v: Vec3) -> [i64; 3] {
|
||||
fn straight_edges_are_one_edge_after_many_cuts() {
|
||||
for (what, s) in [("as saved", open(&design())), ("built from scratch", rebuilt())] {
|
||||
assert_eq!(errors(&s), vec![], "{what}: every feature evaluates");
|
||||
let st = s.world_state().unwrap();
|
||||
let st = s.world_state();
|
||||
let bodies: Vec<_> = st.bodies.iter().filter(|b| !b.body.is_mesh()).collect();
|
||||
assert_eq!(bodies.len(), 1, "{what}: one body");
|
||||
let body = &bodies[0].body;
|
||||
let mesh = body.mesh(&solidcraft_kernel::MeshTolerance::chord(0.01)).unwrap();
|
||||
let mesh = body.tessellate(0.01).unwrap();
|
||||
// Every edge is drawn as one polyline; a straight one as one segment.
|
||||
let mut at: HashMap<[i64; 3], Vec<usize>> = HashMap::new();
|
||||
for (i, e) in mesh.edges.iter().enumerate() {
|
||||
|
||||
@@ -14,18 +14,14 @@ name = "solidcraft-web"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["profile"]
|
||||
default = []
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
# The profiler, compiled in so any build can say where its time goes (the Profile operations
|
||||
# preference, SOLIDCRAFT_PROFILE=1, diagnostics.profile); it records nothing until asked.
|
||||
profile = ["solidcraft-engine/profile"]
|
||||
|
||||
[dependencies]
|
||||
solidcraft-engine = { workspace = true }
|
||||
solidcraft-ui-egui = { workspace = true }
|
||||
egui = { workspace = true }
|
||||
log = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
url = "2"
|
||||
percent-encoding = "2"
|
||||
|
||||
@@ -34,9 +30,10 @@ eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit"] }
|
||||
wasm-bindgen = "0.2"
|
||||
wasm-bindgen-futures = "0.4"
|
||||
js-sys = "0.3"
|
||||
serde_json = { workspace = true }
|
||||
web-sys = { version = "0.3", features = [
|
||||
"Window", "Document", "Element", "HtmlElement", "HtmlCanvasElement", "Location", "Navigator", "StorageManager",
|
||||
"FileSystemHandle", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemGetFileOptions", "FileSystemGetDirectoryOptions", "FileSystemWritableFileStream",
|
||||
"FileSystemHandle", "FileSystemDirectoryHandle", "FileSystemFileHandle", "FileSystemGetFileOptions", "FileSystemWritableFileStream",
|
||||
"WritableStream", "File", "FileList", "Blob", "BlobPropertyBag", "Url", "HtmlAnchorElement", "HtmlInputElement", "Storage", "StorageEvent",
|
||||
"IdbFactory", "IdbDatabase", "IdbObjectStore", "IdbOpenDbRequest", "IdbRequest", "IdbTransaction", "IdbTransactionMode",
|
||||
"Headers", "ReadableStream", "ReadableStreamDefaultReader",
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
//! Links the browser build with an 8 MiB stack: solidcore's kernel algorithms (subdivision,
|
||||
//! intersection, filleting) recurse legitimately, and wasm has no guard page to catch an
|
||||
//! overflow (solidcore's README recommends 8 MiB).
|
||||
|
||||
fn main() {
|
||||
if std::env::var("CARGO_CFG_TARGET_ARCH").is_ok_and(|a| a == "wasm32") {
|
||||
println!("cargo:rustc-link-arg=-zstack-size=8388608");
|
||||
}
|
||||
}
|
||||
@@ -1,2 +0,0 @@
|
||||
This build keeps its designs and settings apart per release channel (src/channel.rs).
|
||||
packaging/web-channels.sh only offers a release candidate that has this file.
|
||||
@@ -34,7 +34,6 @@
|
||||
<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 data-trunk rel="copy-file" href="manifest.webmanifest" />
|
||||
<link data-trunk rel="copy-file" href="channel-storage.txt" />
|
||||
<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" />
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
//! Release channels. The site serves the stable web app at `/app/`, the release candidate at
|
||||
//! `/app-rc/` and the bleeding-edge build at `/app-edge/`: one origin, so one set of browser
|
||||
//! storage. A channel that kept its designs and settings in the same places could save a design
|
||||
//! in a file format the stable app can't open, or overwrite stable's settings. So each
|
||||
//! pre-release channel keeps everything under its own name, and stable (and any copy served from
|
||||
//! another path: the release zip, `trunk serve`) keeps the names it has always used, so what
|
||||
//! people have saved at `/app/` is untouched.
|
||||
//!
|
||||
//! The channel comes from the directory the app is served from, not from the build, so a build
|
||||
//! taken from a release zip is as separate as one built on the spot.
|
||||
|
||||
/// The channel the app is served as, from the page's path: `"rc"`, `"edge"`, or `""` (stable).
|
||||
pub fn of_path(pathname: &str) -> &'static str {
|
||||
match pathname.trim_end_matches("index.html").trim_end_matches('/').rsplit('/').next() {
|
||||
Some("app-rc") => "rc",
|
||||
Some("app-edge") => "edge",
|
||||
_ => "",
|
||||
}
|
||||
}
|
||||
|
||||
/// `name` as this channel stores it: unchanged for stable, `rc:name` and `edge:name` otherwise.
|
||||
pub fn name_in(channel: &str, name: &str) -> String {
|
||||
if channel.is_empty() { name.to_owned() } else { format!("{channel}:{name}") }
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn current() -> &'static str {
|
||||
web_sys::window().and_then(|w| w.location().pathname().ok()).map_or("", |p| of_path(&p))
|
||||
}
|
||||
|
||||
/// A storage key (local storage, IndexedDB database name) of the running channel.
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
pub fn key(name: &str) -> String {
|
||||
name_in(current(), name)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn channel_follows_the_directory() {
|
||||
assert_eq!(of_path("/app/"), "");
|
||||
assert_eq!(of_path("/app/index.html"), "");
|
||||
assert_eq!(of_path("/app-rc/"), "rc");
|
||||
assert_eq!(of_path("/app-rc"), "rc");
|
||||
assert_eq!(of_path("/app-rc/index.html"), "rc");
|
||||
assert_eq!(of_path("/app-edge/"), "edge");
|
||||
assert_eq!(of_path("/"), "");
|
||||
assert_eq!(of_path("/solidcraft-web-0.30.1/"), "");
|
||||
assert_eq!(of_path("/app/app-rc/"), "rc", "only the directory's own name counts");
|
||||
assert_eq!(of_path("/app-rc/app/"), "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn stable_keeps_its_names() {
|
||||
assert_eq!(name_in("", "solidcraft.prefs"), "solidcraft.prefs");
|
||||
assert_eq!(name_in("rc", "solidcraft.prefs"), "rc:solidcraft.prefs");
|
||||
assert_ne!(name_in("rc", "x"), name_in("edge", "x"));
|
||||
}
|
||||
}
|
||||
@@ -467,7 +467,7 @@ mod web {
|
||||
|
||||
async fn open_db() -> Result<IdbDatabase, String> {
|
||||
let factory = web_sys::window().ok_or("no window")?.indexed_db().map_err(err)?.ok_or("this browser has no IndexedDB")?;
|
||||
let request: IdbOpenDbRequest = factory.open_with_u32(&crate::channel::key(DB), 1).map_err(err)?;
|
||||
let request: IdbOpenDbRequest = factory.open_with_u32(DB, 1).map_err(err)?;
|
||||
let target = request.clone();
|
||||
let upgrade = Closure::once_into_js(move || {
|
||||
if let Ok(db) = target.result().and_then(|r| r.dyn_into::<IdbDatabase>()) {
|
||||
@@ -553,7 +553,7 @@ mod web {
|
||||
/// Open a design remembered from an earlier session (a recent one, after a reload): ask once
|
||||
/// for access to the file (call in the click), read it into the engine's files and queue it
|
||||
/// to open. A file that is gone is forgotten and dropped from the recent designs.
|
||||
pub fn restore(files: &Rc<Files>, path: &str, on_opened: Option<Box<dyn FnOnce()>>) {
|
||||
pub fn restore(files: &Rc<Files>, path: &str) {
|
||||
let Some((id, name)) = parse(path) else { return };
|
||||
let (files, path, id, name) = (files.clone(), path.to_string(), id.to_string(), name.to_string());
|
||||
spawn_local(async move {
|
||||
@@ -568,9 +568,6 @@ mod web {
|
||||
files.opened.borrow_mut().push(path);
|
||||
files.wake();
|
||||
touch(&id).await;
|
||||
if let Some(done) = on_opened {
|
||||
done();
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
let gone = e.contains("NotFoundError") || e.contains("could not be found") || e.contains("no longer remembers");
|
||||
|
||||
@@ -5,8 +5,7 @@
|
||||
//! and go out as downloads (exports, and "Download design"). Where the File System Access API
|
||||
//! exists a design also lives on the computer's disk, saved and opened through the browser's file
|
||||
//! dialogs (`disk.rs`). An autosave of unsaved work is kept in OPFS and offered again after a
|
||||
//! reload or a crash. With "keep unsaved work in this browser" on, each open design's unsaved
|
||||
//! changes are kept there instead, one copy per design (`keep.rs`, #10220).
|
||||
//! reload or a crash.
|
||||
|
||||
use std::cell::{Cell, RefCell};
|
||||
use std::rc::Rc;
|
||||
@@ -14,12 +13,10 @@ use std::rc::Rc;
|
||||
use js_sys::{Array, Function, Object, Reflect, Uint8Array};
|
||||
use solidcraft_engine::io::vfs;
|
||||
|
||||
use crate::{disk, keep};
|
||||
use crate::disk;
|
||||
use wasm_bindgen::{JsCast as _, JsValue, closure::Closure};
|
||||
use wasm_bindgen_futures::{JsFuture, spawn_local};
|
||||
use web_sys::{
|
||||
FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetDirectoryOptions, FileSystemGetFileOptions, FileSystemWritableFileStream,
|
||||
};
|
||||
use web_sys::{FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetFileOptions, FileSystemWritableFileStream};
|
||||
|
||||
/// Where OPFS files appear to the engine.
|
||||
pub const OPFS: &str = disk::OPFS_PREFIX;
|
||||
@@ -80,16 +77,6 @@ pub struct Files {
|
||||
pub recovery: RefCell<Option<(String, Option<String>)>>,
|
||||
/// The app's context, to wake it when a browser call finishes.
|
||||
pub ctx: RefCell<Option<egui::Context>>,
|
||||
/// This page's id: the copies it keeps are named after it (`keep::entry_id`).
|
||||
pub keep_page: RefCell<String>,
|
||||
/// Kept copies stored by this page, and the design revision each holds once its write ended.
|
||||
pub kept: RefCell<std::collections::HashMap<String, Option<u64>>>,
|
||||
/// Kept copies being written or removed now (one browser call at a time per copy).
|
||||
pub keeping: RefCell<std::collections::HashSet<String>>,
|
||||
/// When (ms) the last write of a copy failed, to wait before trying again (`keep::may_write`).
|
||||
pub kept_failed: RefCell<std::collections::HashMap<String, f64>>,
|
||||
/// Copies earlier pages left, newer than their design's last save: offered at start.
|
||||
pub keep_offered: RefCell<Vec<keep::Entry>>,
|
||||
}
|
||||
|
||||
impl Files {
|
||||
@@ -124,7 +111,7 @@ thread_local! {
|
||||
static LOCAL: Cell<bool> = const { Cell::new(false) };
|
||||
}
|
||||
|
||||
/// Local storage keys of stored files (under the channel's name, see `channel.rs`).
|
||||
/// Local storage keys of stored files.
|
||||
const LOCAL_PREFIX: &str = "solidcraft.file.";
|
||||
/// Local-storage files kept by earlier releases (compatibility shim, docs/compatibility.md):
|
||||
/// still listed and opened; removing one removes either key.
|
||||
@@ -182,17 +169,7 @@ fn has(sm: &web_sys::StorageManager, method: &str) -> bool {
|
||||
async fn root() -> Result<FileSystemDirectoryHandle, String> {
|
||||
let sm = storage_manager().filter(|sm| has(sm, "getDirectory")).ok_or("this page has no private file storage (it needs https or localhost)")?;
|
||||
let dir = JsFuture::from(sm.get_directory()).await.map_err(err)?;
|
||||
let dir = dir.dyn_into::<FileSystemDirectoryHandle>().map_err(|_| "this browser has no private file storage (OPFS)".to_string())?;
|
||||
// Stable keeps its files in the root, where they have always been; a pre-release channel has a folder of its own.
|
||||
match crate::channel::current() {
|
||||
"" => Ok(dir),
|
||||
channel => {
|
||||
let opts = FileSystemGetDirectoryOptions::new();
|
||||
opts.set_create(true);
|
||||
let sub = JsFuture::from(dir.get_directory_handle_with_options(&format!("channel-{channel}"), &opts)).await.map_err(err)?;
|
||||
sub.dyn_into::<FileSystemDirectoryHandle>().map_err(|_| "not a folder".to_string())
|
||||
}
|
||||
}
|
||||
dir.dyn_into::<FileSystemDirectoryHandle>().map_err(|_| "this browser has no private file storage (OPFS)".to_string())
|
||||
}
|
||||
|
||||
pub(crate) async fn read_handle(h: &FileSystemFileHandle) -> Result<Vec<u8>, String> {
|
||||
@@ -214,7 +191,7 @@ async fn file_handle(dir: &FileSystemDirectoryHandle, name: &str, create: bool)
|
||||
async fn write_file(name: &str, bytes: &[u8]) -> Result<(), String> {
|
||||
if local_only() {
|
||||
return local()?
|
||||
.set_item(&crate::channel::key(&format!("{LOCAL_PREFIX}{name}")), &encode(bytes))
|
||||
.set_item(&format!("{LOCAL_PREFIX}{name}"), &encode(bytes))
|
||||
.map_err(|e| format!("local storage is full or blocked: {}", err(e)));
|
||||
}
|
||||
let dir = root().await?;
|
||||
@@ -230,7 +207,7 @@ async fn remove_file(name: &str) -> Result<(), String> {
|
||||
if local_only() {
|
||||
let st = local()?;
|
||||
st.remove_item(&format!("{LEGACY_LOCAL_PREFIX}{name}")).map_err(err)?;
|
||||
return st.remove_item(&crate::channel::key(&format!("{LOCAL_PREFIX}{name}"))).map_err(err);
|
||||
return st.remove_item(&format!("{LOCAL_PREFIX}{name}")).map_err(err);
|
||||
}
|
||||
let dir = root().await?;
|
||||
JsFuture::from(dir.remove_entry(name)).await.map_err(err)?;
|
||||
@@ -243,10 +220,7 @@ fn read_local() -> Result<Vec<(String, Vec<u8>, f64)>, String> {
|
||||
let mut out = Vec::new();
|
||||
for i in 0..st.length().unwrap_or(0).min(10_000) {
|
||||
let Some(k) = st.key(i).ok().flatten() else { continue };
|
||||
let own = crate::channel::key(LOCAL_PREFIX);
|
||||
// The old names belong to stable; a pre-release channel has none.
|
||||
let legacy = crate::channel::current().is_empty().then_some(LEGACY_LOCAL_PREFIX);
|
||||
let Some(name) = k.strip_prefix(own.as_str()).or_else(|| legacy.and_then(|l| k.strip_prefix(l))) else { continue };
|
||||
let Some(name) = k.strip_prefix(LOCAL_PREFIX).or_else(|| k.strip_prefix(LEGACY_LOCAL_PREFIX)) else { continue };
|
||||
if let Some(v) = st.get_item(&k).ok().flatten() {
|
||||
out.push((name.to_string(), decode(&v), 0.0));
|
||||
}
|
||||
@@ -299,9 +273,6 @@ pub fn start(files: &Rc<Files>) {
|
||||
let _ = JsFuture::from(p).await;
|
||||
});
|
||||
}
|
||||
let page = keep::page_id(js_sys::Date::now(), js_sys::Math::random());
|
||||
hold_page_lock(&page);
|
||||
*files.keep_page.borrow_mut() = page;
|
||||
let files = files.clone();
|
||||
spawn_local(async move {
|
||||
match disk::known_ids().await {
|
||||
@@ -314,7 +285,6 @@ pub fn start(files: &Rc<Files>) {
|
||||
files.say("This page can't use the browser's file storage (it needs https or localhost): designs are kept in local storage, a few MB at most.", false);
|
||||
}
|
||||
let mut entries = Vec::new();
|
||||
let kept = kept_copies(&all);
|
||||
let has_recovery = all.iter().any(|(n, _, _)| n == RECOVERY);
|
||||
let legacy = (!has_recovery).then(|| LEGACY_RECOVERY.into_iter().find(|l| all.iter().any(|(n, _, _)| n == l))).flatten();
|
||||
for (name, bytes, modified) in all {
|
||||
@@ -330,7 +300,7 @@ pub fn start(files: &Rc<Files>) {
|
||||
*files.recovery.borrow_mut() = Some((n, meta["path"].as_str().map(str::to_string)));
|
||||
continue;
|
||||
}
|
||||
if name != RECOVERY && keep::parse_name(&name).is_none() {
|
||||
if name != RECOVERY {
|
||||
entries.push(Entry { name: name.clone(), size: bytes.len() as u64, modified });
|
||||
}
|
||||
vfs::insert(&format!("{OPFS}{name}"), bytes);
|
||||
@@ -341,7 +311,6 @@ pub fn start(files: &Rc<Files>) {
|
||||
if !vfs::exists(&format!("{OPFS}{RECOVERY}")) {
|
||||
*files.recovery.borrow_mut() = None;
|
||||
}
|
||||
sort_kept(&files, kept, &live_pages().await);
|
||||
}
|
||||
Err(e) => files.say(format!("browser storage: {e}"), true),
|
||||
}
|
||||
@@ -359,7 +328,7 @@ pub fn flush(files: &Rc<Files>) {
|
||||
{
|
||||
let mut e = files.entries.borrow_mut();
|
||||
e.retain(|x| x.name != name);
|
||||
if name != RECOVERY && name != RECOVERY_META && keep::parse_name(&name).is_none() {
|
||||
if name != RECOVERY && name != RECOVERY_META {
|
||||
e.push(Entry { name: name.clone(), size: bytes.len() as u64, modified: js_sys::Date::now() });
|
||||
e.sort_by_key(|a| a.name.to_lowercase());
|
||||
}
|
||||
@@ -489,15 +458,7 @@ pub fn recovery_path() -> String {
|
||||
/// A recovery file lives in OPFS, but its relative links belong to the original design's
|
||||
/// folder (which can be on disk). Resolve them before Open sees the temporary file location.
|
||||
pub fn prepare_recovery(original_path: Option<&str>) -> Result<String, String> {
|
||||
prepare_copy(recovery_path(), original_path)
|
||||
}
|
||||
|
||||
/// The same for a kept copy (#10220).
|
||||
pub fn prepare_kept(id: &str, original_path: Option<&str>) -> Result<String, String> {
|
||||
prepare_copy(format!("{OPFS}{}", keep::design_file(id)), original_path)
|
||||
}
|
||||
|
||||
fn prepare_copy(path: String, original_path: Option<&str>) -> Result<String, String> {
|
||||
let path = recovery_path();
|
||||
let bytes = vfs::read(&path, solidcraft_engine::io::MAX_DESIGN_BYTES).map_err(|e| e.to_string())?;
|
||||
let mut doc = solidcraft_engine::io::read_design(&bytes).map_err(|e| e.to_string())?;
|
||||
solidcraft_engine::resolve_design_links(&mut doc, original_path);
|
||||
@@ -670,149 +631,3 @@ pub fn take_drops(files: &Rc<Files>, raw: &mut egui::RawInput, theme: bool) {
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// Hold this page's browser lock while it runs, so other tabs know its kept copies are in use
|
||||
/// (`keep.rs`). Browsers without the Web Locks API hold none; their copies are then offered by
|
||||
/// any later page.
|
||||
fn hold_page_lock(page: &str) {
|
||||
let Some(locks) = web_locks() else { return };
|
||||
let Ok(request) = Reflect::get(&locks, &"request".into()).and_then(|f| f.dyn_into::<Function>().map_err(JsValue::from)) else { return };
|
||||
// A promise that never settles: the lock is let go only when the page goes.
|
||||
let hold = Closure::<dyn FnMut(JsValue) -> js_sys::Promise>::new(|_lock: JsValue| js_sys::Promise::new(&mut |_, _| {}));
|
||||
let name = format!("{}{page}", keep::LOCK_PREFIX);
|
||||
if let Err(e) = request.call2(&locks, &name.into(), hold.as_ref().unchecked_ref()) {
|
||||
log::info!("keep: no page lock: {}", err(e));
|
||||
}
|
||||
// The callback lives as long as the page.
|
||||
hold.forget();
|
||||
}
|
||||
|
||||
fn web_locks() -> Option<JsValue> {
|
||||
let nav = web_sys::window()?.navigator();
|
||||
Reflect::get(&nav, &"locks".into()).ok().filter(|l| l.is_object())
|
||||
}
|
||||
|
||||
/// The pages still open (in this browser profile) that hold their lock.
|
||||
async fn live_pages() -> Vec<String> {
|
||||
let query = async {
|
||||
let locks = web_locks().ok_or("no Web Locks")?;
|
||||
let f: Function = Reflect::get(&locks, &"query".into()).map_err(err)?.dyn_into().map_err(|_| "no query".to_string())?;
|
||||
let p: js_sys::Promise = f.call0(&locks).map_err(err)?.dyn_into().map_err(|_| "no promise".to_string())?;
|
||||
let state = JsFuture::from(p).await.map_err(err)?;
|
||||
let held: Array = Reflect::get(&state, &"held".into()).map_err(err)?.dyn_into().map_err(|_| "no list".to_string())?;
|
||||
Ok::<_, String>(
|
||||
held.iter()
|
||||
.take(10_000)
|
||||
.filter_map(|l| Reflect::get(&l, &"name".into()).ok()?.as_string())
|
||||
.filter_map(|n| n.strip_prefix(keep::LOCK_PREFIX).map(str::to_string))
|
||||
.collect(),
|
||||
)
|
||||
};
|
||||
query.await.unwrap_or_else(|e| {
|
||||
log::info!("keep: open pages unknown ({e})");
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
|
||||
/// The kept copies among the stored files: (copy, when its design's file was last written), and
|
||||
/// the ids of designs stored without their metadata.
|
||||
struct Found {
|
||||
copies: Vec<(keep::Entry, Option<f64>)>,
|
||||
orphans: Vec<String>,
|
||||
}
|
||||
|
||||
fn kept_copies(all: &[(String, Vec<u8>, f64)]) -> Found {
|
||||
let modified = |path: &str| path.strip_prefix(OPFS).and_then(|n| all.iter().find(|(name, _, _)| name == n)).map(|(_, _, m)| *m);
|
||||
let mut copies = Vec::new();
|
||||
let mut designs = Vec::new();
|
||||
for (name, bytes, _) in all {
|
||||
match keep::parse_name(name) {
|
||||
Some((id, keep::Part::Meta)) => {
|
||||
if let Some(e) = keep::Entry::parse(id, bytes) {
|
||||
let saved = e.path.as_deref().and_then(modified);
|
||||
copies.push((e, saved));
|
||||
}
|
||||
}
|
||||
Some((id, keep::Part::Design)) => designs.push(id.to_string()),
|
||||
None => {}
|
||||
}
|
||||
}
|
||||
// A copy is whole only with its design.
|
||||
copies.retain(|(e, _)| designs.contains(&e.id));
|
||||
let orphans = designs.into_iter().filter(|id| !copies.iter().any(|(e, _)| &e.id == id)).collect();
|
||||
Found { copies, orphans }
|
||||
}
|
||||
|
||||
/// Offer the copies earlier pages left that hold unsaved work, and remove those their design's
|
||||
/// later save made stale (and parts without the rest), leaving open pages' copies alone.
|
||||
fn sort_kept(files: &Rc<Files>, found: Found, live: &[String]) {
|
||||
let mut offered = Vec::new();
|
||||
let mut gone = Vec::new();
|
||||
for (e, saved) in found.copies {
|
||||
match keep::fate(&e, live, saved) {
|
||||
keep::Fate::Offer => offered.push(e),
|
||||
keep::Fate::Drop => gone.push(e.id),
|
||||
keep::Fate::Leave => {}
|
||||
}
|
||||
}
|
||||
gone.extend(found.orphans.into_iter().filter(|id| !keep::page_of(id).is_some_and(|p| live.iter().any(|l| l == p))));
|
||||
keep::order_offered(&mut offered);
|
||||
*files.keep_offered.borrow_mut() = offered;
|
||||
for id in gone {
|
||||
remove_kept(files, &id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The path (in the engine's files) a kept copy opens from.
|
||||
pub fn kept_path(id: &str) -> String {
|
||||
format!("{OPFS}{}", keep::design_file(id))
|
||||
}
|
||||
|
||||
/// Write the copy of a design with unsaved changes (`bytes`, described by `entry`) that holds
|
||||
/// its revision `revision`. Its metadata goes last, so a copy is never offered half written.
|
||||
pub fn write_kept(files: &Rc<Files>, entry: &keep::Entry, bytes: Vec<u8>, revision: u64) {
|
||||
let id = entry.id.clone();
|
||||
if !files.keeping.borrow_mut().insert(id.clone()) {
|
||||
return;
|
||||
}
|
||||
files.kept.borrow_mut().entry(id.clone()).or_insert(None);
|
||||
let meta = entry.to_json();
|
||||
let files = files.clone();
|
||||
spawn_local(async move {
|
||||
let r = async {
|
||||
write_file(&keep::design_file(&id), &bytes).await?;
|
||||
write_file(&keep::meta_file(&id), meta.as_bytes()).await
|
||||
};
|
||||
match r.await {
|
||||
Ok(()) => {
|
||||
files.kept.borrow_mut().insert(id.clone(), Some(revision));
|
||||
files.kept_failed.borrow_mut().remove(&id);
|
||||
}
|
||||
Err(e) => {
|
||||
files.kept_failed.borrow_mut().insert(id.clone(), js_sys::Date::now());
|
||||
files.say(format!("keeping unsaved work in this browser: {e}"), true)
|
||||
}
|
||||
}
|
||||
files.keeping.borrow_mut().remove(&id);
|
||||
files.wake();
|
||||
});
|
||||
}
|
||||
|
||||
/// Remove kept copy `id` (its design was saved, closed, discarded or restored).
|
||||
pub fn remove_kept(files: &Rc<Files>, id: &str) {
|
||||
let id = id.to_string();
|
||||
if !files.keeping.borrow_mut().insert(id.clone()) {
|
||||
return;
|
||||
}
|
||||
let _ = vfs::remove(&kept_path(&id));
|
||||
files.kept_failed.borrow_mut().remove(&id);
|
||||
let files = files.clone();
|
||||
spawn_local(async move {
|
||||
// The metadata first: what's left without it is never offered.
|
||||
let _ = remove_file(&keep::meta_file(&id)).await;
|
||||
let _ = remove_file(&keep::design_file(&id)).await;
|
||||
files.kept.borrow_mut().remove(&id);
|
||||
files.keeping.borrow_mut().remove(&id);
|
||||
files.wake();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,360 +0,0 @@
|
||||
//! Keep unsaved work in this browser (#10220): the decisions, in plain Rust (tested natively).
|
||||
//!
|
||||
//! With Preferences › General › "Keep unsaved work in this browser instead of asking when the tab
|
||||
//! closes" on, each open design with unsaved changes has a copy in the browser's private storage:
|
||||
//! `keep-<id>.sc` (the design) and `keep-<id>.json` (its name, its file and when it last
|
||||
//! changed). The copy is written shortly after each change ([`due`]) and again when the page is
|
||||
//! hidden, so it is already current when the tab closes; the page then doesn't ask through the
|
||||
//! browser's "leave site?" box ([`asks_before_unload`]). A design that is saved, closed or
|
||||
//! discarded has its copy removed. The next visit offers the copies newer than their design's
|
||||
//! last save ([`fate`]) in SolidCraft's own window, answered with [`plan`].
|
||||
//!
|
||||
//! An id is `<page>-<n>`: the page that wrote it (each page load has its own) and the design's
|
||||
//! number in that page. A page holds a browser lock named after it while it runs, so another tab
|
||||
//! never offers, or removes, the copies of a page that is still open.
|
||||
#![cfg_attr(not(target_arch = "wasm32"), allow(dead_code))]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
|
||||
/// The start of every kept file's name.
|
||||
pub const PREFIX: &str = "keep-";
|
||||
/// The browser lock a page holds while it runs: this, then its page id.
|
||||
pub const LOCK_PREFIX: &str = "solidcraft.keep.";
|
||||
/// A copy is written once the design has been still this long (ms)…
|
||||
pub const QUIET_MS: f64 = 800.0;
|
||||
/// …or at the latest this long after the first change it doesn't hold yet (ms).
|
||||
pub const MAX_WAIT_MS: f64 = 4000.0;
|
||||
/// The most copies offered at once (the rest wait for the next visit).
|
||||
pub const MAX_OFFERED: usize = 32;
|
||||
const MAX_ID: usize = 64;
|
||||
const MAX_NAME: usize = 256;
|
||||
const MAX_PATH: usize = 4096;
|
||||
/// Copies' metadata is small: a damaged or hostile one is not read.
|
||||
pub const MAX_META_BYTES: usize = 64 * 1024;
|
||||
|
||||
/// Ids are short and safe in a file name: ASCII letters, digits and dashes, with a page part.
|
||||
pub fn valid_id(id: &str) -> bool {
|
||||
!id.is_empty() && id.len() <= MAX_ID && id.chars().all(|c| c.is_ascii_alphanumeric() || c == '-') && page_of(id).is_some()
|
||||
}
|
||||
|
||||
/// The page that wrote copy `id`.
|
||||
pub fn page_of(id: &str) -> Option<&str> {
|
||||
id.rsplit_once('-').map(|(page, _)| page).filter(|p| !p.is_empty())
|
||||
}
|
||||
|
||||
/// This page's id, from the time it started (ms since 1970) and a random number in 0..1:
|
||||
/// letters and digits only.
|
||||
pub fn page_id(now_ms: f64, random: f64) -> String {
|
||||
let now = if now_ms.is_finite() && now_ms > 0.0 { now_ms.min(1e15) as u64 } else { 0 };
|
||||
let random = if random.is_finite() { (random.clamp(0.0, 1.0) * f64::from(0xff_ffff)) as u64 } else { 0 };
|
||||
format!("{now:x}{random:06x}")
|
||||
}
|
||||
|
||||
/// The copy of design `uid` (`Session::uid`) open in page `page`.
|
||||
pub fn entry_id(page: &str, uid: u64) -> String {
|
||||
format!("{page}-{uid}")
|
||||
}
|
||||
|
||||
/// The stored file names of copy `id`: its design and its metadata.
|
||||
pub fn design_file(id: &str) -> String {
|
||||
format!("{PREFIX}{id}.{}", solidcraft_engine::io::DESIGN_EXT)
|
||||
}
|
||||
pub fn meta_file(id: &str) -> String {
|
||||
format!("{PREFIX}{id}.json")
|
||||
}
|
||||
|
||||
/// Which part of a copy a stored file is.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Part {
|
||||
Design,
|
||||
Meta,
|
||||
}
|
||||
|
||||
/// A stored file's copy id and part; `None` for every other file.
|
||||
pub fn parse_name(name: &str) -> Option<(&str, Part)> {
|
||||
let rest = name.strip_prefix(PREFIX)?;
|
||||
let (id, part) = if let Some(id) = rest.strip_suffix(".json") {
|
||||
(id, Part::Meta)
|
||||
} else {
|
||||
(rest.strip_suffix(solidcraft_engine::io::DESIGN_EXT)?.strip_suffix('.')?, Part::Design)
|
||||
};
|
||||
valid_id(id).then_some((id, part))
|
||||
}
|
||||
|
||||
/// A kept copy, as its metadata describes it.
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct Entry {
|
||||
pub id: String,
|
||||
/// The design's name.
|
||||
pub name: String,
|
||||
/// Its file (`None`: never saved).
|
||||
pub path: Option<String>,
|
||||
/// When the design last changed (ms since 1970).
|
||||
pub changed_at: f64,
|
||||
}
|
||||
|
||||
impl Entry {
|
||||
pub fn to_json(&self) -> String {
|
||||
json!({"name": self.name, "path": self.path, "changed_at": self.changed_at}).to_string()
|
||||
}
|
||||
|
||||
/// Read copy `id`'s metadata. Anything malformed, oversized or not finite is no copy.
|
||||
pub fn parse(id: &str, bytes: &[u8]) -> Option<Entry> {
|
||||
if !valid_id(id) || bytes.len() > MAX_META_BYTES {
|
||||
return None;
|
||||
}
|
||||
let v: Value = serde_json::from_slice(bytes).ok()?;
|
||||
let name = v.get("name")?.as_str()?.chars().take(MAX_NAME).collect::<String>();
|
||||
let path = match v.get("path") {
|
||||
None | Some(Value::Null) => None,
|
||||
Some(p) => Some(p.as_str().filter(|p| !p.is_empty() && p.len() <= MAX_PATH)?.to_string()),
|
||||
};
|
||||
let changed_at = v.get("changed_at")?.as_f64().filter(|t| t.is_finite() && *t >= 0.0)?;
|
||||
Some(Entry { id: id.to_string(), name, path, changed_at })
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether a copy that last changed at `changed_at` holds work its design's file doesn't:
|
||||
/// the file was last written at `saved_at` (ms since 1970; `None` or unknown: never saved, or
|
||||
/// the time can't be read, so the copy is kept rather than lost).
|
||||
pub fn newer_than_save(changed_at: f64, saved_at: Option<f64>) -> bool {
|
||||
match saved_at {
|
||||
Some(saved) if saved.is_finite() && saved > 0.0 => changed_at > saved,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
/// What the next visit does with a copy it finds.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Fate {
|
||||
/// Offer it: "Restore unsaved design…?".
|
||||
Offer,
|
||||
/// Its design was saved after it: remove it without asking.
|
||||
Drop,
|
||||
/// Its page is still open (another tab): leave it alone.
|
||||
Leave,
|
||||
}
|
||||
|
||||
/// The copy `entry`, whose design's file was last written at `saved_at`, while the pages
|
||||
/// `live` are open.
|
||||
pub fn fate(entry: &Entry, live: &[String], saved_at: Option<f64>) -> Fate {
|
||||
if page_of(&entry.id).is_some_and(|p| live.iter().any(|l| l == p)) {
|
||||
Fate::Leave
|
||||
} else if newer_than_save(entry.changed_at, saved_at) {
|
||||
Fate::Offer
|
||||
} else {
|
||||
Fate::Drop
|
||||
}
|
||||
}
|
||||
|
||||
/// Copies to offer first: the most recently changed, at most [`MAX_OFFERED`].
|
||||
pub fn order_offered(entries: &mut Vec<Entry>) {
|
||||
entries.sort_by(|a, b| b.changed_at.total_cmp(&a.changed_at).then_with(|| a.id.cmp(&b.id)));
|
||||
entries.truncate(MAX_OFFERED);
|
||||
}
|
||||
|
||||
/// Whether a design's copy is due to be written at `now`: the design last changed at
|
||||
/// `last_change`, and the copy has lacked a change since `first_unwritten` (all ms).
|
||||
pub fn due(now: f64, first_unwritten: f64, last_change: f64) -> bool {
|
||||
now - last_change >= QUIET_MS || now - first_unwritten >= MAX_WAIT_MS
|
||||
}
|
||||
|
||||
/// How long after a failed write of a design's copy it isn't tried again (ms).
|
||||
pub const RETRY_MS: f64 = 30_000.0;
|
||||
|
||||
/// Whether a copy whose write last failed at `failed_at` (ms; `None`: it hasn't) may be written
|
||||
/// at `now`: not before [`RETRY_MS`] has passed, so a full or denied store isn't hit every tick.
|
||||
pub fn may_write(now: f64, failed_at: Option<f64>) -> bool {
|
||||
failed_at.is_none_or(|t| !(now >= t) || now - t >= RETRY_MS)
|
||||
}
|
||||
|
||||
/// Whether `wanted` is the design now open, given the open design's path.
|
||||
pub fn is_open(open: Option<&str>, wanted: &str) -> bool {
|
||||
open == Some(wanted)
|
||||
}
|
||||
|
||||
/// Whether leaving the page asks through the browser's "leave site?" box: there is unsaved work,
|
||||
/// and either the user still wants to be asked or it isn't all kept in the browser yet (with the
|
||||
/// keep preference off it never is, so there the box always stays).
|
||||
pub fn asks_before_unload(persists: bool, keep: bool, also_ask: bool, any_unsaved: bool, all_kept: bool) -> bool {
|
||||
persists && any_unsaved && (also_ask || !(keep && all_kept))
|
||||
}
|
||||
|
||||
/// An answer in the "Restore unsaved design?" window.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Answer {
|
||||
/// Open the copy, with its unsaved changes, as the design it was (saving goes to its file).
|
||||
Restore,
|
||||
/// Open the design's file as last saved; the copy goes.
|
||||
OpenSaved,
|
||||
/// The copy goes.
|
||||
Discard,
|
||||
}
|
||||
|
||||
/// What an answer does.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Plan {
|
||||
/// Open the copy (and, once it is open, give it back its name and file).
|
||||
pub open_copy: bool,
|
||||
/// Open this saved file.
|
||||
pub open_saved: Option<String>,
|
||||
/// Remove the copy.
|
||||
pub drop_copy: bool,
|
||||
}
|
||||
|
||||
/// What `answer` does to copy `entry`; `saved_exists`: its file can still be opened. `None`: the
|
||||
/// answer isn't offered for it (Open saved version of a design never saved, or gone).
|
||||
pub fn plan(answer: Answer, entry: &Entry, saved_exists: bool) -> Option<Plan> {
|
||||
match answer {
|
||||
Answer::Restore => Some(Plan { open_copy: true, open_saved: None, drop_copy: true }),
|
||||
Answer::OpenSaved => {
|
||||
let path = entry.path.clone().filter(|_| saved_exists)?;
|
||||
Some(Plan { open_copy: false, open_saved: Some(path), drop_copy: true })
|
||||
}
|
||||
Answer::Discard => Some(Plan { open_copy: false, open_saved: None, drop_copy: true }),
|
||||
}
|
||||
}
|
||||
|
||||
/// The answers the window offers for `entry`, in order.
|
||||
pub fn answers(entry: &Entry, saved_exists: bool) -> Vec<Answer> {
|
||||
[Answer::Restore, Answer::OpenSaved, Answer::Discard].into_iter().filter(|a| plan(*a, entry, saved_exists).is_some()).collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn entry(id: &str, changed_at: f64, path: Option<&str>) -> Entry {
|
||||
Entry { id: id.into(), name: "Bracket".into(), path: path.map(str::to_string), changed_at }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_failed_write_waits_before_the_next_try() {
|
||||
assert!(may_write(1000.0, None));
|
||||
assert!(!may_write(1000.0 + RETRY_MS - 1.0, Some(1000.0)));
|
||||
assert!(may_write(1000.0 + RETRY_MS, Some(1000.0)));
|
||||
// A clock that went back doesn't block for good.
|
||||
assert!(may_write(10.0, Some(1000.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_design_counts_as_opened_only_at_its_own_path() {
|
||||
assert!(is_open(Some("a.sc"), "a.sc"));
|
||||
assert!(!is_open(Some("b.sc"), "a.sc"));
|
||||
assert!(!is_open(None, "a.sc"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn copies_are_named_by_page_and_design_and_nothing_else_reads_as_one() {
|
||||
let page = page_id(1_700_000_000_000.0, 0.5);
|
||||
assert!(page.chars().all(|c| c.is_ascii_hexdigit()));
|
||||
let id = entry_id(&page, 7);
|
||||
assert!(valid_id(&id));
|
||||
assert_eq!(page_of(&id), Some(page.as_str()));
|
||||
assert_eq!(parse_name(&design_file(&id)), Some((id.as_str(), Part::Design)));
|
||||
assert_eq!(parse_name(&meta_file(&id)), Some((id.as_str(), Part::Meta)));
|
||||
for other in ["part.sc", "recovery.sc", "recovery.json", "keep-.sc", "keep-x.sc", "keep-a/b-1.sc", "keep-a-1.step", "keep-a-1sc"] {
|
||||
assert_eq!(parse_name(other), None, "{other}");
|
||||
}
|
||||
// Hostile clocks still give a usable id.
|
||||
for (now, random) in [(f64::NAN, f64::NAN), (f64::INFINITY, -1.0), (-5.0, 9.0)] {
|
||||
assert!(valid_id(&entry_id(&page_id(now, random), 1)));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn metadata_round_trips_and_hostile_metadata_is_no_copy() {
|
||||
let e = entry("a1-3", 1234.0, Some("/opfs/Bracket.sc"));
|
||||
assert_eq!(Entry::parse("a1-3", e.to_json().as_bytes()), Some(e.clone()));
|
||||
let never_saved = entry("a1-4", 99.0, None);
|
||||
assert_eq!(Entry::parse("a1-4", never_saved.to_json().as_bytes()), Some(never_saved));
|
||||
for bad in [
|
||||
"",
|
||||
"[]",
|
||||
r#"{"name":"x","changed_at":"soon"}"#,
|
||||
r#"{"name":"x","changed_at":-1}"#,
|
||||
r#"{"name":"x","changed_at":1e999}"#,
|
||||
r#"{"name":"x","path":7,"changed_at":1}"#,
|
||||
r#"{"name":"x","path":"","changed_at":1}"#,
|
||||
r#"{"changed_at":1}"#,
|
||||
] {
|
||||
assert_eq!(Entry::parse("a1-3", bad.as_bytes()), None, "{bad}");
|
||||
}
|
||||
assert_eq!(Entry::parse("../x", e.to_json().as_bytes()), None);
|
||||
assert_eq!(Entry::parse("a1-3", &vec![b' '; MAX_META_BYTES + 1]), None);
|
||||
let long = json!({"name": "n".repeat(10_000), "changed_at": 1}).to_string();
|
||||
assert_eq!(Entry::parse("a1-3", long.as_bytes()).map(|e| e.name.len()), Some(MAX_NAME));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_copy_newer_than_the_last_save_is_offered_an_older_one_dropped_an_open_pages_left() {
|
||||
let e = entry("p1-1", 2000.0, Some("/opfs/Bracket.sc"));
|
||||
// Changed after the file was last written: offered.
|
||||
assert_eq!(fate(&e, &[], Some(1000.0)), Fate::Offer);
|
||||
// Saved since (in this tab or another): nothing to offer.
|
||||
assert_eq!(fate(&e, &[], Some(2000.0)), Fate::Drop);
|
||||
assert_eq!(fate(&e, &[], Some(3000.0)), Fate::Drop);
|
||||
// Never saved, or the save time can't be read: kept rather than lost.
|
||||
for saved in [None, Some(0.0), Some(f64::NAN), Some(f64::INFINITY), Some(-4.0)] {
|
||||
assert_eq!(fate(&e, &[], saved), Fate::Offer, "{saved:?}");
|
||||
}
|
||||
// Its page is still open in another tab: neither offered nor removed.
|
||||
assert_eq!(fate(&e, &["p1".into()], Some(1000.0)), Fate::Leave);
|
||||
assert_eq!(fate(&e, &["p1".into()], Some(3000.0)), Fate::Leave);
|
||||
assert_eq!(fate(&e, &["p2".into()], Some(1000.0)), Fate::Offer);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_newest_copies_are_offered_first_and_at_most_a_windowful() {
|
||||
let mut list: Vec<Entry> = (0..40).map(|i| entry(&format!("p-{i}"), f64::from(i), None)).collect();
|
||||
order_offered(&mut list);
|
||||
assert_eq!(list.len(), MAX_OFFERED);
|
||||
assert_eq!(list.first().map(|e| e.id.as_str()), Some("p-39"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_copy_is_written_once_the_design_is_still_or_after_the_longest_wait() {
|
||||
// Still changing, not waited long: not yet.
|
||||
assert!(!due(1000.0, 900.0, 900.0));
|
||||
// Still for QUIET_MS: due.
|
||||
assert!(due(900.0 + QUIET_MS, 900.0, 900.0));
|
||||
// Changing all the time: due after MAX_WAIT_MS anyway.
|
||||
assert!(!due(5000.0, 2000.0, 4900.0));
|
||||
assert!(due(2000.0 + MAX_WAIT_MS, 2000.0, 2000.0 + MAX_WAIT_MS - 1.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_browser_asks_before_unload_unless_told_not_to_and_every_copy_is_current() {
|
||||
// Keeping off: exactly as before, unsaved work asks, whatever the second option says.
|
||||
assert!(asks_before_unload(true, false, true, true, true));
|
||||
assert!(asks_before_unload(true, false, true, true, false));
|
||||
assert!(asks_before_unload(true, false, false, true, true));
|
||||
// Keeping on, asking on (the default): both, the box and the copy.
|
||||
assert!(asks_before_unload(true, true, true, true, true));
|
||||
// Keeping on, asking off, every unsaved design kept: no browser box.
|
||||
assert!(!asks_before_unload(true, true, false, true, true));
|
||||
// Keeping on, asking off, a copy still being written (or failed): the browser still asks.
|
||||
assert!(asks_before_unload(true, true, false, true, false));
|
||||
// Nothing unsaved, or an embedded page that keeps nothing: never.
|
||||
assert!(!asks_before_unload(true, true, true, false, false));
|
||||
assert!(!asks_before_unload(true, false, true, false, false));
|
||||
assert!(!asks_before_unload(false, false, true, true, false));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn restore_open_saved_and_discard_each_do_one_thing_and_always_remove_the_copy() {
|
||||
let saved = entry("p-1", 5.0, Some("/opfs/Bracket.sc"));
|
||||
assert_eq!(plan(Answer::Restore, &saved, true), Some(Plan { open_copy: true, open_saved: None, drop_copy: true }));
|
||||
assert_eq!(
|
||||
plan(Answer::OpenSaved, &saved, true),
|
||||
Some(Plan { open_copy: false, open_saved: Some("/opfs/Bracket.sc".into()), drop_copy: true })
|
||||
);
|
||||
assert_eq!(plan(Answer::Discard, &saved, true), Some(Plan { open_copy: false, open_saved: None, drop_copy: true }));
|
||||
assert_eq!(answers(&saved, true), [Answer::Restore, Answer::OpenSaved, Answer::Discard]);
|
||||
// A design never saved, or whose file is gone, has no saved version to open.
|
||||
let never = entry("p-2", 5.0, None);
|
||||
assert_eq!(plan(Answer::OpenSaved, &never, true), None);
|
||||
assert_eq!(plan(Answer::OpenSaved, &saved, false), None);
|
||||
assert_eq!(answers(&never, false), [Answer::Restore, Answer::Discard]);
|
||||
}
|
||||
}
|
||||
@@ -3,15 +3,12 @@
|
||||
//! Runs the same [`solidcraft_ui_egui::SolveApp`] as the desktop app through eframe's web runner
|
||||
//! (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release` from this
|
||||
//! directory (output in `dist/web`). URL flags: `?webgl` forces WebGL2 (remembered until
|
||||
//! `?webgpu`), `?sample` opens the sample part, `?embed=editor` runs it as an editing embed in a
|
||||
//! host page (no application bar or start page; nothing kept in the browser; #8517).
|
||||
//! `?webgpu`), `?sample` opens the sample part.
|
||||
#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod api;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod channel;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod disk;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod easyprint;
|
||||
@@ -19,8 +16,6 @@ mod easyprint;
|
||||
mod files;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod install;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
mod keep;
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
mod linked;
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
@@ -45,8 +40,6 @@ fn query_flag(query: &str, name: &str) -> bool {
|
||||
struct Launch {
|
||||
open: Option<String>,
|
||||
embed: bool,
|
||||
/// `?embed=editor` (#8517): an editing embed in a host page that keeps the files itself.
|
||||
editor: bool,
|
||||
}
|
||||
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
@@ -57,19 +50,16 @@ impl Launch {
|
||||
for (key, value) in url::form_urlencoded::parse(query.trim_start_matches('?').as_bytes()) {
|
||||
match key.as_ref() {
|
||||
"open" if launch.open.is_none() => launch.open = Some(value.into_owned()),
|
||||
"embed" if value == "editor" => launch.editor = true,
|
||||
"embed" => embed = true,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
launch.embed = embed && launch.open.is_some() && !launch.editor;
|
||||
launch.embed = embed && launch.open.is_some();
|
||||
launch
|
||||
}
|
||||
|
||||
/// The browser's own storage (designs, autosave, recovery, preferences) is used: not when
|
||||
/// embedded, where the host keeps what is kept.
|
||||
fn persists(&self) -> bool {
|
||||
!self.embed && !self.editor
|
||||
!self.embed
|
||||
}
|
||||
|
||||
fn full_app_url(&self, mut url: url::Url) -> String {
|
||||
@@ -162,15 +152,8 @@ impl PrefsWriter {
|
||||
/// tab of the page when one tab writes). Other keys, and settings taken away (clearing the site's
|
||||
/// data), leave this tab's settings as they are.
|
||||
#[cfg(any(target_arch = "wasm32", test))]
|
||||
fn settings_written(own_key: &str, key: Option<&str>, value: Option<String>) -> Option<String> {
|
||||
// Tabs of another channel write to the same storage, under their own key.
|
||||
value.filter(|_| key == Some(own_key))
|
||||
}
|
||||
|
||||
/// The settings key of `channel` (see [`channel`]).
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn prefs_key() -> String {
|
||||
channel::key(solidcraft_ui_egui::prefs::STORAGE_KEY)
|
||||
fn settings_written(key: Option<&str>, value: Option<String>) -> Option<String> {
|
||||
value.filter(|_| key == Some(solidcraft_ui_egui::prefs::STORAGE_KEY))
|
||||
}
|
||||
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
@@ -179,14 +162,12 @@ mod web {
|
||||
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::io::vfs;
|
||||
use solidcraft_ui_egui::ui_kit::BackingTooltipExt as _;
|
||||
use solidcraft_ui_egui::{Services, SolveApp, ui_kit};
|
||||
use wasm_bindgen::JsCast as _;
|
||||
|
||||
use crate::api::Api;
|
||||
use crate::disk::{self, SaveTarget};
|
||||
use crate::files::{self, Files, Intent};
|
||||
use crate::keep;
|
||||
use crate::space_mouse::SpaceMouse;
|
||||
|
||||
const CANVAS_ID: &str = "solidcraft_canvas";
|
||||
@@ -246,9 +227,7 @@ mod web {
|
||||
}
|
||||
|
||||
/// The browser's "leave site? changes you made may not be saved" prompt while a design has
|
||||
/// unsaved changes (the page can't show its own dialog when it is being closed). With "keep
|
||||
/// unsaved work in this browser" on and "still ask" off, there is nothing to ask once every
|
||||
/// unsaved design's copy is current (`keep::asks_before_unload`, #10220).
|
||||
/// unsaved changes (the page can't show its own dialog when it is being closed).
|
||||
fn ask_before_unload(window: &web_sys::Window) {
|
||||
let on_unload = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::BeforeUnloadEvent)>::new(|e: web_sys::BeforeUnloadEvent| {
|
||||
if UNSAVED.with(std::cell::Cell::get) {
|
||||
@@ -261,50 +240,6 @@ mod web {
|
||||
on_unload.forget();
|
||||
}
|
||||
|
||||
/// With "keep unsaved work in this browser" on, the page brings every unsaved design's copy up
|
||||
/// to date when it is hidden or goes (`pagehide`, `visibilitychange`): the browser may not
|
||||
/// wait for that write, so the copies are written shortly after each change as well
|
||||
/// (`Shell::keep_tick`, #10220).
|
||||
fn keep_when_hidden(window: &web_sys::Window, runner: &eframe::WebRunner) {
|
||||
let keep_now = {
|
||||
let runner = runner.clone();
|
||||
move || {
|
||||
if let Some(mut shell) = runner.app_mut::<Shell>() {
|
||||
shell.keep_tick(None, true);
|
||||
}
|
||||
}
|
||||
};
|
||||
let on_hide = {
|
||||
let keep_now = keep_now.clone();
|
||||
wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::Event)>::new(move |_: web_sys::Event| keep_now())
|
||||
};
|
||||
let on_visibility = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::Event)>::new(move |_: web_sys::Event| {
|
||||
let hidden = web_sys::window()
|
||||
.and_then(|w| w.document())
|
||||
.and_then(|d| js_sys::Reflect::get(&d, &"visibilityState".into()).ok())
|
||||
.and_then(|v| v.as_string())
|
||||
.is_some_and(|v| v == "hidden");
|
||||
if hidden {
|
||||
keep_now();
|
||||
}
|
||||
});
|
||||
let _ = window.add_event_listener_with_callback("pagehide", on_hide.as_ref().unchecked_ref());
|
||||
if let Some(document) = window.document() {
|
||||
let _ = document.add_event_listener_with_callback("visibilitychange", on_visibility.as_ref().unchecked_ref());
|
||||
}
|
||||
// The listeners live as long as the page.
|
||||
on_hide.forget();
|
||||
on_visibility.forget();
|
||||
}
|
||||
|
||||
/// One open design followed for its kept copy: its revision, when that last changed, and since
|
||||
/// when its copy has lacked a change (`None`: current, or being written).
|
||||
struct Track {
|
||||
revision: u64,
|
||||
last_change: f64,
|
||||
first_unwritten: Option<f64>,
|
||||
}
|
||||
|
||||
/// eframe cancels the browser's action for every Tab it gets, so the browser couldn't switch
|
||||
/// tabs while the app has the keyboard. Its tab-switching keys stop here, on the way down to
|
||||
/// the canvas, before eframe sees them; the browser then acts on them as usual.
|
||||
@@ -327,7 +262,7 @@ mod web {
|
||||
fn hear_settings_from_other_tabs(window: &web_sys::Window, ctx: &egui::Context, inbox: PrefsInbox) {
|
||||
let ctx = ctx.clone();
|
||||
let on_storage = wasm_bindgen::closure::Closure::<dyn FnMut(web_sys::StorageEvent)>::new(move |e: web_sys::StorageEvent| {
|
||||
if let Some(prefs) = crate::settings_written(&crate::prefs_key(), e.key().as_deref(), e.new_value()) {
|
||||
if let Some(prefs) = crate::settings_written(e.key().as_deref(), e.new_value()) {
|
||||
inbox.set(Some(prefs));
|
||||
ctx.request_repaint();
|
||||
}
|
||||
@@ -377,10 +312,6 @@ mod web {
|
||||
/// The settings this tab wrote or loaded last, and those another tab wrote since (#8644).
|
||||
prefs_writer: crate::PrefsWriter,
|
||||
other_tab_prefs: PrefsInbox,
|
||||
/// The open designs followed for their kept copy, by `Session::uid` (#10220).
|
||||
keep_track: std::collections::HashMap<u64, Track>,
|
||||
/// Every unsaved design's kept copy is current (and the preference is on).
|
||||
all_kept: bool,
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
@@ -403,7 +334,8 @@ mod web {
|
||||
}
|
||||
let prefs = self.persisted_prefs();
|
||||
let Some(prefs) = self.prefs_writer.take(prefs) else { return };
|
||||
let stored = web_sys::window().and_then(|w| w.local_storage().ok().flatten()).map(|s| s.set_item(&crate::prefs_key(), &prefs));
|
||||
let stored =
|
||||
web_sys::window().and_then(|w| w.local_storage().ok().flatten()).map(|s| s.set_item(solidcraft_ui_egui::prefs::STORAGE_KEY, &prefs));
|
||||
if !matches!(stored, Some(Ok(()))) {
|
||||
log::warn!("settings: the browser's storage did not take them; they are saved again later");
|
||||
self.prefs_writer = crate::PrefsWriter::default();
|
||||
@@ -495,14 +427,8 @@ mod web {
|
||||
self.sync_recents();
|
||||
}
|
||||
let now = ctx.input(|i| i.time);
|
||||
// With "keep unsaved work in this browser" on, each design's own copy takes over from
|
||||
// the single autosave (#10220).
|
||||
if self.app.preferences.keep_unsaved_in_browser && self.autosaved.is_some() {
|
||||
files::autosave(&self.files, None);
|
||||
self.autosaved = None;
|
||||
}
|
||||
// Unsaved work is kept (at most every AUTOSAVE_S); saved work needs no autosave.
|
||||
if self.files.ready.get() && self.files.recovery.borrow().is_none() && !self.app.preferences.keep_unsaved_in_browser {
|
||||
if self.files.ready.get() && self.files.recovery.borrow().is_none() {
|
||||
let s = &self.app.session;
|
||||
let changed = s.is_dirty() && self.autosaved != Some(s.revision);
|
||||
let wait = AUTOSAVE_S - (now - self.autosaved_at);
|
||||
@@ -521,175 +447,6 @@ mod web {
|
||||
if !self.files.ready.get() || !self.files.incoming.borrow().is_empty() {
|
||||
ctx.request_repaint_after(std::time::Duration::from_millis(100));
|
||||
}
|
||||
self.all_kept = self.keep_tick(Some(ctx), false);
|
||||
}
|
||||
|
||||
/// Keep each open design's unsaved changes in the browser, when the preference asks for
|
||||
/// it: a design's copy is written once it has been still a moment (`keep::due`), or at
|
||||
/// once with `now` (the page is being hidden); a copy whose design is saved or closed, or
|
||||
/// with the preference off, goes. Answers whether every unsaved design's copy is current.
|
||||
fn keep_tick(&mut self, ctx: Option<&egui::Context>, now: bool) -> bool {
|
||||
if !self.launch.persists() || !self.files.ready.get() {
|
||||
return false;
|
||||
}
|
||||
let Shell { app, files, keep_track, .. } = self;
|
||||
let on = app.preferences.keep_unsaved_in_browser;
|
||||
let time = js_sys::Date::now();
|
||||
let page = files.keep_page.borrow().clone();
|
||||
let mut wanted = std::collections::HashSet::new();
|
||||
let mut all_kept = true;
|
||||
let mut wake: Option<f64> = None;
|
||||
let mut error = None;
|
||||
let mut soon = |ms: f64| wake = Some(wake.map_or(ms, |w: f64| w.min(ms)));
|
||||
if on {
|
||||
for s in solidcraft_ui_egui::documents::open_sessions(app).into_iter().filter(|s| s.is_dirty()) {
|
||||
let id = keep::entry_id(&page, s.uid);
|
||||
wanted.insert(s.uid);
|
||||
let t = keep_track.entry(s.uid).or_insert(Track { revision: s.revision, last_change: time, first_unwritten: None });
|
||||
if t.revision != s.revision {
|
||||
t.revision = s.revision;
|
||||
t.last_change = time;
|
||||
}
|
||||
if files.kept.borrow().get(&id).copied().flatten() == Some(s.revision) {
|
||||
t.first_unwritten = None;
|
||||
continue;
|
||||
}
|
||||
all_kept = false;
|
||||
if files.keeping.borrow().contains(&id) {
|
||||
soon(100.0);
|
||||
continue;
|
||||
}
|
||||
let first = *t.first_unwritten.get_or_insert(time);
|
||||
let failed = files.kept_failed.borrow().get(&id).copied();
|
||||
if !keep::may_write(time, failed) {
|
||||
// The last write failed: try again after a while.
|
||||
soon(keep::RETRY_MS);
|
||||
continue;
|
||||
}
|
||||
if now || keep::due(time, first, t.last_change) {
|
||||
match solidcraft_engine::io::write_design_container(&s.design()) {
|
||||
Ok(bytes) => {
|
||||
let entry = keep::Entry { id, name: s.doc.name.clone(), path: s.path.clone(), changed_at: t.last_change };
|
||||
files::write_kept(files, &entry, bytes, s.revision);
|
||||
t.first_unwritten = None;
|
||||
}
|
||||
Err(e) => {
|
||||
files.kept_failed.borrow_mut().insert(id, time);
|
||||
error = Some(format!("keeping unsaved work in this browser: {e}"));
|
||||
}
|
||||
}
|
||||
soon(100.0);
|
||||
} else {
|
||||
soon((keep::QUIET_MS - (time - t.last_change)).min(keep::MAX_WAIT_MS - (time - first)).max(16.0));
|
||||
}
|
||||
}
|
||||
}
|
||||
// This page's copies no design needs any more: saved, closed, or the preference off.
|
||||
let stale: Vec<String> = files
|
||||
.kept
|
||||
.borrow()
|
||||
.keys()
|
||||
.filter(|id| keep::page_of(id) == Some(page.as_str()))
|
||||
.filter(|id| !wanted.iter().any(|uid| keep::entry_id(&page, *uid) == **id))
|
||||
.cloned()
|
||||
.collect();
|
||||
for id in stale {
|
||||
files::remove_kept(files, &id);
|
||||
soon(100.0);
|
||||
}
|
||||
keep_track.retain(|uid, _| wanted.contains(uid));
|
||||
if let Some(e) = error {
|
||||
app.set_status(e, true);
|
||||
}
|
||||
if let (Some(ctx), Some(ms)) = (ctx, wake) {
|
||||
ctx.request_repaint_after(std::time::Duration::from_secs_f64(ms / 1000.0));
|
||||
}
|
||||
on && all_kept
|
||||
}
|
||||
|
||||
/// Offer the copies an earlier visit kept (#10220): "Restore unsaved design: <name>,
|
||||
/// changed <time>?", one window for all of them, each with Restore, Open saved version
|
||||
/// (when it has a file) and Discard.
|
||||
fn keep_prompt(&mut self, ctx: &egui::Context) {
|
||||
let offered = self.files.keep_offered.borrow().clone();
|
||||
if offered.is_empty() || !self.files.ready.get() {
|
||||
return;
|
||||
}
|
||||
let now = (js_sys::Date::now() / 1000.0) as u64;
|
||||
let rows: Vec<_> = offered
|
||||
.iter()
|
||||
.map(|e| solidcraft_ui_egui::recovery_ui::BrowserCopy {
|
||||
name: &e.name,
|
||||
when: solidcraft_ui_egui::recovery_ui::ago_between(now, (e.changed_at / 1000.0) as u64),
|
||||
open_saved: keep::answers(e, self.saved_exists(e)).contains(&keep::Answer::OpenSaved),
|
||||
})
|
||||
.collect();
|
||||
let act = solidcraft_ui_egui::recovery_ui::browser_copies_window(ctx, &self.app.preferences.language, &rows);
|
||||
let Some((index, answer)) = act else { return };
|
||||
let Some(entry) = offered.get(index) else { return };
|
||||
let answer = match answer {
|
||||
solidcraft_ui_egui::recovery_ui::CopyAnswer::Restore => keep::Answer::Restore,
|
||||
solidcraft_ui_egui::recovery_ui::CopyAnswer::OpenSaved => keep::Answer::OpenSaved,
|
||||
solidcraft_ui_egui::recovery_ui::CopyAnswer::Discard => keep::Answer::Discard,
|
||||
};
|
||||
self.keep_answer(entry, answer);
|
||||
}
|
||||
|
||||
/// Whether a kept copy's design file can still be opened: in the browser's storage, or a
|
||||
/// file on the computer this browser remembers.
|
||||
fn saved_exists(&self, e: &keep::Entry) -> bool {
|
||||
e.path.as_deref().is_some_and(|p| vfs::exists(p) || disk::parse(p).is_some_and(|(id, _)| self.files.disk_known.borrow().contains(id)))
|
||||
}
|
||||
|
||||
/// Act on an answer in the "Restore unsaved design?" window (`keep::plan`).
|
||||
fn keep_answer(&mut self, entry: &keep::Entry, answer: keep::Answer) {
|
||||
let Some(plan) = keep::plan(answer, entry, self.saved_exists(entry)) else { return };
|
||||
if plan.open_copy {
|
||||
let copy = match files::prepare_kept(&entry.id, entry.path.as_deref()) {
|
||||
Ok(copy) => copy,
|
||||
Err(error) => {
|
||||
self.app.set_status(error, true);
|
||||
return;
|
||||
}
|
||||
};
|
||||
self.app.open_path(©);
|
||||
if self.app.session.path.as_deref() != Some(copy.as_str()) {
|
||||
// Not opened (the status says why): it stays offered.
|
||||
return;
|
||||
}
|
||||
// It is its own design again (saving goes to its file), with its changes unsaved.
|
||||
self.app.session.path = entry.path.clone();
|
||||
self.app.session.doc_mut().name = entry.name.clone();
|
||||
self.app.session.mark_unsaved();
|
||||
self.app.home.open = false;
|
||||
}
|
||||
if let Some(path) = &plan.open_saved {
|
||||
if vfs::exists(path) {
|
||||
self.app.open_path(path);
|
||||
if !keep::is_open(self.app.session.path.as_deref(), path) {
|
||||
// Not opened (the status says why): the copy stays offered.
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// A file on the computer: the browser asks for access again, in this click.
|
||||
// The copy goes once the file has opened, not before: if access is refused,
|
||||
// the picker is dismissed or the file is gone, it stays offered.
|
||||
let (files, id) = (self.files.clone(), entry.id.clone());
|
||||
disk::restore(
|
||||
&self.files,
|
||||
path,
|
||||
Some(Box::new(move || {
|
||||
files.keep_offered.borrow_mut().retain(|e| e.id != id);
|
||||
files::remove_kept(&files, &id);
|
||||
})),
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if plan.drop_copy {
|
||||
self.files.keep_offered.borrow_mut().retain(|e| e.id != entry.id);
|
||||
files::remove_kept(&self.files, &entry.id);
|
||||
}
|
||||
}
|
||||
|
||||
/// The start page's recent designs are the stored ones: the remembered order first (those
|
||||
@@ -735,7 +492,7 @@ mod web {
|
||||
files::pick_from_disk(&self.files, Intent::Open);
|
||||
}
|
||||
if ui_kit::button(ui, "Insert from computer…", ui_kit::Button::Normal)
|
||||
.backing_on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_drawings_hint"))
|
||||
.on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_drawings_hint"))
|
||||
.clicked()
|
||||
{
|
||||
files::pick_from_disk(&self.files, Intent::Insert);
|
||||
@@ -789,7 +546,7 @@ mod web {
|
||||
let design = solidcraft_engine::io::is_design_path(&e.name);
|
||||
if !design
|
||||
&& ui_kit::button(ui, "Insert", ui_kit::Button::Small)
|
||||
.backing_on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_stored_drawings_hint"))
|
||||
.on_hover_text(solidcraft_ui_egui::i18n::tr("web.files.insert_stored_drawings_hint"))
|
||||
.clicked()
|
||||
{
|
||||
act = Some(("insert", e.name.clone()));
|
||||
@@ -799,7 +556,7 @@ mod web {
|
||||
self.rename_focus = true;
|
||||
}
|
||||
if ui_kit::button(ui, "Save to computer…", ui_kit::Button::Small)
|
||||
.backing_on_hover_text("A copy in a file you choose; the stored design stays here")
|
||||
.on_hover_text("A copy in a file you choose; the stored design stays here")
|
||||
.clicked()
|
||||
{
|
||||
act = Some(("save_copy", e.name.clone()));
|
||||
@@ -926,13 +683,6 @@ mod web {
|
||||
files::autosave(&self.files, None);
|
||||
}
|
||||
|
||||
/// Whether leaving the page asks through the browser's own box (`keep::asks_before_unload`).
|
||||
fn asks_before_unload(&self) -> bool {
|
||||
let any = solidcraft_ui_egui::documents::any_dirty(&self.app);
|
||||
let prefs = &self.app.preferences;
|
||||
keep::asks_before_unload(self.launch.persists(), prefs.keep_unsaved_in_browser, prefs.keep_unsaved_also_asks, any, self.all_kept)
|
||||
}
|
||||
|
||||
/// Whether the shell already has the on-demand face the live language needs.
|
||||
fn script_font_ready(&self) -> bool {
|
||||
match solidcraft_ui_egui::fonts::web_script_font_kind(solidcraft_ui_egui::i18n::effective_language(&self.app.preferences.language)) {
|
||||
@@ -1044,7 +794,7 @@ mod web {
|
||||
self.api.poll();
|
||||
self.space_mouse.logic(&mut self.app, ctx);
|
||||
self.files_tick(ctx);
|
||||
UNSAVED.with(|u| u.set(self.asks_before_unload()));
|
||||
UNSAVED.with(|u| u.set(solidcraft_ui_egui::documents::any_dirty(&self.app)));
|
||||
DESIGN_SHOWN.with(|d| d.set(solidcraft_ui_egui::toolbar::design_shown(&self.app)));
|
||||
}
|
||||
fn raw_input_hook(&mut self, ctx: &egui::Context, raw: &mut egui::RawInput) {
|
||||
@@ -1064,7 +814,7 @@ mod web {
|
||||
}
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _frame: &mut eframe::Frame) {
|
||||
self.linked.tick(&mut self.app, ui.ctx());
|
||||
UNSAVED.with(|u| u.set(self.asks_before_unload()));
|
||||
UNSAVED.with(|u| u.set(self.launch.persists() && solidcraft_ui_egui::documents::any_dirty(&self.app)));
|
||||
if self.launch.embed {
|
||||
ui_kit::Container::new(ui.ctx(), ui_kit::Surface::Chrome).edge("embed_header", ui_kit::Edge::Top).show(ui, |ui| {
|
||||
if ui_kit::button(ui, solidcraft_ui_egui::i18n::tr("web.open_full"), ui_kit::Button::Normal).clicked()
|
||||
@@ -1079,25 +829,19 @@ mod web {
|
||||
if self.launch.persists() {
|
||||
self.file_panel(&ctx);
|
||||
self.recovery_prompt(&ctx);
|
||||
self.keep_prompt(&ctx);
|
||||
}
|
||||
self.watch_text_for_font(&ctx);
|
||||
self.store_prefs(&ctx);
|
||||
}
|
||||
#[cfg(target_arch = "wasm32")]
|
||||
fn as_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
|
||||
Some(&mut *self)
|
||||
}
|
||||
fn persist_egui_memory(&self) -> bool {
|
||||
// eframe keeps this under one fixed key: only stable uses it, so a pre-release never rewrites it.
|
||||
self.launch.persists() && crate::channel::current().is_empty()
|
||||
self.launch.persists()
|
||||
}
|
||||
fn save(&mut self, storage: &mut dyn eframe::Storage) {
|
||||
if !self.launch.persists() {
|
||||
return;
|
||||
}
|
||||
let prefs = self.persisted_prefs();
|
||||
storage.set_string(&crate::prefs_key(), prefs);
|
||||
storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, prefs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1107,7 +851,7 @@ mod web {
|
||||
|
||||
/// Where `?webgl` is remembered. The installed app starts from its manifest's start URL, which
|
||||
/// has no query, so the choice has to outlive the page it was made on.
|
||||
const WEBGL_KEY: &str = "solidcraft.webgl"; // stored under crate::channel::key
|
||||
const WEBGL_KEY: &str = "solidcraft.webgl";
|
||||
|
||||
/// Render with WebGL2: asked for now (`?webgl`, remembered) or before (until `?webgpu`).
|
||||
fn webgl() -> bool {
|
||||
@@ -1115,17 +859,17 @@ mod web {
|
||||
let q = query();
|
||||
if crate::query_flag(&q, "webgl") {
|
||||
if let Some(s) = &storage {
|
||||
let _ = s.set_item(&crate::channel::key(WEBGL_KEY), "1");
|
||||
let _ = s.set_item(WEBGL_KEY, "1");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
if crate::query_flag(&q, "webgpu") {
|
||||
if let Some(s) = &storage {
|
||||
let _ = s.remove_item(&crate::channel::key(WEBGL_KEY));
|
||||
let _ = s.remove_item(WEBGL_KEY);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
storage.and_then(|s| s.get_item(&crate::channel::key(WEBGL_KEY)).ok().flatten()).is_some()
|
||||
storage.and_then(|s| s.get_item(WEBGL_KEY).ok().flatten()).is_some()
|
||||
}
|
||||
|
||||
/// Fetch font bytes asynchronously through the browser.
|
||||
@@ -1162,8 +906,7 @@ mod web {
|
||||
{
|
||||
create.instance_descriptor.backends = eframe::wgpu::Backends::GL;
|
||||
}
|
||||
let runner = eframe::WebRunner::new();
|
||||
let result = runner
|
||||
let result = eframe::WebRunner::new()
|
||||
.start(
|
||||
canvas,
|
||||
options,
|
||||
@@ -1175,7 +918,7 @@ mod web {
|
||||
}
|
||||
let (panel, opener, saver, shown) = (files.clone(), files.clone(), files.clone(), files.clone());
|
||||
let (restorable, restorer) = (files.clone(), files.clone());
|
||||
let services = if !launch.persists() { Services::default() } else { Services {
|
||||
let services = if launch.embed { Services::default() } else { Services {
|
||||
// Open shows the browser's open dialog for a design where it has one
|
||||
// (the file keeps its handle, so Save writes back to it), else the
|
||||
// browser's files window. A command that reads another kind of file
|
||||
@@ -1207,7 +950,7 @@ mod web {
|
||||
can_restore: Some(Box::new(move |path: &str| {
|
||||
disk::parse(path).is_some_and(|(id, _)| restorable.disk_known.borrow().contains(id))
|
||||
})),
|
||||
restore_file: Some(Box::new(move |path: &str| disk::restore(&restorer, path, None))),
|
||||
restore_file: Some(Box::new(move |path: &str| disk::restore(&restorer, path))),
|
||||
download_design: Some(Box::new(files::download)),
|
||||
easyprint_upload: Some(std::sync::Arc::new(crate::easyprint::upload)),
|
||||
browser_files: Some(Box::new(move || shown.panel.set(true))),
|
||||
@@ -1219,14 +962,13 @@ mod web {
|
||||
let (sender, receiver) = std::sync::mpsc::channel();
|
||||
let mut app = SolveApp::new(Session::default(), services).with_control(receiver);
|
||||
app.embedded = launch.embed;
|
||||
app.embed_editor = launch.editor;
|
||||
// Listening before reading: settings another tab writes from now on are
|
||||
// newer than those read here.
|
||||
let other_tab_prefs = PrefsInbox::default();
|
||||
if launch.persists() {
|
||||
hear_settings_from_other_tabs(&window, &cc.egui_ctx, other_tab_prefs.clone());
|
||||
}
|
||||
if launch.persists() && let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(&crate::channel::key(k)))) {
|
||||
if launch.persists() && let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))) {
|
||||
app.load_prefs(&p);
|
||||
}
|
||||
if let Some(rs) = &cc.wgpu_render_state {
|
||||
@@ -1274,18 +1016,10 @@ mod web {
|
||||
wheel_gestures: solidcraft_ui_egui::WebWheelGestures::default(),
|
||||
prefs_writer: crate::PrefsWriter::default(),
|
||||
other_tab_prefs,
|
||||
keep_track: Default::default(),
|
||||
all_kept: false,
|
||||
}))
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
if result.is_ok()
|
||||
&& crate::Launch::parse(&query()).persists()
|
||||
&& let Some(window) = web_sys::window()
|
||||
{
|
||||
keep_when_hidden(&window, &runner);
|
||||
}
|
||||
if let Some(el) = document.get_element_by_id(LOADING_ID) {
|
||||
match result {
|
||||
Ok(()) => dismiss_loading(el),
|
||||
@@ -1338,17 +1072,6 @@ mod tests {
|
||||
assert!(full.fragment().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn embed_editor_needs_no_design_link_and_keeps_nothing_in_the_browser() {
|
||||
let editor = Launch::parse("?embed=editor");
|
||||
assert!(editor.editor && !editor.embed && !editor.persists());
|
||||
let linked = Launch::parse("?embed=editor&open=https://example.org/a.sc");
|
||||
assert!(linked.editor && !linked.embed, "an editing embed may open a linked design, still editable");
|
||||
let viewer = Launch::parse("?embed=viewer&open=https://example.org/a.sc");
|
||||
assert!(viewer.embed && !viewer.editor, "any other value is the viewer embed, as before");
|
||||
assert!(!Launch::parse("?other=editor").editor);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn design_links_refuse_hostile_schemes_credentials_and_sizes() {
|
||||
assert!(design_url("https://example.org/arm.solidcraft?download=1", "http://solidcraft.io").is_ok());
|
||||
@@ -1448,10 +1171,9 @@ mod tests {
|
||||
#[test]
|
||||
fn only_settings_another_tab_wrote_are_loaded() {
|
||||
let key = solidcraft_ui_egui::prefs::STORAGE_KEY;
|
||||
assert_eq!(settings_written(key, Some(key), Some("{}".into())).as_deref(), Some("{}"));
|
||||
assert_eq!(settings_written(key, Some("solidcraft.webgl"), Some("1".into())), None);
|
||||
assert_eq!(settings_written(key, Some(key), None), None, "removed settings leave this tab's");
|
||||
assert_eq!(settings_written(key, None, None), None, "clearing all storage leaves this tab's settings");
|
||||
assert_eq!(settings_written(key, Some("rc:solidcraft.prefs"), Some("{}".into())), None, "another channel's settings are not loaded");
|
||||
assert_eq!(settings_written(Some(key), Some("{}".into())).as_deref(), Some("{}"));
|
||||
assert_eq!(settings_written(Some("solidcraft.webgl"), Some("1".into())), None);
|
||||
assert_eq!(settings_written(Some(key), None), None, "removed settings leave this tab's");
|
||||
assert_eq!(settings_written(None, None), None, "clearing all storage leaves this tab's settings");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,9 @@ publish = false
|
||||
default-run = "solidcraft"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu", "bundled-render-library", "profile"]
|
||||
default = ["raytrace-cpu", "bundled-render-library"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
# The profiler, compiled in so any build can say where its time goes (the Profile operations
|
||||
# preference, SOLIDCRAFT_PROFILE=1, diagnostics.profile); it records nothing until asked.
|
||||
profile = ["solidcraft-engine/profile"]
|
||||
|
||||
# The workspace lints, except unsafe_code: denied, not forbidden, so that src/open_documents.rs
|
||||
# (macOS file-open events), src/native_crash.rs (Windows exception filter), src/native_crash_macos.rs
|
||||
@@ -81,7 +78,6 @@ windows-sys = { version = "0.61", features = [
|
||||
"Win32_UI_HiDpi",
|
||||
"Win32_UI_WindowsAndMessaging",
|
||||
] }
|
||||
winreg = "0.56"
|
||||
|
||||
[target.'cfg(target_os = "macos")'.dependencies]
|
||||
muda = { version = "0.21.2", default-features = false }
|
||||
@@ -93,7 +89,7 @@ hidapi = { version = "2.6.7", default-features = false, features = ["macos-share
|
||||
# Files opened from Finder and the Dock (src/open_documents.rs), and the fullscreen title-bar
|
||||
# inset (src/main.rs); all already come with eframe and rfd.
|
||||
objc2 = "0.6"
|
||||
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSAccessibility", "NSApplication", "NSResponder", "NSWindow", "NSWorkspace"] }
|
||||
objc2-app-kit = { version = "0.3", default-features = false, features = ["std", "NSApplication", "NSResponder", "NSWindow"] }
|
||||
objc2-foundation = { version = "0.3", default-features = false, features = ["std", "block2", "NSArray", "NSNotification", "NSOperation", "NSProcessInfo", "NSString", "NSURL"] }
|
||||
block2 = "0.6"
|
||||
# Native crash capture (src/native_crash_macos.rs): the signal handler, the faulting module
|
||||
|
||||
@@ -1,100 +0,0 @@
|
||||
//! Is the file chooser that opens the desktop's own? (#10159)
|
||||
//!
|
||||
//! `rfd` opens the file chooser through the XDG desktop portal; without the portal it falls
|
||||
//! back to zenity, a window that does not belong to the app and that the app never mentions.
|
||||
//! The desktop's own chooser is the portal one, so the app has to know when it is not the one
|
||||
//! that opens, and say so in the session log.
|
||||
//!
|
||||
//! The session bus is asked once, on a thread of its own: a bus that answers late must not
|
||||
//! hold the window. What it answered is kept, and read when a file is opened.
|
||||
|
||||
use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
|
||||
|
||||
/// The portal's bus name: the desktop's own file chooser lives behind it.
|
||||
const PORTAL: &str = "org.freedesktop.portal.Desktop";
|
||||
|
||||
/// The bus has not answered yet.
|
||||
const UNKNOWN: u8 = 0;
|
||||
/// There is no session bus to reach the portal on.
|
||||
const NO_SESSION_BUS: u8 = 1;
|
||||
/// The bus is there but the portal does not answer on it.
|
||||
const NO_PORTAL_SERVICE: u8 = 2;
|
||||
/// The bus is there and the portal answers on it.
|
||||
const PORTAL_THERE: u8 = 3;
|
||||
|
||||
/// What the session bus said about the portal.
|
||||
static BUS: AtomicU8 = AtomicU8::new(UNKNOWN);
|
||||
/// A file chooser is opening: whether it is the desktop's own is to be said.
|
||||
static OPENING: AtomicBool = AtomicBool::new(false);
|
||||
|
||||
/// Ask the session bus, on a thread of its own, whether the portal is there.
|
||||
pub fn start(ctx: &egui::Context) {
|
||||
let thread_ctx = ctx.clone();
|
||||
let probe = move || {
|
||||
let answer = match session_bus() {
|
||||
None => NO_SESSION_BUS,
|
||||
Some(connection) => match portal_on(&connection) {
|
||||
true => PORTAL_THERE,
|
||||
false => NO_PORTAL_SERVICE,
|
||||
},
|
||||
};
|
||||
BUS.store(answer, Ordering::Relaxed);
|
||||
// The answer is in: a file chooser waiting for it can be told now.
|
||||
thread_ctx.request_repaint();
|
||||
};
|
||||
if let Err(e) = std::thread::Builder::new().name("desktop-chooser".into()).spawn(probe) {
|
||||
log::warn!("file chooser: no thread to ask the session bus: {e}");
|
||||
// Nothing will answer: the desktop's own chooser cannot be told apart from the start.
|
||||
BUS.store(PORTAL_THERE, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
|
||||
/// The session bus, when there is one to talk to.
|
||||
fn session_bus() -> Option<zbus::blocking::Connection> {
|
||||
zbus::blocking::Connection::session().ok()
|
||||
}
|
||||
|
||||
/// Whether the portal answers on the bus: it is either running on it or waiting to be started.
|
||||
fn portal_on(connection: &zbus::blocking::Connection) -> bool {
|
||||
let Ok(bus) = zbus::blocking::fdo::DBusProxy::new(connection) else { return false };
|
||||
let named = |names: Vec<zbus::names::OwnedBusName>| names.iter().any(|name| name.as_str() == PORTAL);
|
||||
// A portal that is running is on the bus; one that is not running yet is still there when
|
||||
// the bus can start it, which is what the portal's service file is for.
|
||||
match (bus.list_names().map(named), bus.list_activatable_names().map(named)) {
|
||||
(Ok(running), Ok(startable)) => running || startable,
|
||||
(Ok(running), Err(_)) => running,
|
||||
(Err(_), Ok(startable)) => startable,
|
||||
// The bus does not answer: nothing answers on it.
|
||||
(Err(_), Err(_)) => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// A file chooser is opening: ask for a frame, so that the reason it is not the desktop's own
|
||||
/// is said (#10159).
|
||||
pub fn note_opening(ctx: &egui::Context) {
|
||||
OPENING.store(true, Ordering::Relaxed);
|
||||
ctx.request_repaint();
|
||||
}
|
||||
|
||||
/// The reason the file chooser that is opening is not the desktop's own, if it is not.
|
||||
///
|
||||
/// The decision is the one the test states: without a session bus there is no bus to reach the
|
||||
/// portal on; with a bus but no portal on it, `rfd` falls back to zenity; and under XWayland the
|
||||
/// window is an X11 one while the session is a Wayland one, which the portal refuses to parent
|
||||
/// its chooser to.
|
||||
pub fn take_reason() -> Option<&'static str> {
|
||||
if !OPENING.load(Ordering::Relaxed) {
|
||||
return None;
|
||||
}
|
||||
let (dbus_session, portal_service) = match BUS.load(Ordering::Relaxed) {
|
||||
NO_SESSION_BUS => (false, false),
|
||||
NO_PORTAL_SERVICE => (true, false),
|
||||
PORTAL_THERE => (true, true),
|
||||
// The bus has not answered yet: wait for it, and say nothing yet.
|
||||
_ => return None,
|
||||
};
|
||||
OPENING.store(false, Ordering::Relaxed);
|
||||
let wayland_display = std::env::var_os("WAYLAND_DISPLAY").is_some();
|
||||
let x11_display = super::window_system_now() == Some("x11");
|
||||
super::desktop_chooser_fallback(dbus_session, portal_service, wayland_display, x11_display)
|
||||
}
|
||||
@@ -1,63 +0,0 @@
|
||||
//! Cached accessibility signal, observed only while a theme offers blur. No UI-thread IO.
|
||||
use std::sync::{
|
||||
Arc,
|
||||
atomic::{AtomicBool, AtomicU8, Ordering},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
struct Reading {
|
||||
needed: AtomicBool,
|
||||
started: AtomicBool,
|
||||
value: AtomicU8,
|
||||
}
|
||||
|
||||
pub fn cached(ctx: &egui::Context) -> Box<dyn Fn(bool) -> Option<bool>> {
|
||||
let state = Arc::new(Reading::default());
|
||||
let ctx = ctx.clone();
|
||||
Box::new(move |needed| {
|
||||
state.needed.store(needed, Ordering::Relaxed);
|
||||
if needed && !state.started.swap(true, Ordering::Relaxed) {
|
||||
let weak = Arc::downgrade(&state);
|
||||
let ctx = ctx.clone();
|
||||
if let Err(error) = std::thread::Builder::new().name("desktop-transparency".into()).spawn(move || {
|
||||
while let Some(state) = weak.upgrade() {
|
||||
if state.needed.load(Ordering::Relaxed) {
|
||||
let code = match read() {
|
||||
Some(true) => 1,
|
||||
Some(false) => 2,
|
||||
None => 0,
|
||||
};
|
||||
if state.value.swap(code, Ordering::Relaxed) != code {
|
||||
ctx.request_repaint();
|
||||
}
|
||||
}
|
||||
drop(state);
|
||||
std::thread::sleep(std::time::Duration::from_secs(5));
|
||||
}
|
||||
}) {
|
||||
log::warn!("desktop transparency: could not observe accessibility setting: {error}");
|
||||
}
|
||||
}
|
||||
match state.value.load(Ordering::Relaxed) {
|
||||
1 => Some(true),
|
||||
2 => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn read() -> Option<bool> {
|
||||
let key =
|
||||
winreg::RegKey::predef(winreg::enums::HKEY_CURRENT_USER).open_subkey(r"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize").ok()?;
|
||||
match key.get_value::<u32, _>("EnableTransparency").ok()? {
|
||||
0 => Some(true),
|
||||
1 => Some(false),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
fn read() -> Option<bool> {
|
||||
objc2::rc::autoreleasepool(|_| Some(objc2_app_kit::NSWorkspace::sharedWorkspace().accessibilityDisplayShouldReduceTransparency()))
|
||||
}
|
||||
@@ -23,11 +23,7 @@ mod app_icon;
|
||||
mod come_forward;
|
||||
mod control_server;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod desktop_chooser;
|
||||
#[cfg(target_os = "linux")]
|
||||
mod desktop_theme;
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
mod desktop_transparency;
|
||||
mod device_lost;
|
||||
#[cfg(target_os = "windows")]
|
||||
mod native_crash;
|
||||
@@ -244,12 +240,6 @@ impl eframe::App for App {
|
||||
menu.tick(&mut self.solve, ctx);
|
||||
}
|
||||
self.solve.logic(ctx);
|
||||
// The file chooser that opened is not the desktop's own: the user has to be told (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
if let Some(reason) = desktop_chooser::take_reason() {
|
||||
let text = self.solve.tr(reason).to_string();
|
||||
self.solve.set_status(text, false);
|
||||
}
|
||||
self.dialog_folder.design(self.solve.session.path.as_deref());
|
||||
#[cfg(any(target_os = "windows", target_os = "linux", target_os = "macos"))]
|
||||
if !self.solve.file_picker_busy() {
|
||||
@@ -303,8 +293,6 @@ impl eframe::App for App {
|
||||
log::warn!("a picture was still being drawn as the app closed");
|
||||
}
|
||||
solidcraft_ui_egui::recovery_ui::close(&mut self.solve);
|
||||
// A GPU path tracer setup still running did not stop the app (#9236).
|
||||
solidcraft_ui_egui::render_workspace::close(&mut self.solve);
|
||||
solidcraft_ui_egui::open_elsewhere::close(&mut self.solve);
|
||||
// Before a restart (`Drop for App`) starts the next instance, which would otherwise get
|
||||
// nothing from the 3D mouse driver (`solidcraft_3dx_client::stop`).
|
||||
@@ -481,19 +469,12 @@ fn fullscreen_content_insets(raw: &mut egui::RawInput, title_height: f32, zoom:
|
||||
|
||||
fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services {
|
||||
let parent = rfd::AsyncFileDialog::new().set_parent(cc);
|
||||
// The file chooser that opens may not be the desktop's own: the user is told when it does
|
||||
// not (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
let chooser_ctx = cc.egui_ctx.clone();
|
||||
let open_parent = parent.clone();
|
||||
let program_parent = parent.clone();
|
||||
let file_parent = parent.clone();
|
||||
let (open_folder, file_folder, save_folder) = (folder.clone(), folder.clone(), folder.clone());
|
||||
Services {
|
||||
pick_open: Some(Box::new(move |first: &[&str]| {
|
||||
// The chooser that opens may not be the desktop's own (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
desktop_chooser::note_opening(&chooser_ctx);
|
||||
// The file type the command reads comes first, so the picker opens on it (#1801).
|
||||
let mut dialog = open_folder.start(open_parent.clone());
|
||||
if !first.is_empty() {
|
||||
@@ -558,10 +539,6 @@ fn services(cc: &eframe::CreationContext<'_>, folder: &DialogFolder) -> Services
|
||||
desktop_dark: Some(desktop_theme::start(&cc.egui_ctx)),
|
||||
#[cfg(not(target_os = "linux"))]
|
||||
desktop_dark: None,
|
||||
#[cfg(any(target_os = "windows", target_os = "macos"))]
|
||||
reduce_transparency: Some(desktop_transparency::cached(&cc.egui_ctx)),
|
||||
#[cfg(not(any(target_os = "windows", target_os = "macos")))]
|
||||
reduce_transparency: None,
|
||||
http_get: Some(std::sync::Arc::new(http_get)),
|
||||
http_download: Some(std::sync::Arc::new(http_download)),
|
||||
// Already an installed app: only the browser build offers to install.
|
||||
@@ -802,28 +779,6 @@ fn graphics() -> Option<serde_json::Value> {
|
||||
graphics_path().and_then(|p| std::fs::read_to_string(p).ok()).and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
|
||||
}
|
||||
|
||||
fn restore_preferences(app: &mut SolveApp, stored_ui: Option<&str>, saved_graphics: Option<&serde_json::Value>, safe_mode: bool) {
|
||||
if let Some(p) = stored_ui.filter(|_| !safe_mode) {
|
||||
app.load_prefs(p);
|
||||
}
|
||||
// Even safe mode edits the real graphics.json. Keep its choices in Preferences so an
|
||||
// anti-aliasing change cannot erase them; safe mode's renderer still uses its own defaults.
|
||||
restore_graphics_preferences(app, saved_graphics);
|
||||
}
|
||||
|
||||
/// Restore startup choices after loading the separately saved UI preferences.
|
||||
fn restore_graphics_preferences(app: &mut SolveApp, saved: Option<&serde_json::Value>) {
|
||||
app.preferences.graphics_adapter = saved.and_then(|v| v.get("adapter").and_then(serde_json::Value::as_str)).unwrap_or_default().to_string();
|
||||
// graphics.json chooses the window before app.ron is loaded. Reflect that saved choice,
|
||||
// not the current session's window system (which an environment override may change).
|
||||
app.preferences.window_system = saved
|
||||
.and_then(|v| v.get("window_system").and_then(serde_json::Value::as_str))
|
||||
.map(str::trim)
|
||||
.filter(|choice| matches!(*choice, "wayland" | "x11"))
|
||||
.unwrap_or("auto")
|
||||
.to_string();
|
||||
}
|
||||
|
||||
/// The graphics backend preference (`graphics.json`'s `backend`: `auto`, `dx12`, `vulkan`, `gl`).
|
||||
fn backend_preference() -> Option<String> {
|
||||
graphics().and_then(|v| v.get("backend").and_then(serde_json::Value::as_str).map(str::to_string))
|
||||
@@ -940,8 +895,6 @@ fn main() -> eframe::Result {
|
||||
if let Err(e) = startup::relaunch_with_version_compat() {
|
||||
log::warn!("startup: OCLP check failed (continuing normally): {e}");
|
||||
}
|
||||
// SOLIDCRAFT_PROFILE=1: the profiler records from the start (diagnostics.profile reads it).
|
||||
solidcraft_engine::timing::profile_from_env();
|
||||
// Validate before legacy adoption, crash markers or any user preference read.
|
||||
let settings = settings_dir().map_err(|error| eframe::Error::AppCreation(error.into()))?;
|
||||
if std::env::var_os("SOLIDCRAFT_SETTINGS_DIR").is_none() {
|
||||
@@ -1042,8 +995,6 @@ fn main() -> eframe::Result {
|
||||
solidcraft_engine::crash::install(dir, build)
|
||||
})
|
||||
.unwrap_or_default();
|
||||
// The recovery folder also holds the reopen guard marker (#5064); read once, reused below.
|
||||
let recovery_dir = solidcraft_engine::recovery::default_dir();
|
||||
// A crash in native code (a graphics driver) says where it happened: the exception or signal
|
||||
// and the module on Windows and macOS, and on every system the startup step the app was in.
|
||||
#[cfg(target_os = "windows")]
|
||||
@@ -1126,10 +1077,6 @@ fn main() -> eframe::Result {
|
||||
solidcraft_ui_egui::fonts::load_system_fonts_in_background(&cc.egui_ctx);
|
||||
mark("fonts");
|
||||
let dialog_folder = DialogFolder::default();
|
||||
// The file chooser that opens may not be the desktop's own: the session bus is asked
|
||||
// now, so the answer is there when a file is opened (#10159).
|
||||
#[cfg(target_os = "linux")]
|
||||
desktop_chooser::start(&cc.egui_ctx);
|
||||
let mut app = SolveApp::new(Session::default(), services(cc, &dialog_folder));
|
||||
app.apply_off_thread = Some(solidcraft_ui_egui::dialogs::APPLY_AT_ONCE);
|
||||
app.custom_titlebar = custom_titlebar;
|
||||
@@ -1150,8 +1097,9 @@ fn main() -> eframe::Result {
|
||||
app.integrated_titlebar = cfg!(target_os = "macos");
|
||||
app.cpu_meshing = safe_mode;
|
||||
app.graphics_fallback = fallback_note.clone();
|
||||
let stored_ui = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k)));
|
||||
restore_preferences(&mut app, stored_ui.as_deref(), graphics().as_ref(), safe_mode);
|
||||
if let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))).filter(|_| !safe_mode) {
|
||||
app.load_prefs(&p);
|
||||
}
|
||||
if let Some(path) = &theme_file
|
||||
&& let Err(e) = solidcraft_ui_egui::prefs::follow_theme_file_for_run(&mut app, path)
|
||||
{
|
||||
@@ -1164,6 +1112,10 @@ fn main() -> eframe::Result {
|
||||
lost_at: None,
|
||||
reason: String::new(),
|
||||
});
|
||||
if !safe_mode {
|
||||
app.preferences.graphics_adapter =
|
||||
graphics().and_then(|v| v.get("adapter").and_then(serde_json::Value::as_str).map(str::to_string)).unwrap_or_default();
|
||||
}
|
||||
if let Some(rs) = &cc.wgpu_render_state {
|
||||
app.services.graphics_adapters = rs
|
||||
.available_adapters
|
||||
@@ -1236,25 +1188,8 @@ fn main() -> eframe::Result {
|
||||
};
|
||||
// No design given: the designs open at the last quit, when the preference asks (#5542).
|
||||
// Not when driven over the control channel, whose clients expect a fresh design.
|
||||
// A design that stopped the last session while it was being reopened is skipped this
|
||||
// time, so one bad file cannot brick every launch (#5064).
|
||||
app.reopen_guard_dir = recovery_dir.clone();
|
||||
let files = if files.is_empty() && !sample && control_port.is_none() {
|
||||
let (reopen, skipped) = solidcraft_ui_egui::documents::reopen_plan(&app, recovery_dir.as_deref());
|
||||
if !skipped.is_empty() {
|
||||
let names: Vec<String> = skipped
|
||||
.iter()
|
||||
.map(|p| std::path::Path::new(p).file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_else(|| p.clone()))
|
||||
.collect();
|
||||
notes.push(format!(
|
||||
"The last session stopped while reopening {}. It was not reopened this time; open it yourself if you need it.",
|
||||
names.join(", ")
|
||||
));
|
||||
}
|
||||
reopen
|
||||
} else {
|
||||
files
|
||||
};
|
||||
let files =
|
||||
if files.is_empty() && !sample && control_port.is_none() { solidcraft_ui_egui::documents::designs_to_reopen(&app) } else { files };
|
||||
for f in &files {
|
||||
app.open_path_async(f);
|
||||
}
|
||||
@@ -1270,9 +1205,9 @@ fn main() -> eframe::Result {
|
||||
app.home.show(rev);
|
||||
}
|
||||
// Autosave, and offer what crashed sessions left behind.
|
||||
if let Some(dir) = recovery_dir.as_ref() {
|
||||
if let Some(dir) = solidcraft_engine::recovery::default_dir() {
|
||||
let every = if app.autosave_minutes > 0.0 { app.autosave_minutes * 60.0 } else { f64::MAX / 4.0 };
|
||||
solidcraft_ui_egui::recovery_ui::start(&mut app, dir, std::time::Duration::from_secs_f64(every.min(1e9)));
|
||||
solidcraft_ui_egui::recovery_ui::start(&mut app, &dir, std::time::Duration::from_secs_f64(every.min(1e9)));
|
||||
// Safe mode keeps autosaving but offers nothing: the entries wait for a normal start.
|
||||
if safe_mode {
|
||||
app.recoverable.clear();
|
||||
@@ -1460,47 +1395,6 @@ fn prefer_x11(display: bool, wayland_display: bool, desktop: Option<&str>, wayla
|
||||
display && !(niri && wayland_display)
|
||||
}
|
||||
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
const CHOOSER_NO_SESSION_BUS: &str = "file.open.portal_fallback.no_session_bus";
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
const CHOOSER_NO_PORTAL_SERVICE: &str = "file.open.portal_fallback.no_portal_service";
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
const CHOOSER_XWAYLAND: &str = "file.open.portal_fallback.xwayland";
|
||||
|
||||
/// Whether the chooser that opens is NOT the desktop's own, and why.
|
||||
///
|
||||
/// `rfd` opens the file chooser through the XDG desktop portal; when the portal is not there it
|
||||
/// falls back to zenity, which opens a window that does not belong to SolidCraft and that the
|
||||
/// application never mentions. The application has to know that: this decision is what the warning
|
||||
/// is built on.
|
||||
///
|
||||
/// * `dbus_session`: the session bus is there (`DBUS_SESSION_BUS_ADDRESS`).
|
||||
/// * `portal_service`: the file chooser portal answers on it.
|
||||
/// * `wayland_display`: a Wayland session runs (`WAYLAND_DISPLAY`).
|
||||
/// * `x11_display`: the window is an X11 one (`DISPLAY`, or XWayland).
|
||||
///
|
||||
/// `None` means the desktop's own chooser is the one that opens and there is nothing to say;
|
||||
/// `Some` carries the reason to report.
|
||||
#[cfg(all(unix, not(target_os = "macos")))]
|
||||
#[cfg_attr(not(target_os = "linux"), allow(dead_code))]
|
||||
fn desktop_chooser_fallback(dbus_session: bool, portal_service: bool, wayland_display: bool, x11_display: bool) -> Option<&'static str> {
|
||||
// Without a session bus there is no bus to reach the portal on, so the chooser is not the
|
||||
// desktop's own whatever else is there.
|
||||
if !dbus_session {
|
||||
return Some(CHOOSER_NO_SESSION_BUS);
|
||||
}
|
||||
// The bus is there but the portal does not answer on it: `rfd` falls back to zenity.
|
||||
if !portal_service {
|
||||
return Some(CHOOSER_NO_PORTAL_SERVICE);
|
||||
}
|
||||
// Under XWayland the window is an X11 one while the session is a Wayland one, and the portal
|
||||
// refuses to parent its chooser to an X11 window: `rfd` falls back to zenity.
|
||||
if wayland_display && x11_display {
|
||||
return Some(CHOOSER_XWAYLAND);
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(all(test, unix, not(target_os = "macos")))]
|
||||
mod backend_tests {
|
||||
use super::prefer_x11;
|
||||
@@ -1536,30 +1430,6 @@ mod backend_tests {
|
||||
assert!(prefer_x11(true, true, Some("niri"), over(Some("x11"))));
|
||||
}
|
||||
|
||||
/// The file chooser must say when it is NOT the desktop's own: `rfd` falls back to zenity
|
||||
/// when the XDG portal is not there, and the user gets a foreign window with no explanation.
|
||||
#[test]
|
||||
fn the_file_picker_says_when_it_is_not_the_desktops_own_chooser() {
|
||||
use super::desktop_chooser_fallback;
|
||||
// The normal case: the portal is there, the desktop's own chooser opens, nothing to say.
|
||||
assert_eq!(desktop_chooser_fallback(true, true, true, false), None);
|
||||
// No session bus: the portal can not be reached at all, so `rfd` falls back to zenity.
|
||||
assert!(
|
||||
desktop_chooser_fallback(false, true, true, false).is_some(),
|
||||
"without a D-Bus session bus the file chooser is not the desktop's own: the user has to be told"
|
||||
);
|
||||
// The bus is there but the portal service is not: same fallback, same warning.
|
||||
assert!(
|
||||
desktop_chooser_fallback(true, false, true, false).is_some(),
|
||||
"without the desktop portal service the file chooser is not the desktop's own: the user has to be told"
|
||||
);
|
||||
// XWayland: the parent window is an X11 one and the portal refuses it, so `rfd` falls back.
|
||||
assert!(
|
||||
desktop_chooser_fallback(true, true, true, true).is_some(),
|
||||
"under XWayland the portal rejects the parent window: the user has to be told"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_desktops_title_bar_only_when_asked_for() {
|
||||
use super::titlebar_choice;
|
||||
@@ -1837,88 +1707,6 @@ mod viewport_tests {
|
||||
|
||||
#[cfg(test)]
|
||||
mod msaa_tests {
|
||||
/// Safe mode uses default UI settings but edits the real graphics.json for the next start.
|
||||
#[test]
|
||||
fn safe_mode_preserves_graphics_choices_when_changing_antialiasing() {
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_ui_egui::scenario::Harness;
|
||||
|
||||
for choice in ["wayland", "x11"] {
|
||||
let mut h = Harness::new();
|
||||
let path = h.dir.join("graphics.json");
|
||||
let saved = json!({"window_system": choice, "adapter": "Selected GPU", "backend": "vulkan", "msaa": 8});
|
||||
std::fs::write(&path, saved.to_string()).unwrap();
|
||||
h.app.services.graphics_path = Some(path.to_string_lossy().into_owned());
|
||||
let defaults = h.app.preferences.clone();
|
||||
let stored_ui = json!({"preferences": {"window_system": "auto", "graphics_adapter": "Stale GPU", "msaa": 2,
|
||||
"display_quality": "fine"}})
|
||||
.to_string();
|
||||
super::restore_preferences(&mut h.app, Some(&stored_ui), Some(&saved), true);
|
||||
|
||||
assert_eq!(h.app.preferences.window_system, choice);
|
||||
assert_eq!(h.app.preferences.graphics_adapter, "Selected GPU");
|
||||
assert_eq!(h.app.preferences.msaa, defaults.msaa, "safe mode ignores saved anti-aliasing");
|
||||
assert_eq!(h.app.preferences.display_quality, defaults.display_quality, "safe mode ignores saved UI settings");
|
||||
assert_eq!(serde_json::from_str::<Value>(&std::fs::read_to_string(&path).unwrap()).unwrap(), saved, "startup does not write settings");
|
||||
|
||||
assert_eq!(h.call("ui.prefs", json!({"open": "Graphics"}))["ok"], true);
|
||||
assert_eq!(h.call("ui.prefs", json!({"msaa": 1}))["ok"], true);
|
||||
assert_eq!(h.call("ui.prefs", json!({"button": "ok"}))["ok"], true);
|
||||
let after: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
assert_eq!(after, json!({"window_system": choice, "adapter": "Selected GPU", "backend": "vulkan", "msaa": 1}));
|
||||
|
||||
let mut normal = Harness::new();
|
||||
super::restore_preferences(&mut normal.app, Some(&stored_ui), Some(&after), false);
|
||||
assert_eq!(normal.app.preferences.window_system, choice);
|
||||
assert_eq!(normal.app.preferences.graphics_adapter, "Selected GPU");
|
||||
assert_eq!(normal.app.preferences.display_quality, "fine", "normal startup still restores UI settings");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_or_invalid_startup_window_system_uses_automatic() {
|
||||
use serde_json::json;
|
||||
|
||||
for saved in [None, Some(json!(null)), Some(json!({})), Some(json!({"window_system": 7})), Some(json!({"window_system": "unknown"}))] {
|
||||
let mut app = super::SolveApp::new(super::Session::default(), super::Services::default());
|
||||
app.preferences.window_system = "wayland".into();
|
||||
super::restore_graphics_preferences(&mut app, saved.as_ref());
|
||||
assert_eq!(app.preferences.window_system, "auto", "{saved:?}");
|
||||
}
|
||||
}
|
||||
|
||||
/// A manual graphics.json choice wins over stale app.ron preferences and survives an
|
||||
/// unrelated graphics edit (#10116), including on a fresh launch with no saved UI state.
|
||||
#[test]
|
||||
fn startup_window_system_survives_changing_antialiasing() {
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_ui_egui::scenario::Harness;
|
||||
|
||||
for choice in ["wayland", "x11", "auto", " wayland ", " x11 "] {
|
||||
for stale in [None, Some("auto"), Some("wayland"), Some("x11")] {
|
||||
let mut h = Harness::new();
|
||||
if let Some(stale) = stale {
|
||||
h.app.load_prefs(&json!({"preferences": {"window_system": stale}}).to_string());
|
||||
}
|
||||
let path = h.dir.join("graphics.json");
|
||||
std::fs::write(&path, json!({"window_system": choice, "backend": "vulkan", "adapter": "Selected GPU", "msaa": 4}).to_string())
|
||||
.unwrap();
|
||||
h.app.services.graphics_path = Some(path.to_string_lossy().into_owned());
|
||||
let saved: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
super::restore_graphics_preferences(&mut h.app, Some(&saved));
|
||||
|
||||
let result = h.call("ui.prefs", json!({"msaa": 2}));
|
||||
assert_eq!(result["ok"], true, "{result}");
|
||||
let after: Value = serde_json::from_str(&std::fs::read_to_string(&path).unwrap()).unwrap();
|
||||
assert_eq!(after["window_system"], choice.trim(), "saved choice {choice:?}, stale UI choice {stale:?}");
|
||||
assert_eq!(result["result"]["window_system"], choice.trim());
|
||||
assert_eq!(after["msaa"], 2);
|
||||
assert_eq!(after["backend"], "vulkan");
|
||||
assert_eq!(after["adapter"], "Selected GPU");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_launch_adopts_the_previous_release_settings() {
|
||||
let d = std::env::temp_dir().join(format!("solidcraft-adopt-settings-{}", std::process::id()));
|
||||
|
||||
@@ -40,10 +40,10 @@ fn connect_to(path: &Path) -> Option<UnixStream> {
|
||||
|
||||
/// The axes of one record, or `None` for a record that is not motion.
|
||||
///
|
||||
/// The daemon reports x to the right, y up and z away from the user; our raw HID frame is x
|
||||
/// right, y towards the user and z down. Swap and negate y/z and ry/rz to match that frame.
|
||||
/// With nothing set in `spnavcfg` the two readers give the same motion; what the user swaps
|
||||
/// or inverts there then carries over.
|
||||
/// The daemon's frame is right-handed with x to the right, y away from the user and z up; ours is
|
||||
/// the device's raw one, x right, y towards the user and z down, so y and z (and the rotations
|
||||
/// about them) change sign. With nothing set in `spnavcfg` the two give the same motion; what
|
||||
/// the user swaps or inverts there then carries over.
|
||||
fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
|
||||
let mut data = [0_i32; 8];
|
||||
for (slot, bytes) in data.iter_mut().zip(record.as_chunks::<4>().0) {
|
||||
@@ -51,8 +51,8 @@ fn decode(record: &[u8; RECORD_BYTES]) -> Option<Axes> {
|
||||
}
|
||||
let [kind, x, y, z, rx, ry, rz, _period] = data;
|
||||
(kind == 0).then(|| Axes {
|
||||
translation: [unit(x), unit(-i64::from(z)), unit(-i64::from(y))],
|
||||
rotation: [unit(rx), unit(-i64::from(rz)), unit(-i64::from(ry))],
|
||||
translation: [unit(x), unit(-i64::from(y)), unit(-i64::from(z))],
|
||||
rotation: [unit(rx), unit(-i64::from(ry)), unit(-i64::from(rz))],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -149,28 +149,22 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn motion_records_become_axes_in_the_devices_raw_frame() {
|
||||
// Each daemon axis reaches only its matching raw HID axis, in both directions.
|
||||
for (daemon_axis, raw_axis, sign) in [(0, 0, 1), (1, 2, -1), (2, 1, -1), (3, 3, 1), (4, 5, -1), (5, 4, -1)] {
|
||||
for value in [-500, 500] {
|
||||
let mut data = [0; 8];
|
||||
data[daemon_axis + 1] = value;
|
||||
let mut expected = [0; 6];
|
||||
expected[raw_axis] = sign * (value * 350 / 500) as i16;
|
||||
assert_eq!(decode(&record(data)), Some(Axes::from(expected)), "daemon axis {daemon_axis}, value {value}");
|
||||
}
|
||||
}
|
||||
// Full deflection of the daemon's x, y and z is the device's full scale, and y and z flip.
|
||||
assert_eq!(decode(&record([0, 500, 500, 500, 0, 0, 0, 8])), Some(Axes { translation: [350, -350, -350], rotation: [0; 3] }));
|
||||
// Rotations follow translations: right-handed in both frames, so ry and rz flip as y and z do.
|
||||
assert_eq!(decode(&record([0, 0, 0, 0, 250, 250, 250, 8])), Some(Axes { translation: [0; 3], rotation: [175, -175, -175] }));
|
||||
// Rest is a motion record of zeros.
|
||||
assert_eq!(decode(&record([0, 0, 0, 0, 0, 0, 0, 1200])), Some(Axes::default()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_spnavcfg_swaps_reaches_the_view() {
|
||||
// A push away is daemon z (raw y); swapping Y/Z makes it a lift (raw z).
|
||||
// The same swap changes a sideways tilt (daemon rz, raw ry) into a twist (raw rz).
|
||||
let before = decode(&record([0, 0, 0, 500, 0, 0, 250, 8]));
|
||||
let after = decode(&record([0, 0, 500, 0, 0, 250, 0, 8]));
|
||||
assert_eq!(before, Some(Axes { translation: [0, -350, 0], rotation: [0, -175, 0] }));
|
||||
assert_eq!(after, Some(Axes { translation: [0, 0, -350], rotation: [0, 0, -175] }));
|
||||
// The daemon sends a push away as y. With Y and Z swapped in spnavcfg it sends the same push
|
||||
// as z, and the view gets a lift (raw z) instead of the zoom (raw y).
|
||||
let before = decode(&record([0, 0, 500, 0, 0, 0, 0, 8])).map(|axes| axes.translation);
|
||||
let after = decode(&record([0, 0, 0, 500, 0, 0, 0, 8])).map(|axes| axes.translation);
|
||||
assert_eq!(before, Some([0, -350, 0]));
|
||||
assert_eq!(after, Some([0, 0, -350]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -185,7 +179,7 @@ mod tests {
|
||||
// Nothing overflows on the extremes of `i32`.
|
||||
let extreme = decode(&record([0, i32::MIN, i32::MIN, i32::MAX, i32::MAX, i32::MIN, i32::MIN, -1]));
|
||||
assert_eq!(extreme.map(|axes| axes.translation[0]), Some(i16::MIN));
|
||||
assert_eq!(extreme.map(|axes| axes.translation), Some([i16::MIN, i16::MIN, i16::MAX]));
|
||||
assert_eq!(extreme.map(|axes| axes.translation[1]), Some(i16::MAX));
|
||||
assert_eq!(extreme.map(|axes| axes.rotation[0]), Some(i16::MAX));
|
||||
}
|
||||
|
||||
|
||||
@@ -311,18 +311,13 @@ pub const SYSTEM_VERSION_COMPAT_ENV: &str = "SYSTEM_VERSION_COMPAT";
|
||||
#[cfg(target_os = "macos")]
|
||||
pub const OCLP_NVRAM_KEY: &str = "4D1FDA02-38C7-4A6A-9CC6-4BCCA8B30102:OCLP-Version";
|
||||
|
||||
/// Where macOS keeps its `nvram` tool.
|
||||
#[cfg(target_os = "macos")]
|
||||
const NVRAM_TOOL: &str = "/usr/sbin/nvram";
|
||||
|
||||
/// `true` if the OCLP NVRAM key is present on this system.
|
||||
///
|
||||
/// Runs `/usr/sbin/nvram <key>` and treats exit 0 as present. The full path keeps a `PATH` that
|
||||
/// lacks `/usr/sbin`, or lists a look-alike first, from changing the answer. Any error (missing
|
||||
/// binary, permission denied, key absent) returns `false`: this is a best-effort probe.
|
||||
/// Runs `nvram <key>` and treats exit 0 as present. Any error (missing binary,
|
||||
/// permission denied, key absent) returns `false` — this is a best-effort probe.
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn oclp_nvram_present() -> bool {
|
||||
std::process::Command::new(NVRAM_TOOL).arg(OCLP_NVRAM_KEY).output().map(|o| o.status.success()).unwrap_or(false)
|
||||
std::process::Command::new("nvram").arg(OCLP_NVRAM_KEY).output().map(|o| o.status.success()).unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Whether to re-launch with `SYSTEM_VERSION_COMPAT=1`: it is not set yet and OCLP shows through
|
||||
@@ -339,8 +334,8 @@ fn compat_wanted(compat_set: bool, nvram: impl FnOnce() -> bool) -> bool {
|
||||
///
|
||||
/// `SYSTEM_VERSION_COMPAT=1` must be present **before** the process starts: setting it
|
||||
/// with `std::env::set_var` inside `main` is too late because the macOS frameworks have
|
||||
/// already queried the OS version by then, so the program starts itself again with the variable
|
||||
/// set and waits for that child.
|
||||
/// already queried the OS version by then. Re-launching is the only safe approach that
|
||||
/// works without `unsafe` (a true `execve` replacement would require it).
|
||||
///
|
||||
/// Returns `Ok(false)` immediately when:
|
||||
/// - `SYSTEM_VERSION_COMPAT` is already set (re-launched process, no loop), or
|
||||
|
||||
@@ -17,7 +17,6 @@ assets/
|
||||
ui/design/tools/ # authoring bases, checks and included review app
|
||||
ui/source/ # Rust viewport glyphs, markers and navigation
|
||||
branding/web/source/og.png # shared social preview card
|
||||
branding/logo/ # print/web logo files; guide in branding/README.md
|
||||
styles/ui/palettes.rs # light/dark defaults
|
||||
styles/web/loading.css # web loading/canvas styles
|
||||
appearances/library.rs # built-in visual presets
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
# SolidCraft brand kit
|
||||
|
||||
Logo files for stickers, shirts, the web and social posts. Everything in `logo/` is drawn
|
||||
from the app icon (`assets/icons/app/macos/source/SolidCraft.icon/Assets/03-solid-blue-cutout.svg`),
|
||||
which is also the last frame of the loading animation (`assets/styles/web/loading-logo.svg`).
|
||||
The SVGs are plain paths: no scripts, fonts, animation or external links, so any printer can use them.
|
||||
|
||||
## Files
|
||||
|
||||
| File | Use it for |
|
||||
|---|---|
|
||||
| `logo/solidcraft-mark.svg` | The mark in full colour. First choice for stickers, print and the web. |
|
||||
| `logo/solidcraft-mark-mono-black.svg` | One-colour print on light material: screen-printed shirts, stamps, engraving, vinyl. |
|
||||
| `logo/solidcraft-mark-mono-white.svg` | One-colour print on dark material, such as a white print on a dark shirt. |
|
||||
| `logo/solidcraft-mark-512.png`, `-1024.png`, `-2048.png` | Transparent square PNGs for places that don't take SVG: avatars, social posts, slides, print shops that ask for a bitmap (send the 2048). |
|
||||
| `logo/solidcraft-wordmark.svg` | The name on its own, outlined to paths. |
|
||||
| `logo/solidcraft-lockup-horizontal.svg` | Mark and name side by side: headers, banners, shirt fronts, wide stickers. |
|
||||
| `web/source/og.png` | The existing link preview card for the docs site. |
|
||||
|
||||
The gaps between the faces are part of the logo. In the colour and one-colour files they are
|
||||
transparent, so the material shows through. For a die-cut sticker, cut around the outer shape
|
||||
and let the backing colour (white is best) fill the gaps.
|
||||
|
||||
## Colours
|
||||
|
||||
The mark's five faces each use a two-stop gradient from the icon source:
|
||||
|
||||
| Face | From | To |
|
||||
|---|---|---|
|
||||
| Top | `#51adff` | `#2b83f1` |
|
||||
| Left | `#258df5` | `#1461da` |
|
||||
| Right | `#1b66cf` | `#124596` |
|
||||
| Lower step (top) | `#459fff` | `#2379e7` |
|
||||
| Upper step (inner side) | `#1d63c6` | `#15499e` |
|
||||
|
||||
Other colours, all already used in the project:
|
||||
|
||||
| Colour | Hex | Where it comes from |
|
||||
|---|---|---|
|
||||
| Wordmark blue | `#15499e` | loading logo `--word` |
|
||||
| Interface accent | `#2678da` | light theme `accent` token (`assets/styles/ui/palettes.rs`) |
|
||||
| Light background | `#e8ebf0` | web loading screen (`assets/styles/web/loading.css`) |
|
||||
| Dark background | `#1f232b` | dark theme `file_background` token |
|
||||
| Black / white | `#000000` / `#ffffff` | one-colour versions only |
|
||||
|
||||
If a printer can't do gradients, use the one-colour files, or fill every face with `#2678da`.
|
||||
|
||||
## Clear space and size
|
||||
|
||||
- Keep empty space around the mark of at least one fifth of its height on every side. Treat
|
||||
the lockup the same way, measured from the mark.
|
||||
- Smallest mark: 16 px tall on screen, 12 mm tall in print. Below that the gaps fill in.
|
||||
- Smallest lockup: 120 px wide on screen, 40 mm wide in print.
|
||||
|
||||
## Don'ts
|
||||
|
||||
- Don't recolour it outside the colours above.
|
||||
- Don't stretch, squash, rotate, skew or crop it. Scale it evenly.
|
||||
- Don't add shadows, outlines, glows, bevels or other effects.
|
||||
- Don't fill or close the gaps between the faces, or move the faces apart.
|
||||
- Don't rearrange the lockup, retype the name in another font, or put the mark on a busy
|
||||
background where it loses contrast.
|
||||
|
||||
## Name and logo
|
||||
|
||||
The SolidCraft code and these files are licensed MIT OR Apache-2.0 (see the [README](../../README.md#licence)),
|
||||
so you may copy and change them. The name SolidCraft and this logo identify the SolidCraft
|
||||
project. Use them to refer to the project, for example in an article, a talk, or a sticker for a
|
||||
meetup. If you publish a fork or a modified build, give it its own name and logo so people
|
||||
don't mistake it for SolidCraft.
|
||||
|
||||
The wordmark is set in IBM Plex Sans Bold (SIL Open Font License 1.1, the interface font in
|
||||
`assets/fonts/ibm-plex-sans/`) and outlined, so you don't need the font to print it.
|
||||
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 2748.71 904.436" width="2749" height="904" role="img"><title>SolidCraft</title><defs><linearGradient id="sc1" x1="619.14" y1="179.734" x2="694.687" y2="396.761" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d63c6"/><stop offset="1" stop-color="#15499e"/></linearGradient><linearGradient id="sc2" x1="619.14" y1="413.244" x2="790.839" y2="605.547" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#459fff"/><stop offset="1" stop-color="#2379e7"/></linearGradient><linearGradient id="sc3" x1="632.876" y1="399.508" x2="783.971" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b66cf"/><stop offset="1" stop-color="#124596"/></linearGradient><linearGradient id="sc4" x1="207.063" y1="303.357" x2="426.837" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#258df5"/><stop offset="1" stop-color="#1461da"/></linearGradient><linearGradient id="sc5" x1="385.63" y1="53.364" x2="522.989" y2="510.769" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#51adff"/><stop offset="1" stop-color="#2b83f1"/></linearGradient></defs><g><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z" fill="url(#sc1)"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z" fill="url(#sc2)"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z" fill="url(#sc3)"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z" fill="url(#sc4)"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z" fill="url(#sc5)"/></g><path fill="#15499e" transform="translate(1092.78 632.20) scale(0.36281)" d="M303 12Q212 12 147.5 -20Q83 -52 38 -101L138 -202Q173 -162 217.5 -142Q262 -122 313 -122Q369 -122 396 -145Q423 -168 423 -206Q423 -225 416.5 -240.5Q410 -256 392.5 -266.5Q375 -277 342 -281L273 -290Q200 -300 152.5 -326.5Q105 -353 82 -396.5Q59 -440 59 -498Q59 -561 90.5 -608.5Q122 -656 180.5 -683Q239 -710 321 -710Q400 -710 460 -684.5Q520 -659 563 -612L462 -510Q437 -539 401.5 -557.5Q366 -576 312 -576Q261 -576 236 -558Q211 -540 211 -508Q211 -484 219.5 -469Q228 -454 246.5 -446Q265 -438 294 -433L363 -422Q435 -411 482 -385Q529 -359 552 -317Q575 -275 575 -215Q575 -148 543.5 -97Q512 -46 451 -17Q390 12 303 12ZM926 12Q849 12 794 -21.5Q739 -55 709.5 -117Q680 -179 680 -263Q680 -348 709.5 -409Q739 -470 794 -503.5Q849 -537 926 -537Q1003 -537 1058 -503.5Q1113 -470 1142.5 -409Q1172 -348 1172 -263Q1172 -179 1142.5 -117Q1113 -55 1058 -21.5Q1003 12 926 12ZM926 -105Q970 -105 994 -132.5Q1018 -160 1018 -209V-316Q1018 -366 994 -393Q970 -420 926 -420Q883 -420 858.5 -393Q834 -366 834 -316V-209Q834 -160 858.5 -132.5Q883 -105 926 -105ZM1511 0H1444Q1368 0 1332.5 -37Q1297 -74 1297 -144V-740H1445V-115H1511ZM1694 -581Q1649 -581 1628.5 -602Q1608 -623 1608 -654V-676Q1608 -708 1628.5 -728.5Q1649 -749 1694 -749Q1739 -749 1759.5 -728.5Q1780 -708 1780 -676V-654Q1780 -623 1759.5 -602Q1739 -581 1694 -581ZM1620 0V-525H1768V0ZM2248 0V-88H2243Q2232 -45 2192.5 -16.5Q2153 12 2102 12Q2036 12 1991 -19.5Q1946 -51 1923 -112.5Q1900 -174 1900 -263Q1900 -352 1923 -413Q1946 -474 1991 -505.5Q2036 -537 2102 -537Q2153 -537 2192.5 -509Q2232 -481 2243 -437H2248V-740H2396V0ZM2151 -108Q2179 -108 2201 -117Q2223 -126 2235.5 -143.5Q2248 -161 2248 -185V-340Q2248 -364 2235.5 -381.5Q2223 -399 2201 -408Q2179 -417 2151 -417Q2109 -417 2081.5 -387.5Q2054 -358 2054 -308V-217Q2054 -168 2081.5 -138Q2109 -108 2151 -108ZM2844 12Q2750 12 2680.5 -28Q2611 -68 2573 -147Q2535 -226 2535 -344Q2535 -462 2573 -543.5Q2611 -625 2680.5 -667.5Q2750 -710 2844 -710Q2940 -710 3003.5 -670Q3067 -630 3105 -546L2974 -478Q2961 -522 2930.5 -548.5Q2900 -575 2844 -575Q2777 -575 2736.5 -531.5Q2696 -488 2696 -405V-293Q2696 -211 2736.5 -167Q2777 -123 2844 -123Q2900 -123 2933.5 -153.5Q2967 -184 2983 -227L3107 -155Q3068 -76 3004 -32Q2940 12 2844 12ZM3373 0H3225V-525H3373V-411H3378Q3384 -440 3400 -466Q3416 -492 3443.5 -508.5Q3471 -525 3512 -525H3538V-387H3501Q3458 -387 3429.5 -379.5Q3401 -372 3387 -355Q3373 -338 3373 -307ZM4105 0H4023Q3996 0 3972.5 -13.5Q3949 -27 3935 -53.5Q3921 -80 3921 -117V-130L3953 -92H3917Q3905 -41 3864 -14.5Q3823 12 3763 12Q3684 12 3642 -30.5Q3600 -73 3600 -141Q3600 -197 3627.5 -233Q3655 -269 3705.5 -287Q3756 -305 3825 -305H3908V-338Q3908 -376 3888 -398.5Q3868 -421 3821 -421Q3777 -421 3751 -402Q3725 -383 3708 -359L3620 -437Q3652 -484 3701 -510.5Q3750 -537 3832 -537Q3943 -537 3999.5 -487.5Q4056 -438 4056 -345V-115H4105ZM3908 -221H3836Q3793 -221 3771 -206Q3749 -191 3749 -162V-147Q3749 -119 3767 -105Q3785 -91 3818 -91Q3843 -91 3863 -98Q3883 -105 3895.5 -120Q3908 -135 3908 -159ZM4250 0V-410H4176V-525H4250V-587Q4250 -663 4290.5 -701.5Q4331 -740 4407 -740H4503V-625H4398V-525H4503V-410H4398V0ZM4877 0H4789Q4713 0 4673 -39Q4633 -78 4633 -153V-410H4559V-525H4596Q4626 -525 4637 -539.5Q4648 -554 4648 -581V-667H4781V-525H4885V-410H4781V-115H4877Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
|
Before Width: | Height: | Size: 76 KiB |
|
Before Width: | Height: | Size: 193 KiB |
|
Before Width: | Height: | Size: 32 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><g fill="#000000"><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 612 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><g fill="#ffffff"><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 612 B |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="116.406 53.364 791.188 904.436" width="791" height="904" role="img"><title>SolidCraft</title><defs><linearGradient id="sc1" x1="619.14" y1="179.734" x2="694.687" y2="396.761" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d63c6"/><stop offset="1" stop-color="#15499e"/></linearGradient><linearGradient id="sc2" x1="619.14" y1="413.244" x2="790.839" y2="605.547" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#459fff"/><stop offset="1" stop-color="#2379e7"/></linearGradient><linearGradient id="sc3" x1="632.876" y1="399.508" x2="783.971" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1b66cf"/><stop offset="1" stop-color="#124596"/></linearGradient><linearGradient id="sc4" x1="207.063" y1="303.357" x2="426.837" y2="968.175" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#258df5"/><stop offset="1" stop-color="#1461da"/></linearGradient><linearGradient id="sc5" x1="385.63" y1="53.364" x2="522.989" y2="510.769" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#51adff"/><stop offset="1" stop-color="#2b83f1"/></linearGradient></defs><g><path d="M547.883 282.66 L709.797 188.211 L709.797 375.985 Z" fill="url(#sc1)"/><path d="M727.797 593.626 L727.797 407.136 L889.571 500.381 Z" fill="url(#sc2)"/><path d="M530 521.17 L691.797 427.912 L691.797 655.928 L907.594 531.545 L907.594 740.159 L530 957.8 Z" fill="url(#sc3)"/><path d="M116.406 510.769 L116.406 303.529 L494 521.17 L494 957.8 L314.203 854.167 L314.203 624.777 Z" fill="url(#sc4)"/><path d="M134.374 272.334 L512 53.364 L691.828 157.015 L476.117 282.846 L673.752 396.761 L512 489.993 Z" fill="url(#sc5)"/></g></svg>
|
||||
|
Before Width: | Height: | Size: 1.7 KiB |
@@ -1 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="38.0 -749.0 4847.0 761.0" width="4847" height="761" role="img"><title>SolidCraft</title><path fill="#15499e" d="M303 12Q212 12 147.5 -20Q83 -52 38 -101L138 -202Q173 -162 217.5 -142Q262 -122 313 -122Q369 -122 396 -145Q423 -168 423 -206Q423 -225 416.5 -240.5Q410 -256 392.5 -266.5Q375 -277 342 -281L273 -290Q200 -300 152.5 -326.5Q105 -353 82 -396.5Q59 -440 59 -498Q59 -561 90.5 -608.5Q122 -656 180.5 -683Q239 -710 321 -710Q400 -710 460 -684.5Q520 -659 563 -612L462 -510Q437 -539 401.5 -557.5Q366 -576 312 -576Q261 -576 236 -558Q211 -540 211 -508Q211 -484 219.5 -469Q228 -454 246.5 -446Q265 -438 294 -433L363 -422Q435 -411 482 -385Q529 -359 552 -317Q575 -275 575 -215Q575 -148 543.5 -97Q512 -46 451 -17Q390 12 303 12ZM926 12Q849 12 794 -21.5Q739 -55 709.5 -117Q680 -179 680 -263Q680 -348 709.5 -409Q739 -470 794 -503.5Q849 -537 926 -537Q1003 -537 1058 -503.5Q1113 -470 1142.5 -409Q1172 -348 1172 -263Q1172 -179 1142.5 -117Q1113 -55 1058 -21.5Q1003 12 926 12ZM926 -105Q970 -105 994 -132.5Q1018 -160 1018 -209V-316Q1018 -366 994 -393Q970 -420 926 -420Q883 -420 858.5 -393Q834 -366 834 -316V-209Q834 -160 858.5 -132.5Q883 -105 926 -105ZM1511 0H1444Q1368 0 1332.5 -37Q1297 -74 1297 -144V-740H1445V-115H1511ZM1694 -581Q1649 -581 1628.5 -602Q1608 -623 1608 -654V-676Q1608 -708 1628.5 -728.5Q1649 -749 1694 -749Q1739 -749 1759.5 -728.5Q1780 -708 1780 -676V-654Q1780 -623 1759.5 -602Q1739 -581 1694 -581ZM1620 0V-525H1768V0ZM2248 0V-88H2243Q2232 -45 2192.5 -16.5Q2153 12 2102 12Q2036 12 1991 -19.5Q1946 -51 1923 -112.5Q1900 -174 1900 -263Q1900 -352 1923 -413Q1946 -474 1991 -505.5Q2036 -537 2102 -537Q2153 -537 2192.5 -509Q2232 -481 2243 -437H2248V-740H2396V0ZM2151 -108Q2179 -108 2201 -117Q2223 -126 2235.5 -143.5Q2248 -161 2248 -185V-340Q2248 -364 2235.5 -381.5Q2223 -399 2201 -408Q2179 -417 2151 -417Q2109 -417 2081.5 -387.5Q2054 -358 2054 -308V-217Q2054 -168 2081.5 -138Q2109 -108 2151 -108ZM2844 12Q2750 12 2680.5 -28Q2611 -68 2573 -147Q2535 -226 2535 -344Q2535 -462 2573 -543.5Q2611 -625 2680.5 -667.5Q2750 -710 2844 -710Q2940 -710 3003.5 -670Q3067 -630 3105 -546L2974 -478Q2961 -522 2930.5 -548.5Q2900 -575 2844 -575Q2777 -575 2736.5 -531.5Q2696 -488 2696 -405V-293Q2696 -211 2736.5 -167Q2777 -123 2844 -123Q2900 -123 2933.5 -153.5Q2967 -184 2983 -227L3107 -155Q3068 -76 3004 -32Q2940 12 2844 12ZM3373 0H3225V-525H3373V-411H3378Q3384 -440 3400 -466Q3416 -492 3443.5 -508.5Q3471 -525 3512 -525H3538V-387H3501Q3458 -387 3429.5 -379.5Q3401 -372 3387 -355Q3373 -338 3373 -307ZM4105 0H4023Q3996 0 3972.5 -13.5Q3949 -27 3935 -53.5Q3921 -80 3921 -117V-130L3953 -92H3917Q3905 -41 3864 -14.5Q3823 12 3763 12Q3684 12 3642 -30.5Q3600 -73 3600 -141Q3600 -197 3627.5 -233Q3655 -269 3705.5 -287Q3756 -305 3825 -305H3908V-338Q3908 -376 3888 -398.5Q3868 -421 3821 -421Q3777 -421 3751 -402Q3725 -383 3708 -359L3620 -437Q3652 -484 3701 -510.5Q3750 -537 3832 -537Q3943 -537 3999.5 -487.5Q4056 -438 4056 -345V-115H4105ZM3908 -221H3836Q3793 -221 3771 -206Q3749 -191 3749 -162V-147Q3749 -119 3767 -105Q3785 -91 3818 -91Q3843 -91 3863 -98Q3883 -105 3895.5 -120Q3908 -135 3908 -159ZM4250 0V-410H4176V-525H4250V-587Q4250 -663 4290.5 -701.5Q4331 -740 4407 -740H4503V-625H4398V-525H4503V-410H4398V0ZM4877 0H4789Q4713 0 4673 -39Q4633 -78 4633 -153V-410H4559V-525H4596Q4626 -525 4637 -539.5Q4648 -554 4648 -581V-667H4781V-525H4885V-410H4781V-115H4877Z"/></svg>
|
||||
|
Before Width: | Height: | Size: 3.3 KiB |
@@ -60,6 +60,7 @@ circle_ttt false
|
||||
close false
|
||||
coil false
|
||||
colored_components false
|
||||
colored_components_off false
|
||||
combine false
|
||||
compare false
|
||||
component false
|
||||
@@ -160,7 +161,6 @@ min_radius false
|
||||
mirror false
|
||||
motion_link false
|
||||
move false
|
||||
nav_settings false
|
||||
new false
|
||||
normal false
|
||||
offset false
|
||||
@@ -201,18 +201,6 @@ point_vertex false
|
||||
polygon false
|
||||
polygon_circumscribed false
|
||||
polygon_edge false
|
||||
prefs_appearance false
|
||||
prefs_assistant false
|
||||
prefs_design false
|
||||
prefs_general false
|
||||
prefs_graphics false
|
||||
prefs_layout false
|
||||
prefs_mouse3d false
|
||||
prefs_navigation false
|
||||
prefs_plugins false
|
||||
prefs_shortcuts false
|
||||
prefs_sketch false
|
||||
prefs_tools false
|
||||
presspull false
|
||||
print_face false
|
||||
project false
|
||||
@@ -244,6 +232,11 @@ section false
|
||||
select false
|
||||
send false
|
||||
settings false
|
||||
settings_keyboard false
|
||||
settings_monitor false
|
||||
settings_mouse false
|
||||
settings_palette false
|
||||
settings_plug false
|
||||
settings_profile false
|
||||
sheet false
|
||||
sheet_rules false
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Appearance</title>
|
||||
<path d="M32 7.5C18 7.5 7.5 18.5 7.5 32C7.5 45.5 18.5 56.5 32 56.5C37.5 56.5 40 53 38 49C36 45 38.5 42 43 42L47.5 42C52.5 42 56.5 38 56.5 33C56.5 18.5 46 7.5 32 7.5Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><circle cx="20" cy="33" r="4" fill="currentColor"/><circle cx="28" cy="20" r="4" fill="currentColor"/><circle cx="42" cy="22" r="4" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 520 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Assistant</title>
|
||||
<path d="M29 12.5Q30.2 33.8 51.5 35Q30.2 36.2 29 57.5Q27.8 36.2 6.5 35Q27.8 33.8 29 12.5Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M50 6Q50.6 12.4 57 13Q50.6 13.6 50 20Q49.4 13.6 43 13Q49.4 12.4 50 6Z" fill="currentColor" stroke="currentColor" stroke-width="4.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 479 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Design</title>
|
||||
<path d="M32 7.7L53.04 19.85L53.04 44.15L32 56.3L10.96 44.15L10.96 19.85Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M10.96 19.85L32 32L53.04 19.85M32 32L32 56.3" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 427 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>General</title>
|
||||
<path d="M27.24 14.85L27.15 7.07L36.85 7.07L36.76 14.85L44.48 19.3L51.17 15.34L56.02 23.73L49.23 27.54L49.23 36.46L56.02 40.27L51.17 48.66L44.48 44.7L36.76 49.15L36.85 56.93L27.15 56.93L27.24 49.15L19.52 44.7L12.83 48.66L7.98 40.27L14.77 36.46L14.77 27.54L7.98 23.73L12.83 15.34L19.52 19.3Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M25 32A7 7 0 1 0 39 32A7 7 0 1 0 25 32Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 640 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Graphics</title>
|
||||
<path d="M14 8L50 8A6 6 0 0 1 56 14L56 38A6 6 0 0 1 50 44L14 44A6 6 0 0 1 8 38L8 14A6 6 0 0 1 14 8ZM32 44L32 54M20 54L44 54" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 323 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Layout</title>
|
||||
<path d="M14 10L50 10A6 6 0 0 1 56 16L56 48A6 6 0 0 1 50 54L14 54A6 6 0 0 1 8 48L8 16A6 6 0 0 1 14 10Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M24 10L24 54M24 39L56 39" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 436 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>3D mouse</title>
|
||||
<path d="M6.5 32A25.5 25.5 0 1 0 57.5 32A25.5 25.5 0 1 0 6.5 32Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M24 32A8 8 0 1 0 40 32A8 8 0 1 0 24 32Z" fill="currentColor" stroke="currentColor" stroke-width="4.08" stroke-linejoin="round" stroke-linecap="round"/><path d="M53.4 32L45.96 35.9L45.96 28.1Z" fill="currentColor" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M32 53.4L28.1 45.96L35.9 45.96Z" fill="currentColor" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M10.6 32L18.04 28.1L18.04 35.9Z" fill="currentColor" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/><path d="M32 10.6L35.9 18.04L28.1 18.04Z" fill="currentColor" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 1.0 KiB |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Navigation</title>
|
||||
<path d="M32 7A14 14 0 0 1 46 21L46 43A14 14 0 0 1 18 43L18 21A14 14 0 0 1 32 7Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M18 38L46 38" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M32 14L32 22" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 530 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Plugins</title>
|
||||
<path d="M24 7L24 20M40 7L40 20M16 20L48 20L48 28A16 16 0 0 1 16 28ZM32 44L32 57" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 279 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Shortcuts</title>
|
||||
<path d="M12 13L52 13A6 6 0 0 1 58 19L58 45A6 6 0 0 1 52 51L12 51A6 6 0 0 1 6 45L6 19A6 6 0 0 1 12 13Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><circle cx="18" cy="26" r="3.6" fill="currentColor"/><circle cx="28" cy="26" r="3.6" fill="currentColor"/><circle cx="38" cy="26" r="3.6" fill="currentColor"/><circle cx="48" cy="26" r="3.6" fill="currentColor"/><path d="M20 40L44 40" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 639 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Sketch</title>
|
||||
<path d="M7 57L25.53 51.48L12.52 38.47Z" fill="currentColor" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M7 57L25.53 51.48L53.81 23.2L40.8 10.19L12.52 38.47Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/><path d="M44.62 32.39L31.61 19.38" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 545 B |
@@ -1,4 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Tools</title>
|
||||
<path d="M56.86 18.45A16.5 16.5 0 0 1 37.12 39.04L22.33 53.83A8.6 8.6 0 0 1 10.17 41.67L24.96 26.88A16.5 16.5 0 0 1 45.55 7.14L37.55 15.14L39.94 24.06L48.86 26.45Z" fill="none" stroke="currentColor" stroke-width="6.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 360 B |
@@ -0,0 +1,21 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Disable component colors</title>
|
||||
<path d="M9.235 17.706L15.059 20.882L15.059 48.941L9.235 44.926Z" fill="#328bd3"/>
|
||||
<path d="M35.176 9.235L15.059 20.882L15.059 48.941L36.765 35.139Z" fill="#236ead"/>
|
||||
<path d="M29.046 5L35.176 8.176L15.196 19.718L9.066 16.541Z" fill="#b9ddf8"/>
|
||||
<path d="M29.046 5.529L35.176 8.706L35.176 36.235L15.059 49.471L9.235 45.765L9.066 17.071Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M9.066 17.6L15.059 20.882L34.647 9.235" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M15.059 20.882L15.059 47.067" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M32.222 18.595L44.949 25.944L44.949 51.122L32.222 43.774Z" fill="#328bd3"/>
|
||||
<path d="M54.934 20.173L44.949 25.944L44.949 51.122L54.934 45.352Z" fill="#236ead"/>
|
||||
<path d="M42.059 12.412L54.934 20.173L44.949 25.944L32 17.706Z" fill="#b9ddf8"/>
|
||||
<path d="M41.159 11.766L54.934 20.173L54.934 45.352L44.949 51.122L31.164 42.715L31.164 17.536Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M32.222 18.595L44.949 25.944L54.934 20.173" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M44.949 25.944L44.949 51.122" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.299 39.062L31.015 46.411L31.015 59L18.299 51.652Z" fill="#328bd3"/>
|
||||
<path d="M41.921 40.121L31.015 46.411L31.015 59L41.921 52.711Z" fill="#236ead"/>
|
||||
<path d="M29.194 32.773L41.921 40.121L31.015 46.411L18.299 39.062Z" fill="#b9ddf8"/>
|
||||
<path d="M29.194 32.773L41.921 40.121L41.921 52.711L31.015 59L18.299 51.652L18.299 39.062Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M18.299 39.062L31.015 46.411L41.921 40.121" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
<path d="M31.015 46.411L31.015 59" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.3 KiB |
@@ -79,6 +79,17 @@ pub(crate) fn snap_hint(painter: &egui::Painter, c: Pos2, kind: SnapKind, col: C
|
||||
painter.circle_filled(c, 1.3, col);
|
||||
}
|
||||
|
||||
/// A placed hole's drag handle: a ring round the hole's centre with a cross through it, filled
|
||||
/// while it is hovered or dragged.
|
||||
pub(crate) fn hole_handle(painter: &egui::Painter, c: Pos2, col: Color32, hot: bool) {
|
||||
let ic = Tokens::get().icons;
|
||||
let r = 6.0;
|
||||
painter.circle_filled(c, r + 3.5, ic.marker_halo);
|
||||
painter.circle(c, r, if hot { col } else { col.gamma_multiply(0.35) }, Stroke::new(1.6, col));
|
||||
let ink = Stroke::new(1.3, if hot { ic.highlight } else { col });
|
||||
painter.line_segment([c - vec2(r + 3.0, 0.0), c + vec2(r + 3.0, 0.0)], ink);
|
||||
painter.line_segment([c - vec2(0.0, r + 3.0), c + vec2(0.0, r + 3.0)], ink);
|
||||
}
|
||||
pub(crate) fn glyph_icon(name: &str) -> Option<&'static str> {
|
||||
Some(match name {
|
||||
"Coincident" => "c_coincident",
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
//! Navigation drawing definitions. Camera projection and picking remain in the UI widgets.
|
||||
use crate::theme::Tokens;
|
||||
use egui::{Color32, FontId, Pos2, Rect, Shape, Stroke, pos2, vec2};
|
||||
use solidcore_record::Vec3;
|
||||
use solidcraft_engine::geom::Ring;
|
||||
use solidcraft_engine::geom::{Ring, Vec3};
|
||||
pub(crate) struct Facet {
|
||||
pub(crate) direction: Vec3,
|
||||
pub(crate) points: Ring<Pos2>,
|
||||
@@ -152,12 +151,6 @@ pub(crate) fn curved_arrow(painter: &egui::Painter, at: Pos2, clockwise: bool, h
|
||||
/// The pointer target around a compass end: larger than the drawn disc, so it is easy to hit.
|
||||
pub(crate) const AXIS_RADIUS: f32 = 15.0;
|
||||
pub(crate) fn axis_tip(painter: &egui::Painter, c: Pos2, name: &str, d: &Vec3, at: Pos2, depth: f64, hovered: bool, t: Tokens) {
|
||||
axis_tip_scaled(painter, c, name, d, at, depth, hovered, t, 1.0);
|
||||
}
|
||||
|
||||
/// The same compass artwork at a uniform scale, including its lettering and strokes.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn axis_tip_scaled(painter: &egui::Painter, c: Pos2, name: &str, d: &Vec3, at: Pos2, depth: f64, hovered: bool, t: Tokens, scale: f32) {
|
||||
let col = if d.x != 0.0 {
|
||||
t.axis_x
|
||||
} else if d.y != 0.0 {
|
||||
@@ -170,11 +163,11 @@ pub(crate) fn axis_tip_scaled(painter: &egui::Painter, c: Pos2, name: &str, d: &
|
||||
// Positive shafts are the full axis colour; negative ones are a dimmed tint (and any end
|
||||
// pointing away from the viewer dims further).
|
||||
let ink = if !positive || depth < -0.01 { col.gamma_multiply(0.55) } else { col };
|
||||
let radius = if positive { 11.0 } else { 4.8 } * scale;
|
||||
let radius = if positive { 11.0 } else { 4.8 };
|
||||
let direction = (at - c).normalized();
|
||||
let width = 3.6 * scale;
|
||||
let width = 3.6;
|
||||
// A negative shaft stops at the outer edge of its ring; a positive one runs under its disc.
|
||||
let end = if positive { at - direction * radius } else { at - direction * (radius + 1.2 * scale) };
|
||||
let end = if positive { at - direction * radius } else { at - direction * (radius + 1.2) };
|
||||
painter.line_segment([c, end], Stroke::new(width, ink));
|
||||
// The round cap sits at the origin; the other end is covered by the disc or ring.
|
||||
painter.circle_filled(c, width / 2.0, ink);
|
||||
@@ -186,18 +179,18 @@ pub(crate) fn axis_tip_scaled(painter: &egui::Painter, c: Pos2, name: &str, d: &
|
||||
// kind turns fully white.
|
||||
if hovered {
|
||||
// Same outer size as the resting tip: a ring's outline is half its stroke outside.
|
||||
painter.circle_filled(at, if positive { radius } else { radius + 1.2 * scale }, t.on_accent);
|
||||
painter.circle_filled(at, if positive { radius } else { radius + 1.2 }, t.on_accent);
|
||||
// On the light theme a white disc vanishes into the compass, so it carries an outline in
|
||||
// the axis colour, drawn inside the disc so it does not grow.
|
||||
if light && positive {
|
||||
painter.circle_stroke(at, radius - scale, Stroke::new(2.0 * scale, col));
|
||||
painter.circle_stroke(at, radius - 1.0, Stroke::new(2.0, col));
|
||||
}
|
||||
}
|
||||
if positive {
|
||||
// On a hovered (white) disc the letter takes the axis colour.
|
||||
let letter = if hovered { col } else { t.on_accent };
|
||||
let label = name.to_uppercase();
|
||||
let galley = painter.layout_no_wrap(label, crate::fonts::bold(crate::theme::Tokens::get().font_compass * scale), letter);
|
||||
let galley = painter.layout_no_wrap(label, crate::fonts::bold(crate::theme::Tokens::get().font_compass), letter);
|
||||
let origin = at - galley.size() / 2.0;
|
||||
let ppp = painter.ctx().pixels_per_point().max(0.5);
|
||||
let origin = pos2((origin.x * ppp).round() / ppp, (origin.y * ppp).round() / ppp);
|
||||
@@ -208,7 +201,7 @@ pub(crate) fn axis_tip_scaled(painter: &egui::Painter, c: Pos2, name: &str, d: &
|
||||
at,
|
||||
radius,
|
||||
Stroke::new(
|
||||
2.4 * scale,
|
||||
2.4,
|
||||
if !hovered {
|
||||
ink
|
||||
} else if light {
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Pravougaoni",
|
||||
"Rectangular Tube": "Pravougaona cijev",
|
||||
"Reference face": "Referentno lice",
|
||||
"Reference plane": "Referentna ravan",
|
||||
"Relief Depth": "Dubina rasterećenja",
|
||||
"Relief Width": "Širina rasterećenja",
|
||||
"Remove Tools": "Ukloni alate",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Presjek",
|
||||
"Section (optional)": "Presjek (neobavezno)",
|
||||
"Section position": "Položaj presjeka",
|
||||
"Sections": "Presjeci",
|
||||
"Sections (one sketch each)": "Presjeci (po jedna skica)",
|
||||
"Select Other": "Izaberi drugo",
|
||||
"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).": "Izaberi drugo: držite lijevo dugme mirno na modelu (ili desni klik › Izaberi drugo…) da prikažete sve ispod pokazivača; ponovo kliknite na isto mjesto da izaberete sljedeći element. Dvaput kliknite srednjim dugmetom za Početni prikaz (Postavke › Navigacija).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Izdvoji u datoteku",
|
||||
"cmd.component_finish_edit": "Završi uređivanje",
|
||||
"cmd.component_from_bodies": "Napravi komponente iz tijela",
|
||||
"cmd.component_hide_history": "Sakrij vremensku traku komponente",
|
||||
"cmd.component_insert_design": "Umetni dizajn",
|
||||
"cmd.component_links": "Popis veza",
|
||||
"cmd.component_list": "Popis komponenti",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Preimenuj komponentu",
|
||||
"cmd.component_replace_with_link": "Zamijeni vezom",
|
||||
"cmd.component_revert_position": "Vrati položaj",
|
||||
"cmd.component_show_history": "Prikaži vremensku traku komponente",
|
||||
"cmd.component_unlock": "Otključaj sadržaj",
|
||||
"cmd.component_update_links": "Ažuriraj veze",
|
||||
"cmd.config_activate": "Aktiviraj konfiguraciju",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Pravilo konfiguracije",
|
||||
"cmd.config_rules": "Pravila konfiguracije",
|
||||
"cmd.config_table": "Prikaži tabelu konfiguracija",
|
||||
"cmd.construct.plane.axis_point": "Ravan kroz osu i tačku",
|
||||
"cmd.construct_axis_edge": "Osa kroz ivicu",
|
||||
"cmd.construct_axis_normal_to_face": "Osa okomita na lice",
|
||||
"cmd.construct_axis_through_cylinder": "Osa kroz cilindar/konus/torus",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Prikaži rastavljeni prikaz",
|
||||
"cmd.expr_evaluate": "Izračunaj izraz",
|
||||
"cmd.face_delete": "Izbriši lice",
|
||||
"cmd.file.export_3mf": "Izvezi 3MF",
|
||||
"cmd.file.export_obj": "Izvezi OBJ",
|
||||
"cmd.file.export_step": "Izvezi STEP",
|
||||
"cmd.file.export_stl": "Izvezi STL",
|
||||
"cmd.file.export_urdf": "Izvezi URDF",
|
||||
"cmd.file_export": "Izvoz",
|
||||
"cmd.file_insert_mesh": "Umetni mrežu",
|
||||
"cmd.file_insert_step": "Umetni STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Analiza dostupnosti",
|
||||
"cmd.inspect_center_of_mass": "Centar mase",
|
||||
"cmd.inspect_clearance": "Provjera zazora",
|
||||
"cmd.inspect_colored_components": "Omogući boje komponenti",
|
||||
"cmd.inspect_colored_components_disable": "Onemogući boje komponenti",
|
||||
"cmd.inspect_compare": "Uporedi tijela",
|
||||
"cmd.inspect_compare_picture": "Uporedi sliku",
|
||||
"cmd.inspect_continuity": "Analiza kontinuiteta",
|
||||
"cmd.inspect_curvature_comb": "Analiza češlja zakrivljenosti",
|
||||
"cmd.inspect_curvature_map": "Analiza mape zakrivljenosti",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Uredi oblak tačaka",
|
||||
"cmd.pointcloud_insert": "Umetni oblak tačaka",
|
||||
"cmd.pointcloud_list": "Oblaci tačaka",
|
||||
"cmd.render.compare.divider": "Pomjeri razdjelnik poređenja renderovanja",
|
||||
"cmd.render.result.discard": "Odbaci rezultat renderovanja",
|
||||
"cmd.render.result.select": "Izaberi rezultat renderovanja",
|
||||
"cmd.render_aging_describe": "Slojevito starenje površine",
|
||||
"cmd.render_backends": "Pozadinski sistemi renderovanja",
|
||||
"cmd.render_batch": "Renderuj sve kadrove",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Pomjeri oznaku vraćanja",
|
||||
"cmd.timeline_stop": "Zaustavi vremensku traku",
|
||||
"cmd.timeline_suppress": "Potisni operaciju",
|
||||
"cmd.ui.assistant": "Prikaži ili sakrij Asistenta",
|
||||
"cmd.view.render_browser": "Stablo renderovanja",
|
||||
"cmd.view.render_dock": "Premjesti panel renderovanja",
|
||||
"cmd.view.render_focus": "Prikaz fokusa renderovanja",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Alat za navigaciju",
|
||||
"cmd.view_top": "Pogled odozgo",
|
||||
"cmd.view_visual_style": "Vizuelni stil",
|
||||
"color.blue": "Plava",
|
||||
"color.copy": "Kliknite da kopirate vrijednosti boje",
|
||||
"color.edit": "Kliknite da uredite boju",
|
||||
"color.gamma": "Prikaz vrijednosti boje u gama prostoru 0-255",
|
||||
"color.green": "Zelena",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Unesite #rrggbb ili #rrggbbaa; šest cifara zadržava neprozirnost",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Prikaz vrijednosti boje u linearnom prostoru 0-1",
|
||||
"color.recent": "Nedavne boje",
|
||||
"color.recent_help": "Ovdje se pojavljuju boje koje izaberete u bilo kojem polju boje",
|
||||
"color.red": "Crvena",
|
||||
"color.saturation": "Zasićenost (procenat)",
|
||||
"color.saturation_value": "Zasićenost i vrijednost",
|
||||
"color.selected": "Izabrana boja",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Povećaj za korak",
|
||||
"color.value": "Vrijednost, svjetlina (procenat)",
|
||||
"config.column_filter_empty": "Nema odgovarajućih kolona",
|
||||
"config.rule_parameter_columns": "Prikazani su samo parametri koji imaju kolonu u tabeli. Dodajte kolonu pomoću Dodaj kolonu (+) da biste parametar koristili ovdje.",
|
||||
"config.rule_suppression_columns": "Prikazane su samo operacije koje imaju kolonu Potisni u tabeli. Dodajte kolonu pomoću Dodaj kolonu (+) da biste ovdje mogli upravljati operacijom.",
|
||||
"contact.components_count": "Komponente: {count}",
|
||||
"contact.enabled_label": "omogućeno",
|
||||
"coupling.gear_1": "Zupčanik 1",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Izaberite tijela ili operacije: skica nije ni jedno ni drugo. Ponovite njene krive pomoću alata Niz ili Preslikaj u samoj skici ili je prvo ekstrudirajte.",
|
||||
"dialog.select": "↖ Izaberi",
|
||||
"dialog.selected_count": "↖ Izabrano: {count}",
|
||||
"dialog.selection_constraint": "Ograničenje {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · kriva {id}",
|
||||
"dialog.selection_guidance": "Izaberite ulaz da ga promijenite. Kliknite izabrane objekte da ih uklonite; Ctrl/Cmd-klik ih također uključuje i isključuje. Pogledajte pregled, a zatim pritisnite U redu.",
|
||||
"dialog.selection_midpoint": "Središnja tačka · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step} od {total}: Izaberite {input}",
|
||||
"dialog.step": "Korak",
|
||||
"document.changed_on_disk": "{name} je promijenjen na disku",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Pravi pomoćnu osu kroz dvije tačke, vrhove ili tačke skice. Koristite je za osu koja prolazi između dva poznata položaja.",
|
||||
"help.construct.plane.along_path": "Pravi pomoćnu ravan okomitu na ivicu ili krivu, na nekom položaju duž nje. Koristite je da skicirate profil za povlačenje ili presjek putanje.",
|
||||
"help.construct.plane.at_angle": "Pravi pomoćnu ravan zakrenutu za ugao u odnosu na ravan ili lice, oko ose ili ivice. Koristite je za nagnute skice, npr. kutni nosač.",
|
||||
"help.construct.plane.axis_point": "Pravi pomoćnu ravan koja sadrži osu i tačku izvan te ose. Izaberite osu ishodišta ili pomoćnu osu, ravnu ivicu ili liniju skice, pa vrh, tačku skice ili pomoćnu tačku. Tačka na osi ne može odrediti jedinstvenu ravan.",
|
||||
"help.construct.plane.midplane": "Pravi pomoćnu ravan na pola puta između dvije ravni ili ravna lica, ili koja ih raspolavlja tamo gdje se sastaju pod uglom. Koristite je kao ravan simetrije za preslikavanje.",
|
||||
"help.construct.plane.offset": "Pravi pomoćnu ravan paralelnu s ravni ili ravnim licem, na određenoj udaljenosti ili kroz tačku. Koristite je da skicirate na zadatoj visini iznad lica.",
|
||||
"help.construct.plane.perpendicular": "Pravi pomoćnu ravan koja sadrži liniju ili ivicu i okomita je na ravan ili lice. Koristite je da skicirate uspravno na modelu duž ivice.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Provjerite vrstu spoja (npr. Kruti), učvršćenje, granice spoja i vrijednosti koje pokreću parametri.",
|
||||
"joint_move.blocked_title": "Ovaj dio se ne može pomjeriti",
|
||||
"joint_move.coarse": "Korak rotacije blizu ose (°)",
|
||||
"joint_move.conflict": "Jedan spoj ovog dijela je u sukobu s ostalima: {joint}.",
|
||||
"joint_move.conflict_hint": "Uredite, potisnite ili izbrišite spoj koji je u sukobu da bi ostali spojevi mogli postaviti dio.",
|
||||
"joint_move.distance": "Udaljenost od početka",
|
||||
"joint_move.done": "Gotovo",
|
||||
"joint_move.fine": "Korak rotacije dalje od ose (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "Staza {number}",
|
||||
"nav.alias.help": "Shift+Alt+povlačenje lijevim dugmetom orbitira, Shift+Alt+srednjim pomjera, Shift+Alt+desnim zumira; povlačenje srednjim dugmetom također pomjera, a desnim orbitira",
|
||||
"nav.blender.help": "Povlačenje srednjim dugmetom orbitira, Shift+srednje pomjera, Ctrl+srednje zumira; povlačenje desnim dugmetom ne dira kameru",
|
||||
"nav.creo.help": "Povlačenje srednjim dugmetom orbitira, Shift+srednje pomjera, Ctrl+srednje uz vertikalno povlačenje zumira (prema dolje za približavanje); točkić također zumira",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "Miš i 3D miš rotiraju kroz oba pola i čuvaju nagib. Privlačenje osa tipkom Shift je isključeno.",
|
||||
"nav.free_orbit.label": "Slobodno orbitiranje",
|
||||
"nav.free_orbit.snap_help": "Miš i 3D miš rotiraju kroz oba pola i čuvaju nagib. Držite Shift tokom povlačenja za orbitiranje da privučete smjer pogleda i nagib.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Vijak s poluokruglom glavom i imbusom",
|
||||
"parts.family.dowel_pin": "Cilindrični zatik",
|
||||
"parts.family.hex_bolt": "Vijak sa šestougaonom glavom",
|
||||
"parts.family.hex_cap_screw_inch": "Vijak sa šestougaonom glavom (inč)",
|
||||
"parts.family.hex_nut": "Šestougaona matica",
|
||||
"parts.family.hex_nut_inch": "Šestougaona matica (inč)",
|
||||
"parts.family.set_screw": "Bezglavi vijak s imbusom, ravni vrh",
|
||||
"parts.family.socket_head_cap_screw": "Vijak s cilindričnom glavom i imbusom",
|
||||
"parts.family.washer": "Obična podloška",
|
||||
"parts.family.washer_sae": "Obična podloška, SAE (uska)",
|
||||
"parts.family.washer_uss": "Obična podloška, USS (široka)",
|
||||
"parts.holes": "Rupe (neobavezno)",
|
||||
"parts.placement_hint": "izaberite jednu ili više rupa da u svaku postavite dio, ili ga U redu postavlja u ishodište",
|
||||
"parts.system": "Sistem jedinica",
|
||||
"parts.system.inch": "Inčni",
|
||||
"parts.system.metric": "Metrički",
|
||||
"pattern.alignment_2": "Poravnanje 2",
|
||||
"pattern.alignment_hint": "Broj uključuje original. Centar ravnomjerno raspoređuje kopije; Obrni bira dodatnu stranu za parne ukupne brojeve.",
|
||||
"pattern.direction_1": "Smjer 1",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Povlačenje lijevim dugmetom",
|
||||
"prefs.button_middle": "Povlačenje srednjim dugmetom",
|
||||
"prefs.button_right": "Povlačenje desnim dugmetom",
|
||||
"prefs.carousel_next": "Sljedeće",
|
||||
"prefs.carousel_previous": "Prethodno",
|
||||
"prefs.choose_font": "Izaberi datoteku fonta…",
|
||||
"prefs.click": "Klik",
|
||||
"prefs.contact_shading": "Meko sjenčenje dodira",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Zakrivljene površi",
|
||||
"prefs.curves": "Zakrivljene površi",
|
||||
"prefs.cut_preview_tint": "Prikaži uklonjeni materijal u crvenoj boji",
|
||||
"prefs.cut_preview_tint_help": "Pregled ispunjava materijal koji operacija uklanja i oblik koji zamjenjuje crvenom prozirnom sjenom. Kada je isključeno, te ispune su potpuno prozirne i crtaju se samo njihovi obrisi.",
|
||||
"prefs.dark_parts_shading": "Zadrži oblik tamnih dijelova",
|
||||
"prefs.dark_parts_shading_help": "Sjenči prikaz u linearnom svjetlu s mekim dopunskim svjetlom neba, tako da tamna lica zadrže gradijent umjesto da pocrne",
|
||||
"prefs.dead_zone_note": "Šrafirana traka je mrtva zona: kretanje unutar nje se zanemaruje.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Jezik interfejsa",
|
||||
"prefs.interface_note": "Kako SolidCraft izgleda i gdje su njegovi dijelovi.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Obrni zakrivljene strelice za valjanje",
|
||||
"prefs.invert_curved_roll_arrows_note": "Dvije zakrivljene strelice u uglu kocke valjaju prikaz unutar ravni ekrana. Uključeno: strelica u smjeru kazaljke na satu valja prikaz suprotno od smjera kazaljke na satu, a strelica suprotno od smjera kazaljke na satu valja ga u smjeru kazaljke na satu. Isključeno: svaka strelica valja prikaz onako kako je zakrivljena.",
|
||||
"prefs.invert_navigator_arrows": "Obrni strelice za rotaciju kocke prikaza",
|
||||
"prefs.invert_navigator_arrows_note": "Strelice oko kocke orbitiraju kameru u smjeru u kojem pokazuju, kao u programima Fusion i Inventor: strelica s lijeve strane pokazuje udesno i zakreće kameru udesno. Isključeno: strelica okreće kocku prema strani na kojoj se nalazi, pa lijeva strelica zakreće kameru ulijevo. Postavka ne utiče na zakrivljene strelice za valjanje.",
|
||||
"prefs.keep_work_on_tab_switch": "Zadrži dijaloge i alate otvorenima pri prebacivanju između dizajna",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Postavljanje",
|
||||
"prefs.prefer_free_sketch_points": "Daj prednost pokretnim tačkama skice kada se tačke preklapaju",
|
||||
"prefs.preview_choices": "Pregled vaših izbora",
|
||||
"prefs.preview_tint_new_faces": "Oboji novu površ u pregledu",
|
||||
"prefs.preview_tint_new_faces_help": "Pregled operacije koja mijenja tijelo može nacrtati površ koju dodaje u boji pregleda. Kada je isključeno, tijelo se crta kakvo jest.",
|
||||
"prefs.print_face_highlight": "Isticanje površine za štampu",
|
||||
"prefs.print_face_highlight_tip": "Koliko se jako površina za štampu tijela boji i obrubljuje u prikazu. 0 % ne prikazuje isticanje.",
|
||||
"prefs.profile_active": "Aktivni profil",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Osnovna boja · sRGB",
|
||||
"render.base_metallic": "Metalni udio",
|
||||
"render.base_opaque_only": "Ovaj uređivač čuva semantiku neprozirnih tijela. Za optičko propuštanje koristite izričite kontrole stakla.",
|
||||
"render.batch_all_shots": "Svi kadrovi ({count})",
|
||||
"render.batch_empty": "Sačuvajte kadar prije renderovanja svih kadrova.",
|
||||
"render.batch_limit": "Renderovanje svih kadrova ograničeno je na 32 kadra.",
|
||||
"render.batch_status": "U toku: {running} · Na čekanju: {waiting}",
|
||||
"render.bloom_enable": "Sjaj prikaza",
|
||||
"render.bloom_help": "Kreativni sjaj samo na izlazu iz pozitivnih linearnih svijetlih dijelova, raširen kroz tri normalizovana prolaza kutijastog filtera. Poluprečnik je u pikselima izlaza. Dodaje se u linearnoj svjetlosti prikaza prije sRGB-a; nije objektiv, volumetrijski efekat niti poboljšanje konvergencije. Sirovi EXR ostaje nepromijenjen.",
|
||||
"render.bloom_opaque": "Sjaj zahtijeva neprozirni izlaz. Prozirni PNG se odbija, a ne spljoštava tiho.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Prvo izaberite izgled u Izgledima",
|
||||
"render.chromatic_offset": "Hromatski pomak ivica",
|
||||
"render.clear_stage": "Ukloni sačuvanu scenu",
|
||||
"render.compare_divider": "Razdjelnik poređenja",
|
||||
"render.compare_live": "Uživo",
|
||||
"render.compare_live_preview": "Pregled uživo",
|
||||
"render.compare_render": "Renderovanje {number}",
|
||||
"render.compare_side": "Renderovanje {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · zabilježeno u reviziji {revision}",
|
||||
"render.compare_swap": "Zamijeni strane",
|
||||
"render.copy_request": "Kopiraj JSON",
|
||||
"render.cpu_aa_help": "Prostorni uzorci: više uzoraka poboljšava ivice i produžava renderovanje.",
|
||||
"render.cpu_area_add": "Dodaj pravougaono CPU svjetlo",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Prikazuje se posljednja završena slika; novije renderovanje je još nije zamijenilo.",
|
||||
"render.result_sequence": "Kontrolna tačka",
|
||||
"render.result_title": "Rezultat renderovanja",
|
||||
"render.results_choose": "Zadržani rezultati",
|
||||
"render.results_label": "Renderovanje {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [zastarjelo]",
|
||||
"render.results_unnamed": "Slika",
|
||||
"render.return_design": "Nazad na Dizajn",
|
||||
"render.review_stale": "Ovaj pregled je zastario. Riješite ga ili ponovo provjerite prije primjene.",
|
||||
"render.ribbon_aging": "Starenje",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "oba ishodišta su na istoj komponenti",
|
||||
"thread.automatic_size": "Automatska veličina",
|
||||
"thread.designation": "Oznaka",
|
||||
"thread.direction": "Smjer navoja",
|
||||
"thread.iso_metric": "ISO metrički",
|
||||
"thread.left_hand": "Lijevi",
|
||||
"thread.right_hand": "Desni",
|
||||
"thread.select_size": "Izaberite veličinu",
|
||||
"timeline.delete_after_marker_one": "Izbriši 1 operaciju nakon oznake vraćanja",
|
||||
"timeline.delete_after_marker_other": "Izbriši operacije nakon oznake vraćanja: {count}",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Pokrenite ovo u terminalu, a zatim pritisnite Poveži ponovo:",
|
||||
"ui.agent_chat_auth_title": "Prijavi se kod agenta",
|
||||
"ui.agent_chat_cancel": "Otkaži",
|
||||
"ui.agent_chat_cancel_timeout": "Agent nije odgovorio na Zaustavi pa je veza s njim prekinuta. Povežite se ponovo da biste nastavili.",
|
||||
"ui.agent_chat_cannot_start": "Instalirano, ali se ne pokreće: {error}",
|
||||
"ui.agent_chat_changes": "Promjene: {n}, jedan korak poništavanja",
|
||||
"ui.agent_chat_changes_n": "Promjene: {n}",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Komanda agenta",
|
||||
"ui.agent_chat_connect": "Poveži",
|
||||
"ui.agent_chat_copy": "Kopiraj",
|
||||
"ui.agent_chat_copy_message": "Kopiraj poruku",
|
||||
"ui.agent_chat_ctx_bodies": "Tijela",
|
||||
"ui.agent_chat_ctx_elements": "Elementi",
|
||||
"ui.agent_chat_ctx_features": "Operacije",
|
||||
"ui.agent_chat_ctx_more": "+još {n}",
|
||||
"ui.agent_chat_ctx_other": "Ostalo",
|
||||
"ui.agent_chat_custom_heading": "Prilagođeni model",
|
||||
"ui.agent_chat_custom_hint": "Bilo koji program koji govori Agent Client Protocol na stdin i stdout. Ne pokreće ga nikakva ljuska.",
|
||||
"ui.agent_chat_denied": "Odbijeno",
|
||||
"ui.agent_chat_deny": "Odbij",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Sve",
|
||||
"ui.agent_chat_mode_ask": "Pitaj",
|
||||
"ui.agent_chat_mode_edits": "Izmjene",
|
||||
"ui.agent_chat_model_custom": "Koristi prilagođeni: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} ne može koristiti SolidCraft alate, pa može samo razgovarati. Izaberite model s podrškom za alate.",
|
||||
"ui.agent_chat_model_rejected": "Agent nije prihvatio model {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Pretraži modele…",
|
||||
"ui.agent_chat_model_tip": "Model koji agent koristi. Vaš izbor se pamti za ovog agenta.",
|
||||
"ui.agent_chat_models_failed": "Popis modela nije moguće učitati: {error}",
|
||||
"ui.agent_chat_models_heading": "Dostupni modeli",
|
||||
"ui.agent_chat_models_listing": "Učitavanje popisa modela…",
|
||||
"ui.agent_chat_models_none": "Krajnja tačka ne navodi nijedan model",
|
||||
"ui.agent_chat_models_not_a_list": "Odgovor nije popis modela",
|
||||
"ui.agent_chat_new": "Novi razgovor",
|
||||
"ui.agent_chat_new_confirm": "Započeti novi razgovor? Trenutni će biti obrisan.",
|
||||
"ui.agent_chat_next_picture": "Sljedeća slika",
|
||||
@@ -4472,11 +4407,9 @@
|
||||
"ui.construction.contact_hint": "Tangenta: izaberite krug/luk ili otvoreni splajn/koniku, pa tačku dodira (krive slobodnog oblika: krajnje tačke)",
|
||||
"ui.construction.external_hint": "Tangenta: izaberite krug ili luk, vanjsku tačku, pa željeno rješenje",
|
||||
"ui.construction.free_hint": "Pomoćna prava: kliknite dvije tačke; desnim klikom počnite ispočetka",
|
||||
"ui.construction.normal_needs_length": "Beskonačna linija je uvijek pomoćna: upišite dužinu da biste nacrtali običnu liniju",
|
||||
"ui.construction.point_hint": "Pomoćna prava: kliknite tačku kroz koju prolazi",
|
||||
"ui.construction.reference_hint": "Pomoćna prava: izaberite pravu liniju, pa tačku; desnim klikom promijenite referencu",
|
||||
"ui.construction.ribbon_label": "Pomoćne",
|
||||
"ui.construction.side_hint": "Pomoćna prava: kliknite stranu prema kojoj linija ide; desnim klikom počnite ispočetka",
|
||||
"ui.context_menu": "Kontekstni meni",
|
||||
"ui.continuity_g0": "G0 pregib",
|
||||
"ui.continuity_g1": "G1 tangentno",
|
||||
@@ -4742,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Sakrij ishodište",
|
||||
"ui.hidePlanes": "Sakrij ravni",
|
||||
"ui.hideProfile": "Sakrij profil",
|
||||
"ui.hide_component_history": "Sakrij njenu vremensku traku",
|
||||
"ui.hide_origin_by_default": "Podrazumijevano sakrij ishodište",
|
||||
"ui.hide_origin_note": "Novi dizajni počinju sa skrivenim folderom Ishodište. Dizajn zadržava vlastitu postavku Ishodišta kada ga prikažete ili sakrijete.",
|
||||
"ui.history_back": "Idi korak nazad",
|
||||
@@ -4750,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "dvaput kliknite ivicu",
|
||||
"ui.history_first": "Idi na početak",
|
||||
"ui.history_forward": "Idi korak naprijed",
|
||||
"ui.history_hide_hidden": "Sakrij skrivene komponente",
|
||||
"ui.history_hide_names": "Sakrij nazive",
|
||||
"ui.history_last": "Idi na kraj",
|
||||
"ui.history_marker_tip": "Oznaka vraćanja: povucite da vratite model unazad; nove operacije dolaze ovdje",
|
||||
@@ -4760,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Prikaži dugme za reprodukciju",
|
||||
"ui.history_play_finish_action": "Završite trenutnu radnju prije reprodukcije vremenske trake",
|
||||
"ui.history_play_roll_back": "Pomjerite oznaku vraćanja unazad prije reprodukcije",
|
||||
"ui.history_show_hidden": "Prikaži skrivene komponente",
|
||||
"ui.history_show_names": "Prikaži nazive",
|
||||
"ui.history_sketch_listed_under": "Navedeno ispod {feature}",
|
||||
"ui.history_title": "Vremenska traka",
|
||||
"ui.history_toggle_names": "Prikaži ili sakrij nazive operacija",
|
||||
"ui.history_waits_for_editor": "Poništi, Ponovi i vremenska traka čekaju dok se ne otvori uređivač operacije",
|
||||
"ui.hole": "Rupa",
|
||||
"ui.hole_tap_type": "Vrsta navoja rupe",
|
||||
"ui.hole_type": "Vrsta rupe",
|
||||
@@ -5046,7 +4975,6 @@
|
||||
"ui.showDimension": "Prikaži kotu",
|
||||
"ui.showOrigin": "Prikaži ishodište",
|
||||
"ui.showPlanes": "Prikaži ravni",
|
||||
"ui.show_component_history": "Prikaži njenu vremensku traku",
|
||||
"ui.show_constraints": "Ograničenja",
|
||||
"ui.show_construction_geometries": "Pomoćna geometrija",
|
||||
"ui.show_dimensions": "Kote",
|
||||
@@ -5273,7 +5201,6 @@
|
||||
"web.open_link": "Otvoriti povezani dizajn?",
|
||||
"web.open_link_confirm": "Postoje nesačuvane promjene. Otvoriti povezani dizajn u novoj kartici?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ izaberi · Enter / Tab dovrši · Esc odbaci",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ izbor · Enter primjenjuje ovu vrijednost",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ izaberi · Enter primjenjuje ono što ste upisali · Esc odbaci",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter nastavlja na GitHubu."
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Obdélníkový",
|
||||
"Rectangular Tube": "Obdélníková trubka",
|
||||
"Reference face": "Referenční plocha",
|
||||
"Reference plane": "Referenční rovina",
|
||||
"Relief Depth": "Hloubka odlehčení",
|
||||
"Relief Width": "Šířka odlehčení",
|
||||
"Remove Tools": "Odstranit nástroje",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Řez",
|
||||
"Section (optional)": "Průřez (volitelné)",
|
||||
"Section position": "Poloha průřezu",
|
||||
"Sections": "Průřezy",
|
||||
"Sections (one sketch each)": "Průřezy (každý jeden náčrt)",
|
||||
"Select Other": "Vybrat jiné",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Vybrat jiné: podržte levé tlačítko na modelu (nebo pravé tlačítko › Vybrat jiné…) a zobrazí se vše pod kurzorem; dalším kliknutím na stejné místo vyberete další prvek. Dvojklikem prostředním tlačítkem přejdete na výchozí pohled (Předvolby › Navigace).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Extrahovat do souboru",
|
||||
"cmd.component_finish_edit": "Dokončit úpravu",
|
||||
"cmd.component_from_bodies": "Vytvořit komponenty z těles",
|
||||
"cmd.component_hide_history": "Skrýt historii komponenty",
|
||||
"cmd.component_insert_design": "Vložit návrh",
|
||||
"cmd.component_links": "Seznam odkazů",
|
||||
"cmd.component_list": "Seznam komponent",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Přejmenovat komponentu",
|
||||
"cmd.component_replace_with_link": "Nahradit odkazem",
|
||||
"cmd.component_revert_position": "Vrátit polohu",
|
||||
"cmd.component_show_history": "Zobrazit historii komponenty",
|
||||
"cmd.component_unlock": "Odemknout obsah",
|
||||
"cmd.component_update_links": "Aktualizovat odkazy",
|
||||
"cmd.config_activate": "Aktivovat konfiguraci",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Pravidlo konfigurace",
|
||||
"cmd.config_rules": "Pravidla konfigurace",
|
||||
"cmd.config_table": "Zobrazit tabulku konfigurací",
|
||||
"cmd.construct.plane.axis_point": "Rovina osou a bodem",
|
||||
"cmd.construct_axis_edge": "Osa hranou",
|
||||
"cmd.construct_axis_normal_to_face": "Osa kolmá k ploše",
|
||||
"cmd.construct_axis_through_cylinder": "Osa válce/kužele/anuloidu",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Zobrazit rozložený pohled",
|
||||
"cmd.expr_evaluate": "Vyhodnotit výraz",
|
||||
"cmd.face_delete": "Smazat plochu",
|
||||
"cmd.file.export_3mf": "Exportovat 3MF",
|
||||
"cmd.file.export_obj": "Exportovat OBJ",
|
||||
"cmd.file.export_step": "Exportovat STEP",
|
||||
"cmd.file.export_stl": "Exportovat STL",
|
||||
"cmd.file.export_urdf": "Exportovat URDF",
|
||||
"cmd.file_export": "Exportovat",
|
||||
"cmd.file_insert_mesh": "Vložit síť",
|
||||
"cmd.file_insert_step": "Vložit STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Analýza přístupnosti",
|
||||
"cmd.inspect_center_of_mass": "Těžiště",
|
||||
"cmd.inspect_clearance": "Vůle",
|
||||
"cmd.inspect_colored_components": "Zapnout barvy komponent",
|
||||
"cmd.inspect_colored_components_disable": "Vypnout barvy komponent",
|
||||
"cmd.inspect_compare": "Porovnat tělesa",
|
||||
"cmd.inspect_compare_picture": "Porovnat obrázek",
|
||||
"cmd.inspect_continuity": "Analýza spojitosti",
|
||||
"cmd.inspect_curvature_comb": "Analýza hřebenu křivosti",
|
||||
"cmd.inspect_curvature_map": "Mapa křivosti",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Upravit mračno bodů",
|
||||
"cmd.pointcloud_insert": "Vložit mračno bodů",
|
||||
"cmd.pointcloud_list": "Mračna bodů",
|
||||
"cmd.render.compare.divider": "Posunout dělicí čáru porovnání renderů",
|
||||
"cmd.render.result.discard": "Zahodit výsledek renderu",
|
||||
"cmd.render.result.select": "Vybrat výsledek renderu",
|
||||
"cmd.render_aging_describe": "Vrstvené stárnutí povrchu",
|
||||
"cmd.render_backends": "Renderovací jádra",
|
||||
"cmd.render_batch": "Renderovat všechny záběry",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Přesunout značku časové osy",
|
||||
"cmd.timeline_stop": "Zastavit historii",
|
||||
"cmd.timeline_suppress": "Potlačit prvek",
|
||||
"cmd.ui.assistant": "Zobrazit nebo skrýt Asistenta",
|
||||
"cmd.view.render_browser": "Prohlížeč renderu",
|
||||
"cmd.view.render_dock": "Přesunout panel renderu",
|
||||
"cmd.view.render_focus": "Soustředěný pohled na render",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Navigační nástroj",
|
||||
"cmd.view_top": "Pohled shora",
|
||||
"cmd.view_visual_style": "Vizuální styl",
|
||||
"color.blue": "Modrá",
|
||||
"color.copy": "Kliknutím zkopírujete hodnoty barvy",
|
||||
"color.edit": "Kliknutím upravíte barvu",
|
||||
"color.gamma": "Hodnoty barvy v gama prostoru 0–255",
|
||||
"color.green": "Zelená",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Zadejte #rrggbb nebo #rrggbbaa; šest číslic zachová neprůhlednost",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Hodnoty barvy v lineárním prostoru 0–1",
|
||||
"color.recent": "Nedávné barvy",
|
||||
"color.recent_help": "Barvy, které vyberete v libovolném poli barvy, se zobrazí zde",
|
||||
"color.red": "Červená",
|
||||
"color.saturation": "Sytost (v procentech)",
|
||||
"color.saturation_value": "Sytost a jas",
|
||||
"color.selected": "Vybraná barva",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Zvýšit o krok",
|
||||
"color.value": "Hodnota, tedy jas (v procentech)",
|
||||
"config.column_filter_empty": "Žádné odpovídající sloupce",
|
||||
"config.rule_parameter_columns": "Uvedeny jsou jen parametry, které mají v tabulce sloupec. Sloupec přidáte pomocí Přidat sloupec (+), abyste parametr mohli použít zde.",
|
||||
"config.rule_suppression_columns": "Uvedeny jsou jen prvky, které mají v tabulce sloupec Potlačit. Sloupec přidáte pomocí Přidat sloupec (+), abyste mohli prvek řídit zde.",
|
||||
"contact.components_count": "Počet komponent: {count}",
|
||||
"contact.enabled_label": "povoleno",
|
||||
"coupling.gear_1": "Kolo 1",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Vyberte tělesa nebo prvky: náčrt není ani jedno. Opakujte jeho křivky vlastním nástrojem Pole nebo Zrcadlit v náčrtu, nebo jej nejprve vysuňte.",
|
||||
"dialog.select": "↖ Vybrat",
|
||||
"dialog.selected_count": "↖ Vybráno: {count}",
|
||||
"dialog.selection_constraint": "Vazba {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · křivka {id}",
|
||||
"dialog.selection_guidance": "Vstup změníte jeho výběrem. Kliknutím na vybraný objekt jej odeberete; Ctrl/Cmd+klik výběr také přepíná. Zkontrolujte náhled a stiskněte OK.",
|
||||
"dialog.selection_midpoint": "Střed · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step} z {total}: Vyberte {input}",
|
||||
"dialog.step": "Krok",
|
||||
"document.changed_on_disk": "Soubor {name} byl změněn na disku",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Vytvoří konstrukční osu procházející dvěma body, vrcholy nebo body náčrtu. Použijte ji pro osu vedoucí mezi dvěma známými polohami.",
|
||||
"help.construct.plane.along_path": "Vytvoří konstrukční rovinu kolmou k hraně nebo křivce v zadané poloze podél ní. Použijte to k náčrtu profilu pro tažení nebo příčného řezu podél cesty.",
|
||||
"help.construct.plane.at_angle": "Vytvoří konstrukční rovinu natočenou o úhel od roviny nebo plochy kolem osy nebo hrany. Použijte ji pro šikmé náčrty, například u úhlového držáku.",
|
||||
"help.construct.plane.axis_point": "Vytvoří konstrukční rovinu obsahující osu a bod mimo tuto osu. Vyberte osu počátku nebo konstrukční osu, přímou hranu nebo úsečku náčrtu a pak vrchol, bod náčrtu nebo konstrukční bod. Bod ležící na ose nemůže určit jednoznačnou rovinu.",
|
||||
"help.construct.plane.midplane": "Vytvoří konstrukční rovinu uprostřed mezi dvěma rovinami nebo rovinnými plochami, nebo rovinu půlící jejich úhel, když se protínají pod úhlem. Použijte ji jako rovinu souměrnosti pro zrcadlení.",
|
||||
"help.construct.plane.offset": "Vytvoří konstrukční rovinu rovnoběžnou s rovinou nebo rovinnou plochou ve zvolené vzdálenosti nebo procházející bodem. Použijte ji k náčrtu v dané výšce nad plochou.",
|
||||
"help.construct.plane.perpendicular": "Vytvoří konstrukční rovinu, která obsahuje přímku nebo hranu a je kolmá k rovině nebo ploše. Použijte ji k náčrtu kolmo na model podél hrany.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Zkontrolujte typ kloubu (např. Pevný), ukotvení, limity kloubu a hodnoty řízené parametry.",
|
||||
"joint_move.blocked_title": "Tento díl nelze posunout",
|
||||
"joint_move.coarse": "Krok otáčení blízko osy (°)",
|
||||
"joint_move.conflict": "Kloub tohoto dílu je v konfliktu s ostatními: {joint}.",
|
||||
"joint_move.conflict_hint": "Upravte, potlačte nebo smažte kloub v konfliktu, aby mohly díl umístit ostatní klouby.",
|
||||
"joint_move.distance": "Vzdálenost od začátku",
|
||||
"joint_move.done": "Hotovo",
|
||||
"joint_move.fine": "Krok otáčení daleko od osy (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "Stopa {number}",
|
||||
"nav.alias.help": "Shift+Alt+tažení levým tlačítkem otáčí, Shift+Alt+tažení prostředním posouvá, Shift+Alt+tažení pravým přibližuje; tažení prostředním tlačítkem také posouvá a pravým otáčí",
|
||||
"nav.blender.help": "Tažení prostředním tlačítkem otáčí, Shift+prostřední posouvá, Ctrl+prostřední přibližuje; tažení pravým tlačítkem kameru nemění",
|
||||
"nav.creo.help": "Tažení prostředním tlačítkem otáčí, Shift+prostřední posouvá, Ctrl+prostřední se svislým tažením přibližuje (tažením dolů přiblížíte); přibližuje i kolečko",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "Myš a 3D myš otáčejí přes oba póly a zachovávají natočení. Přichytávání os klávesou Shift je vypnuto.",
|
||||
"nav.free_orbit.label": "Volné otáčení",
|
||||
"nav.free_orbit.snap_help": "Myš a 3D myš otáčejí přes oba póly a zachovávají natočení. Podržením klávesy Shift při otáčení tažením přichytíte směr pohledu a natočení.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Šroub s půlkulovou hlavou a vnitřním šestihranem",
|
||||
"parts.family.dowel_pin": "Válcový kolík (lícovaný)",
|
||||
"parts.family.hex_bolt": "Šroub se šestihrannou hlavou",
|
||||
"parts.family.hex_cap_screw_inch": "Šroub se šestihrannou hlavou (palcový)",
|
||||
"parts.family.hex_nut": "Šestihranná matice",
|
||||
"parts.family.hex_nut_inch": "Šestihranná matice (palcová)",
|
||||
"parts.family.set_screw": "Stavěcí šroub s vnitřním šestihranem, s plochým koncem",
|
||||
"parts.family.socket_head_cap_screw": "Šroub s válcovou hlavou a vnitřním šestihranem",
|
||||
"parts.family.washer": "Plochá podložka",
|
||||
"parts.family.washer_sae": "Plochá podložka, SAE (úzká)",
|
||||
"parts.family.washer_uss": "Plochá podložka, USS (široká)",
|
||||
"parts.holes": "Otvory (volitelné)",
|
||||
"parts.placement_hint": "vyberte jeden nebo více otvorů, aby se do každého usadil díl, nebo OK umístí díl do počátku",
|
||||
"parts.system": "Soustava jednotek",
|
||||
"parts.system.inch": "Palcová",
|
||||
"parts.system.metric": "Metrická",
|
||||
"pattern.alignment_2": "Zarovnání 2",
|
||||
"pattern.alignment_hint": "Počty zahrnují originál. Na střed rozloží kopie rovnoměrně; Převrátit volí stranu navíc u sudých počtů.",
|
||||
"pattern.direction_1": "Směr 1",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Tažení levým tlačítkem",
|
||||
"prefs.button_middle": "Tažení prostředním tlačítkem",
|
||||
"prefs.button_right": "Tažení pravým tlačítkem",
|
||||
"prefs.carousel_next": "Další",
|
||||
"prefs.carousel_previous": "Předchozí",
|
||||
"prefs.choose_font": "Vybrat soubor písma…",
|
||||
"prefs.click": "Klik",
|
||||
"prefs.contact_shading": "Měkké stínování kontaktů",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Zakřivené plochy",
|
||||
"prefs.curves": "Zakřivené plochy",
|
||||
"prefs.cut_preview_tint": "Zobrazit odebraný materiál červeně",
|
||||
"prefs.cut_preview_tint_help": "Náhled vyplní materiál, který prvek odebírá, i tvar, který nahrazuje, červeným průhledným stínem. Když je vypnuto, jsou tyto výplně zcela průhledné a kreslí se jen jejich obrysy.",
|
||||
"prefs.dark_parts_shading": "Zachovat tvar tmavých dílů",
|
||||
"prefs.dark_parts_shading_help": "Stínuje pohled v lineárním světle s jemným světlem oblohy, takže tmavé plochy zachovají přechod a nezčernají",
|
||||
"prefs.dead_zone_note": "Šrafované pásmo je mrtvá zóna: pohyb uvnitř něj se ignoruje.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Jazyk rozhraní",
|
||||
"prefs.interface_note": "Vzhled aplikace SolidCraft a rozmístění jejích částí.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Obrátit zakřivené šipky natočení",
|
||||
"prefs.invert_curved_roll_arrows_note": "Dvě zakřivené šipky v rohu krychle natáčejí pohled v rovině obrazovky. Zapnuto: šipka po směru hodinových ručiček natáčí pohled proti směru hodinových ručiček a šipka proti směru hodinových ručiček ho natáčí po směru. Vypnuto: každá šipka natáčí pohled tak, jak je zakřivená.",
|
||||
"prefs.invert_navigator_arrows": "Obrátit šipky otáčení krychle pohledu",
|
||||
"prefs.invert_navigator_arrows_note": "Šipky kolem krychle otáčejí kameru tím směrem, kam míří, jako ve Fusion a Inventor: šipka vlevo míří doprava a stáčí kameru doprava. Vypnuto: šipka otáčí krychli na stranu, na které leží, takže šipka vlevo stáčí kameru doleva. Zakřivené šipky natočení tím nejsou dotčeny.",
|
||||
"prefs.keep_work_on_tab_switch": "Ponechat dialogy a nástroje otevřené při přepínání návrhů",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Umístění",
|
||||
"prefs.prefer_free_sketch_points": "Při překrytí bodů upřednostnit pohyblivé body náčrtu",
|
||||
"prefs.preview_choices": "Náhled vašich voleb",
|
||||
"prefs.preview_tint_new_faces": "Podbarvit nový povrch v náhledu",
|
||||
"prefs.preview_tint_new_faces_help": "Náhled prvku, který mění těleso, může vykreslit povrch, který prvek přidává, barvou náhledu. Když je vypnuto, těleso se vykreslí samo o sobě.",
|
||||
"prefs.print_face_highlight": "Zvýraznění tiskové plochy",
|
||||
"prefs.print_face_highlight_tip": "Jak výrazně se tisková plocha tělesa v pohledu obarví a obtáhne. 0 % nezobrazí žádné zvýraznění.",
|
||||
"prefs.profile_active": "Aktivní profil",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Základní barva · sRGB",
|
||||
"render.base_metallic": "Kovový podíl",
|
||||
"render.base_opaque_only": "Tento editor zachovává sémantiku neprůhledných těles. Pro optický průchod světla použijte samostatné ovládací prvky skla.",
|
||||
"render.batch_all_shots": "Všechny záběry ({count})",
|
||||
"render.batch_empty": "Před renderováním všech záběrů nejprve uložte záběr.",
|
||||
"render.batch_limit": "Renderování všech záběrů je omezeno na 32 záběrů.",
|
||||
"render.batch_status": "Probíhá: {running} · Čeká: {waiting}",
|
||||
"render.bloom_enable": "Záře ve výstupu",
|
||||
"render.bloom_help": "Kreativní záře z lineárních jasů se třemi normalizovanými průchody. Poloměr v pixelech výstupu, přičtení před sRGB. Nejde o simulaci čočky, objemu ani zlepšení konvergence. Surové EXR se nemění.",
|
||||
"render.bloom_opaque": "Záře vyžaduje neprůhledný výstup. Průhledné PNG se odmítne, neprovede se skryté sloučení.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Nejprve vyberte styl ve Vzhledech",
|
||||
"render.chromatic_offset": "Chromatický posun hran",
|
||||
"render.clear_stage": "Odebrat uloženou scénografii",
|
||||
"render.compare_divider": "Dělicí čára porovnání",
|
||||
"render.compare_live": "Živě",
|
||||
"render.compare_live_preview": "Živý náhled",
|
||||
"render.compare_render": "Render {number}",
|
||||
"render.compare_side": "Render {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · zachyceno ve revizi {revision}",
|
||||
"render.compare_swap": "Prohodit",
|
||||
"render.copy_request": "Kopírovat JSON",
|
||||
"render.cpu_aa_help": "Prostorové vzorky: více vzorků zlepší hrany a prodlouží vykreslení.",
|
||||
"render.cpu_area_add": "Přidat obdélníkové světlo CPU",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Zobrazuje se poslední hotový obrázek; novější vykreslení jej zatím nenahradilo.",
|
||||
"render.result_sequence": "Kontrolní bod",
|
||||
"render.result_title": "Výsledek vykreslení",
|
||||
"render.results_choose": "Ponechané výsledky",
|
||||
"render.results_label": "Render {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [zastaralý]",
|
||||
"render.results_unnamed": "Obrázek",
|
||||
"render.return_design": "Zpět k návrhu",
|
||||
"render.review_stale": "Tato kontrola je zastaralá. Před použitím ji vyřešte nebo proveďte znovu.",
|
||||
"render.ribbon_aging": "Stárnutí",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "oba počátky jsou na téže komponentě",
|
||||
"thread.automatic_size": "Automatická velikost",
|
||||
"thread.designation": "Označení",
|
||||
"thread.direction": "Směr závitu",
|
||||
"thread.iso_metric": "Metrický ISO",
|
||||
"thread.left_hand": "Levotočivý",
|
||||
"thread.right_hand": "Pravotočivý",
|
||||
"thread.select_size": "Vybrat velikost",
|
||||
"timeline.delete_after_marker_one": "Smazat 1 prvek za značkou historie",
|
||||
"timeline.delete_after_marker_other": "Smazat prvky za značkou historie: {count}",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Spusťte toto v terminálu a pak znovu stiskněte Připojit:",
|
||||
"ui.agent_chat_auth_title": "Přihlásit se k agentovi",
|
||||
"ui.agent_chat_cancel": "Zrušit",
|
||||
"ui.agent_chat_cancel_timeout": "Agent na Zastavit neodpověděl a byl odpojen. Pro pokračování se připojte znovu.",
|
||||
"ui.agent_chat_cannot_start": "Nainstalováno, ale nespouští se: {error}",
|
||||
"ui.agent_chat_changes": "Změny: {n}, jeden krok zpět",
|
||||
"ui.agent_chat_changes_n": "Změn: {n}",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Příkaz agenta",
|
||||
"ui.agent_chat_connect": "Připojit",
|
||||
"ui.agent_chat_copy": "Kopírovat",
|
||||
"ui.agent_chat_copy_message": "Kopírovat zprávu",
|
||||
"ui.agent_chat_ctx_bodies": "Tělesa",
|
||||
"ui.agent_chat_ctx_elements": "Elementy",
|
||||
"ui.agent_chat_ctx_features": "Prvky",
|
||||
"ui.agent_chat_ctx_more": "+{n} dalších",
|
||||
"ui.agent_chat_ctx_other": "Jiné",
|
||||
"ui.agent_chat_custom_heading": "Vlastní model",
|
||||
"ui.agent_chat_custom_hint": "Jakýkoli program, který komunikuje protokolem Agent Client Protocol přes stdin a stdout. Nespouští se přes shell.",
|
||||
"ui.agent_chat_denied": "Zamítnuto",
|
||||
"ui.agent_chat_deny": "Zamítnout",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Vše",
|
||||
"ui.agent_chat_mode_ask": "Ptát se",
|
||||
"ui.agent_chat_mode_edits": "Úpravy",
|
||||
"ui.agent_chat_model_custom": "Použít vlastní: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} nemůže používat nástroje SolidCraftu, takže umí jen chatovat. Vyberte model s podporou nástrojů.",
|
||||
"ui.agent_chat_model_rejected": "Agent nepřijal model {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Hledat modely…",
|
||||
"ui.agent_chat_model_tip": "Model, který agent používá. Vaše volba se pro tohoto agenta zapamatuje.",
|
||||
"ui.agent_chat_models_failed": "Seznam modelů se nepodařilo načíst: {error}",
|
||||
"ui.agent_chat_models_heading": "Dostupné modely",
|
||||
"ui.agent_chat_models_listing": "Načítání seznamu modelů…",
|
||||
"ui.agent_chat_models_none": "Endpoint neuvádí žádné modely",
|
||||
"ui.agent_chat_models_not_a_list": "Odpověď nebyla seznam modelů",
|
||||
"ui.agent_chat_new": "Nový chat",
|
||||
"ui.agent_chat_new_confirm": "Začít nový chat? Aktuální bude vymazán.",
|
||||
"ui.agent_chat_next_picture": "Další obrázek",
|
||||
@@ -4472,21 +4407,14 @@
|
||||
"ui.construction.contact_hint": "Tečna: vyberte kružnici/oblouk nebo otevřený splajn/kuželosečku, pak bod dotyku (volné křivky: koncové body)",
|
||||
"ui.construction.external_hint": "Tečna: vyberte kružnici nebo oblouk, vnější bod, pak požadované řešení",
|
||||
"ui.construction.free_hint": "Konstrukční přímka: klikněte na dva body; pravým tlačítkem začnete znovu",
|
||||
"ui.construction.normal_needs_length": "Nekonečná přímka je vždy konstrukční: zadejte délku, abyste nakreslili běžnou úsečku",
|
||||
"ui.construction.point_hint": "Konstrukční přímka: klikněte na bod, kterým má procházet",
|
||||
"ui.construction.reference_hint": "Konstrukční přímka: vyberte přímku, pak bod; pravým tlačítkem změníte referenci",
|
||||
"ui.construction.ribbon_label": "Pom. přímky",
|
||||
"ui.construction.side_hint": "Konstrukční přímka: klikněte na stranu, kterým směrem přímka vede; pravým tlačítkem začnete znovu",
|
||||
"ui.context_menu": "Místní nabídka",
|
||||
"ui.continuity_crease": "Hrana",
|
||||
"ui.continuity_curvature": "Křivost",
|
||||
"ui.continuity_curvature_change": "Změna křivosti",
|
||||
"ui.continuity_g0": "G0 hrana",
|
||||
"ui.continuity_g1": "G1 tečně",
|
||||
"ui.continuity_g2": "G2 křivost",
|
||||
"ui.continuity_gap": "Mezera",
|
||||
"ui.continuity_position": "Poloha",
|
||||
"ui.continuity_tangent": "Tečnost",
|
||||
"ui.copy": "Kopírovat",
|
||||
"ui.corner": "Roh",
|
||||
"ui.corners": "Rohy",
|
||||
@@ -4747,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Skrýt počátek",
|
||||
"ui.hidePlanes": "Skrýt roviny",
|
||||
"ui.hideProfile": "Skrýt profil",
|
||||
"ui.hide_component_history": "Skrýt její historii",
|
||||
"ui.hide_origin_by_default": "Ve výchozím stavu skrýt počátek",
|
||||
"ui.hide_origin_note": "Nové návrhy začínají se skrytou složkou Počátek. Návrh si ponechá vlastní nastavení počátku, jakmile ho zobrazíte nebo skryjete.",
|
||||
"ui.history_back": "O krok zpět",
|
||||
@@ -4755,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "dvakrát klikněte na hranu",
|
||||
"ui.history_first": "Přejít na začátek",
|
||||
"ui.history_forward": "O krok vpřed",
|
||||
"ui.history_hide_hidden": "Skrýt skryté komponenty",
|
||||
"ui.history_hide_names": "Skrýt názvy",
|
||||
"ui.history_last": "Přejít na konec",
|
||||
"ui.history_marker_tip": "Značka historie: přetažením vrátíte model zpět; nové prvky se vkládají sem",
|
||||
@@ -4765,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Zobrazit tlačítko Přehrát",
|
||||
"ui.history_play_finish_action": "Před přehráním historie dokončete aktuální akci",
|
||||
"ui.history_play_roll_back": "Před přehráním posuňte značku historie zpět",
|
||||
"ui.history_show_hidden": "Zobrazit skryté komponenty",
|
||||
"ui.history_show_names": "Zobrazit názvy",
|
||||
"ui.history_sketch_listed_under": "Uvedeno pod prvkem {feature}",
|
||||
"ui.history_title": "Historie",
|
||||
"ui.history_toggle_names": "Zobrazit nebo skrýt názvy prvků",
|
||||
"ui.history_waits_for_editor": "Zpět, Znovu a Historie počkají, dokud se neotevře editor prvku",
|
||||
"ui.hole": "Otvor",
|
||||
"ui.hole_tap_type": "Typ závitu otvoru",
|
||||
"ui.hole_type": "Typ otvoru",
|
||||
@@ -5051,7 +4975,6 @@
|
||||
"ui.showDimension": "Zobrazit kótu",
|
||||
"ui.showOrigin": "Zobrazit počátek",
|
||||
"ui.showPlanes": "Zobrazit roviny",
|
||||
"ui.show_component_history": "Zobrazit její historii",
|
||||
"ui.show_constraints": "Vazby",
|
||||
"ui.show_construction_geometries": "Konstrukční geometrie",
|
||||
"ui.show_dimensions": "Kóty",
|
||||
@@ -5234,7 +5157,7 @@
|
||||
"update.check_unavailable": "Pro tuto instalaci není kontrola aktualizací dostupná.",
|
||||
"update.checking": "Kontrola…",
|
||||
"update.copy": "Kopírovat",
|
||||
"update.dmg_opened": "Obraz disku je otevřený a ověřený: ukončete SolidCraft, přetáhněte nový SolidCraft do Aplikací místo staré kopie a poté SolidCraft znovu otevřete.",
|
||||
"update.dmg_opened": "Obraz disku je otevřený a ověřený: přetáhněte SolidCraft do Aplikací místo staré kopie a poté SolidCraft restartujte.",
|
||||
"update.download": "Stáhnout",
|
||||
"update.download_open": "Stáhnout a otevřít",
|
||||
"update.downloading": "Stahování a ověřování… {done} z {total} MB",
|
||||
@@ -5278,7 +5201,6 @@
|
||||
"web.open_link": "Otevřít propojený návrh?",
|
||||
"web.open_link_confirm": "Existují neuložené změny. Otevřít propojený návrh na nové kartě?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ vybrat · Enter / Tab doplnit · Esc zavřít",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ výběr · Enter použije tuto hodnotu",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ výběr · Enter použije, co jste napsali · Esc zavře",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter pokračuje na GitHubu."
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Rektangulær",
|
||||
"Rectangular Tube": "Rektangulært hulprofil",
|
||||
"Reference face": "Referenceflade",
|
||||
"Reference plane": "Referenceplan",
|
||||
"Relief Depth": "Aflastningsdybde",
|
||||
"Relief Width": "Aflastningsbredde",
|
||||
"Remove Tools": "Fjern værktøjsemner",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Snit",
|
||||
"Section (optional)": "Tværsnit (valgfrit)",
|
||||
"Section position": "Profilets placering",
|
||||
"Sections": "Tværsnit",
|
||||
"Sections (one sketch each)": "Tværsnit (én skitse til hvert)",
|
||||
"Select Other": "Vælg andet",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Vælg andet: Hold venstre museknap nede uden at flytte markøren på modellen (eller højreklik › Vælg andet…) for at vise alt under markøren. Klik igen samme sted for at vælge næste objekt. Dobbeltklik på den midterste museknap for at gå til Hjem (Indstillinger › Navigation).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Udtræk til fil",
|
||||
"cmd.component_finish_edit": "Afslut redigering",
|
||||
"cmd.component_from_bodies": "Opret komponenter fra emner",
|
||||
"cmd.component_hide_history": "Skjul komponenthistorik",
|
||||
"cmd.component_insert_design": "Indsæt design",
|
||||
"cmd.component_links": "Vis forbindelser",
|
||||
"cmd.component_list": "Vis komponenter",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Omdøb komponent",
|
||||
"cmd.component_replace_with_link": "Erstat med link",
|
||||
"cmd.component_revert_position": "Tilbagefør position",
|
||||
"cmd.component_show_history": "Vis komponenthistorik",
|
||||
"cmd.component_unlock": "Lås indhold op",
|
||||
"cmd.component_update_links": "Opdatér forbindelser",
|
||||
"cmd.config_activate": "Aktivér konfiguration",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Konfigurationsregel",
|
||||
"cmd.config_rules": "Konfigurationsregler",
|
||||
"cmd.config_table": "Vis konfigurationstabel",
|
||||
"cmd.construct.plane.axis_point": "Plan gennem akse og punkt",
|
||||
"cmd.construct_axis_edge": "Akse gennem kant",
|
||||
"cmd.construct_axis_normal_to_face": "Akse vinkelret på flade",
|
||||
"cmd.construct_axis_through_cylinder": "Akse gennem cylinder/kegle/torus",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Vis eksploderet visning",
|
||||
"cmd.expr_evaluate": "Beregn udtryk",
|
||||
"cmd.face_delete": "Slet flade",
|
||||
"cmd.file.export_3mf": "Eksportér 3MF",
|
||||
"cmd.file.export_obj": "Eksportér OBJ",
|
||||
"cmd.file.export_step": "Eksportér STEP",
|
||||
"cmd.file.export_stl": "Eksportér STL",
|
||||
"cmd.file.export_urdf": "Eksportér URDF",
|
||||
"cmd.file_export": "Eksportér",
|
||||
"cmd.file_insert_mesh": "Indsæt mesh",
|
||||
"cmd.file_insert_step": "Indsæt STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Tilgængelighedsanalyse",
|
||||
"cmd.inspect_center_of_mass": "Massecentrum",
|
||||
"cmd.inspect_clearance": "Frigangskontrol",
|
||||
"cmd.inspect_colored_components": "Aktivér komponentfarver",
|
||||
"cmd.inspect_colored_components_disable": "Deaktivér komponentfarver",
|
||||
"cmd.inspect_compare": "Sammenlign emner",
|
||||
"cmd.inspect_compare_picture": "Sammenlign billede",
|
||||
"cmd.inspect_continuity": "Kontinuitetsanalyse",
|
||||
"cmd.inspect_curvature_comb": "Krumningskamanalyse",
|
||||
"cmd.inspect_curvature_map": "Krumningskortanalyse",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Redigér punktsky",
|
||||
"cmd.pointcloud_insert": "Indsæt punktsky",
|
||||
"cmd.pointcloud_list": "Punktskyer",
|
||||
"cmd.render.compare.divider": "Flyt skillelinjen i renderingssammenligningen",
|
||||
"cmd.render.result.discard": "Kassér renderingsresultat",
|
||||
"cmd.render.result.select": "Vælg renderingsresultat",
|
||||
"cmd.render_aging_describe": "Lagdelt overfladeældning",
|
||||
"cmd.render_backends": "Renderingsmotorer",
|
||||
"cmd.render_batch": "Render alle optagelser",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Flyt tilbagerulningsmarkør",
|
||||
"cmd.timeline_stop": "Stop historik",
|
||||
"cmd.timeline_suppress": "Undertryk feature",
|
||||
"cmd.ui.assistant": "Vis/skjul assistent",
|
||||
"cmd.view.render_browser": "Renderingsbrowser",
|
||||
"cmd.view.render_dock": "Flyt renderingsdock",
|
||||
"cmd.view.render_focus": "Fokusvisning af rendering",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Navigationsværktøj",
|
||||
"cmd.view_top": "Visning ovenfra",
|
||||
"cmd.view_visual_style": "Visuel stil",
|
||||
"color.blue": "Blå",
|
||||
"color.copy": "Klik for at kopiere farveværdier",
|
||||
"color.edit": "Klik for at redigere farve",
|
||||
"color.gamma": "Viser farveværdier i 0-255 gammarum",
|
||||
"color.green": "Grøn",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Angiv #rrggbb eller #rrggbbaa; seks cifre bevarer opaciteten",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Viser farveværdier i 0-1 lineært rum",
|
||||
"color.recent": "Seneste farver",
|
||||
"color.recent_help": "Farver, du vælger i et farvefelt, vises her",
|
||||
"color.red": "Rød",
|
||||
"color.saturation": "Mætning (procent)",
|
||||
"color.saturation_value": "Mætning og værdi",
|
||||
"color.selected": "Valgt farve",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Ét trin op",
|
||||
"color.value": "Værdi, lysstyrken (procent)",
|
||||
"config.column_filter_empty": "Ingen matchende kolonner",
|
||||
"config.rule_parameter_columns": "Kun parametre med en kolonne i tabellen vises. Tilføj en kolonne med Tilføj kolonne (+) for at bruge parameteren her.",
|
||||
"config.rule_suppression_columns": "Kun features med en Undertryk-kolonne i tabellen vises. Tilføj en kolonne med Tilføj kolonne (+) for at styre undertrykkelsen her.",
|
||||
"contact.components_count": "{count} komponenter",
|
||||
"contact.enabled_label": "aktiveret",
|
||||
"coupling.gear_1": "Tandhjul 1",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Vælg emner eller features: En skitse er ingen af delene. Gentag dens kurver med skitsens eget Mønster eller Spejling, eller ekstruder den først.",
|
||||
"dialog.select": "↖ Vælg",
|
||||
"dialog.selected_count": "↖ Valgte: {count}",
|
||||
"dialog.selection_constraint": "Binding {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · kurve {id}",
|
||||
"dialog.selection_guidance": "Vælg et input for at ændre det. Klik på valgte objekter for at fjerne dem; Ctrl/Cmd-klik skifter også deres valgstatus. Gennemgå forhåndsvisningen, og tryk derefter på OK.",
|
||||
"dialog.selection_midpoint": "Midtpunkt · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step} af {total}: Vælg {input}",
|
||||
"dialog.step": "Trin",
|
||||
"document.changed_on_disk": "{name} er ændret på disken",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Opretter en konstruktionsakse gennem to punkter, hjørnepunkter eller skitsepunkter. Brug den til en akse mellem to kendte positioner.",
|
||||
"help.construct.plane.along_path": "Opretter et konstruktionsplan vinkelret på en kant eller kurve i en given position langs den. Brug det til at skitsere en profil til et sweep eller et snit af en bane.",
|
||||
"help.construct.plane.at_angle": "Opretter et konstruktionsplan, der er drejet med en vinkel fra et plan eller en flade omkring en akse eller kant. Brug det til skrå skitser, f.eks. et vinklet beslag.",
|
||||
"help.construct.plane.axis_point": "Opretter et konstruktionsplan, der indeholder en akse og et punkt uden for aksen. Vælg en akse fra nulpunktet eller en konstruktionsakse, en ret kant eller skitselinje og derefter et hjørnepunkt, skitsepunkt eller konstruktionspunkt. Et punkt på aksen kan ikke definere et entydigt plan.",
|
||||
"help.construct.plane.midplane": "Opretter et konstruktionsplan midt mellem to planer eller plane flader eller halverende dem, hvor de mødes i en vinkel. Brug det som symmetriplan til spejling.",
|
||||
"help.construct.plane.offset": "Opretter et konstruktionsplan parallelt med et plan eller en plan flade i en afstand eller gennem et punkt. Brug det til at skitsere i en bestemt højde over en flade.",
|
||||
"help.construct.plane.perpendicular": "Opretter et konstruktionsplan, der indeholder en linje eller kant og står vinkelret på et plan eller en flade. Brug det til at skitsere opretstående på en model langs en kant.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Kontrollér ledtypen (f.eks. Stiv), fastgørelse, bevægelsesgrænser for led og parameterstyrede værdier.",
|
||||
"joint_move.blocked_title": "Kan ikke flytte dette emne",
|
||||
"joint_move.coarse": "Rotationstrin nær aksen (°)",
|
||||
"joint_move.conflict": "Et led på dette emne er i konflikt med de andre: {joint}.",
|
||||
"joint_move.conflict_hint": "Redigér, undertryk eller slet det modstridende led, så de andre led kan placere emnet.",
|
||||
"joint_move.distance": "Afstand fra start",
|
||||
"joint_move.done": "Færdig",
|
||||
"joint_move.fine": "Rotationstrin langt fra aksen (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "Spor {number}",
|
||||
"nav.alias.help": "Shift+Alt+træk med venstre museknap roterer visningen, Shift+Alt+midterste panorerer, Shift+Alt+højre zoomer; træk med midterste museknap panorerer også, og træk med højre roterer visningen",
|
||||
"nav.blender.help": "Træk med midterste museknap roterer visningen, Shift+midterste panorerer, Ctrl+midterste zoomer; træk med højre museknap påvirker ikke kameraet",
|
||||
"nav.creo.help": "Træk med midterste museknap roterer visningen, Shift+midterste panorerer, Ctrl+træk lodret med midterste zoomer (ned for at zoome ind); rulning med hjulet zoomer",
|
||||
"nav.creo.label": "Creo",
|
||||
"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.free_orbit.snap_help": "Musen og 3D-musen roterer gennem begge poler og bevarer rulning. Hold Shift nede under et rotationstræk for at snappe visningsretningen og rulningen.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Rundhovedet skrue med indvendig sekskant",
|
||||
"parts.family.dowel_pin": "Cylindrisk stift (dyvel)",
|
||||
"parts.family.hex_bolt": "Sekskanthovedet bolt / skrue",
|
||||
"parts.family.hex_cap_screw_inch": "Sekskantskrue (tommer)",
|
||||
"parts.family.hex_nut": "Sekskantmøtrik",
|
||||
"parts.family.hex_nut_inch": "Sekskantmøtrik (tommer)",
|
||||
"parts.family.set_screw": "Stilleskrue med indvendig sekskant, flad spids",
|
||||
"parts.family.socket_head_cap_screw": "Cylinderskrue med indvendig sekskant",
|
||||
"parts.family.washer": "Almindelig skive",
|
||||
"parts.family.washer_sae": "Almindelig skive, SAE (smal)",
|
||||
"parts.family.washer_uss": "Almindelig skive, USS (bred)",
|
||||
"parts.holes": "Huller (valgfrit)",
|
||||
"parts.placement_hint": "vælg et eller flere huller for at sætte en del i hvert, eller tryk OK for at placere den ved nulpunktet",
|
||||
"parts.system": "Enhedssystem",
|
||||
"parts.system.inch": "Tommer",
|
||||
"parts.system.metric": "Metrisk",
|
||||
"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",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Træk med venstre museknap",
|
||||
"prefs.button_middle": "Træk med midterste museknap",
|
||||
"prefs.button_right": "Træk med højre museknap",
|
||||
"prefs.carousel_next": "Næste",
|
||||
"prefs.carousel_previous": "Forrige",
|
||||
"prefs.choose_font": "Vælg skrifttypefil…",
|
||||
"prefs.click": "Klik",
|
||||
"prefs.contact_shading": "Blød kontaktskygning",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Krumme overflader",
|
||||
"prefs.curves": "Krumme overflader",
|
||||
"prefs.cut_preview_tint": "Vis fjernet materiale i rødt",
|
||||
"prefs.cut_preview_tint_help": "En forhåndsvisning udfylder det materiale, som en feature fjerner, og den form, den erstatter, med et rødt, gennemsigtigt spøgelse. Når indstillingen er slået fra, er udfyldningerne helt gennemsigtige, og kun deres omrids tegnes.",
|
||||
"prefs.dark_parts_shading": "Bevar formen på mørke dele",
|
||||
"prefs.dark_parts_shading_help": "Skygger visningen i lineært lys med en blød himmelfyld, så mørke flader beholder et farveforløb i stedet for at blive sorte",
|
||||
"prefs.dead_zone_note": "Det skraverede bånd er dødzonen: Bevægelser inden for det ignoreres.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Sprog i brugergrænsefladen",
|
||||
"prefs.interface_note": "SolidCrafts udseende og placeringen af dets dele.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Vend de buede rulningspile om",
|
||||
"prefs.invert_curved_roll_arrows_note": "De to buede pile i kubens hjørne ruller visningen i skærmplanet. Slået til: Pilen med uret ruller mod uret, og pilen mod uret ruller med uret. Slået fra: Hver pil ruller visningen den vej, den krummer.",
|
||||
"prefs.invert_navigator_arrows": "Vend visningskubens rotationspile om",
|
||||
"prefs.invert_navigator_arrows_note": "Pilene rundt om kuben roterer kameraet i den retning, de peger, som i Fusion og Inventor: Pilen til venstre peger mod højre og svinger kameraet til højre. Slået fra: En pil drejer kuben mod den side, den sidder på, så venstre pil svinger kameraet til venstre. De buede rulningspile påvirkes ikke.",
|
||||
"prefs.keep_work_on_tab_switch": "Behold dialoger og værktøjer åbne, når du skifter design",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Placering",
|
||||
"prefs.prefer_free_sketch_points": "Foretræk flytbare skitsepunkter, når punkter overlapper",
|
||||
"prefs.preview_choices": "Forhåndsvisning af dine valg",
|
||||
"prefs.preview_tint_new_faces": "Farv ny overflade i en forhåndsvisning",
|
||||
"prefs.preview_tint_new_faces_help": "En forhåndsvisning af en feature, der ændrer et emne, kan tegne den overflade, den tilføjer, i forhåndsvisningens farve. Når indstillingen er slået fra, tegnes emnet, som det er.",
|
||||
"prefs.print_face_highlight": "Fremhævning af printflade",
|
||||
"prefs.print_face_highlight_tip": "Hvor kraftigt et legemes printflade farves og omrids i visningen. 0 % viser ingen fremhævning.",
|
||||
"prefs.profile_active": "Aktiv profil",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Basisfarve · sRGB",
|
||||
"render.base_metallic": "Metallisk andel",
|
||||
"render.base_opaque_only": "Denne editor bevarer emnets uigennemsigtighed. Brug de eksplicitte glasindstillinger til optisk transmission.",
|
||||
"render.batch_all_shots": "Alle optagelser ({count})",
|
||||
"render.batch_empty": "Gem en optagelse, før alle optagelser renderes.",
|
||||
"render.batch_limit": "Rendering af alle optagelser er begrænset til 32 optagelser.",
|
||||
"render.batch_status": "{running} kører · {waiting} venter",
|
||||
"render.bloom_enable": "Vis bloom",
|
||||
"render.bloom_help": "En kreativ glød, der kun påvirker output, ud fra positive lineære højlys og spredt med tre normaliserede box-pas. Radius er i outputpixels. Lægges til i lineært visningslys før sRGB; ikke en linseeffekt, en volumeneffekt eller en forbedring af konvergens. Den rå EXR er uændret.",
|
||||
"render.bloom_opaque": "Bloom kræver uigennemsigtigt output. Gennemsigtig PNG afvises og gøres ikke stiltiende uigennemsigtig.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Vælg først et udseende under Udseender",
|
||||
"render.chromatic_offset": "Kromatisk kantforskydning",
|
||||
"render.clear_stage": "Fjern gemt kulisse",
|
||||
"render.compare_divider": "Sammenligningens skillelinje",
|
||||
"render.compare_live": "Live",
|
||||
"render.compare_live_preview": "Live-forhåndsvisning",
|
||||
"render.compare_render": "Rendering {number}",
|
||||
"render.compare_side": "Rendering {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · registreret ved revision {revision}",
|
||||
"render.compare_swap": "Ombyt",
|
||||
"render.copy_request": "Kopiér JSON",
|
||||
"render.cpu_aa_help": "Rumlige samples: flere samples forbedrer kanter og øger renderingstiden.",
|
||||
"render.cpu_area_add": "Tilføj rektangulært CPU-lys",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Viser det seneste færdige billede; en nyere rendering har ikke erstattet det.",
|
||||
"render.result_sequence": "Kontrolpunkt",
|
||||
"render.result_title": "Renderingsresultat",
|
||||
"render.results_choose": "Bevarede resultater",
|
||||
"render.results_label": "Rendering {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [forældet]",
|
||||
"render.results_unnamed": "Billede",
|
||||
"render.return_design": "Tilbage til Design",
|
||||
"render.review_stale": "Denne gennemgang er forældet. Afklar den, eller kontrollér igen, før du anvender den.",
|
||||
"render.ribbon_aging": "Ældning",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "de to nulpunkter er på samme komponent",
|
||||
"thread.automatic_size": "Automatisk størrelse",
|
||||
"thread.designation": "Betegnelse",
|
||||
"thread.direction": "Gevindretning",
|
||||
"thread.iso_metric": "ISO-metrisk",
|
||||
"thread.left_hand": "Venstregænget",
|
||||
"thread.right_hand": "Højregænget",
|
||||
"thread.select_size": "Vælg størrelse",
|
||||
"timeline.delete_after_marker_one": "Slet 1 feature efter historikmarkøren",
|
||||
"timeline.delete_after_marker_other": "Slet {count} features efter historikmarkøren",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Kør dette i en terminal, og tryk derefter på Forbind igen:",
|
||||
"ui.agent_chat_auth_title": "Log ind hos agenten",
|
||||
"ui.agent_chat_cancel": "Annullér",
|
||||
"ui.agent_chat_cancel_timeout": "Agenten svarede ikke på Stop og blev afbrudt. Forbind igen for at fortsætte.",
|
||||
"ui.agent_chat_cannot_start": "Installeret, men den starter ikke: {error}",
|
||||
"ui.agent_chat_changes": "Ændringer: {n}, ét fortrydelsestrin",
|
||||
"ui.agent_chat_changes_n": "{n} ændringer",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Agentkommando",
|
||||
"ui.agent_chat_connect": "Forbind",
|
||||
"ui.agent_chat_copy": "Kopiér",
|
||||
"ui.agent_chat_copy_message": "Kopiér besked",
|
||||
"ui.agent_chat_ctx_bodies": "Emner",
|
||||
"ui.agent_chat_ctx_elements": "Elementer",
|
||||
"ui.agent_chat_ctx_features": "Features",
|
||||
"ui.agent_chat_ctx_more": "+{n} flere",
|
||||
"ui.agent_chat_ctx_other": "Andet",
|
||||
"ui.agent_chat_custom_heading": "Brugerdefineret model",
|
||||
"ui.agent_chat_custom_hint": "Ethvert program, der bruger Agent Client Protocol via stdin og stdout. Det køres uden en kommandoskal.",
|
||||
"ui.agent_chat_denied": "Afvist",
|
||||
"ui.agent_chat_deny": "Afvis",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Alt",
|
||||
"ui.agent_chat_mode_ask": "Spørg",
|
||||
"ui.agent_chat_mode_edits": "Ændringer",
|
||||
"ui.agent_chat_model_custom": "Brug brugerdefineret model: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} kan ikke bruge SolidCrafts værktøjer og kan derfor kun chatte. Vælg en model med understøttelse af værktøjer.",
|
||||
"ui.agent_chat_model_rejected": "Agenten accepterede ikke {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Søg efter modeller…",
|
||||
"ui.agent_chat_model_tip": "Modellen, som agenten bruger. Dit valg huskes for denne agent.",
|
||||
"ui.agent_chat_models_failed": "Kunne ikke hente listen over modeller: {error}",
|
||||
"ui.agent_chat_models_heading": "Tilgængelige modeller",
|
||||
"ui.agent_chat_models_listing": "Henter modeller…",
|
||||
"ui.agent_chat_models_none": "Endepunktet viser ingen modeller",
|
||||
"ui.agent_chat_models_not_a_list": "Svaret var ikke en liste over modeller",
|
||||
"ui.agent_chat_new": "Ny chat",
|
||||
"ui.agent_chat_new_confirm": "Start en ny chat? Den aktuelle chat ryddes.",
|
||||
"ui.agent_chat_next_picture": "Næste billede",
|
||||
@@ -4472,21 +4407,14 @@
|
||||
"ui.construction.contact_hint": "Tangent: Vælg en cirkel/bue eller en åben spline/keglesnit, og vælg derefter røringspunktet (frie kurver: endepunkter)",
|
||||
"ui.construction.external_hint": "Tangent: Vælg en cirkel eller bue og et eksternt punkt, og vælg derefter den ønskede løsning",
|
||||
"ui.construction.free_hint": "Hjælpelinje: Klik på to punkter; højreklik for at starte forfra",
|
||||
"ui.construction.normal_needs_length": "En uendelig linje er altid en hjælpelinje: Skriv en længde for at tegne en normal linje",
|
||||
"ui.construction.point_hint": "Hjælpelinje: Klik på det punkt, linjen skal gå igennem",
|
||||
"ui.construction.reference_hint": "Hjælpelinje: Vælg en ret linje og derefter et punkt; højreklik for at skifte reference",
|
||||
"ui.construction.ribbon_label": "Hjælpelinjer",
|
||||
"ui.construction.side_hint": "Hjælpelinje: Klik på den side, linjen går mod; højreklik for at starte forfra",
|
||||
"ui.context_menu": "Genvejsmenu",
|
||||
"ui.continuity_crease": "Knæk",
|
||||
"ui.continuity_curvature": "Krumning",
|
||||
"ui.continuity_curvature_change": "Ændring i krumning",
|
||||
"ui.continuity_g0": "G0-knæk",
|
||||
"ui.continuity_g1": "G1-tangentkontinuitet",
|
||||
"ui.continuity_g2": "G2-krumningskontinuitet",
|
||||
"ui.continuity_gap": "Åbning mellem flader",
|
||||
"ui.continuity_position": "Positionskontinuitet",
|
||||
"ui.continuity_tangent": "Tangentkontinuitet",
|
||||
"ui.copy": "Kopiér",
|
||||
"ui.corner": "Hjørne",
|
||||
"ui.corners": "Hjørner",
|
||||
@@ -4747,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Skjul nulpunkt",
|
||||
"ui.hidePlanes": "Skjul planer",
|
||||
"ui.hideProfile": "Skjul profil",
|
||||
"ui.hide_component_history": "Skjul dens historik",
|
||||
"ui.hide_origin_by_default": "Skjul nulpunktet som standard",
|
||||
"ui.hide_origin_note": "Nye designs starter med mappen Nulpunkt skjult. Et design beholder sin egen indstilling for nulpunktet, når du har vist eller skjult det.",
|
||||
"ui.history_back": "Gå ét trin tilbage",
|
||||
@@ -4755,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "dobbeltklik på kanten",
|
||||
"ui.history_first": "Gå til begyndelsen",
|
||||
"ui.history_forward": "Gå ét trin frem",
|
||||
"ui.history_hide_hidden": "Skjul skjulte komponenter",
|
||||
"ui.history_hide_names": "Skjul navne",
|
||||
"ui.history_last": "Gå til slutningen",
|
||||
"ui.history_marker_tip": "Historikmarkør: Træk for at rulle modellen tilbage; nye features indsættes her",
|
||||
@@ -4765,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Vis afspilningsknappen",
|
||||
"ui.history_play_finish_action": "Afslut den aktuelle handling, før historikken afspilles",
|
||||
"ui.history_play_roll_back": "Flyt historikmarkøren tilbage, før du afspiller",
|
||||
"ui.history_show_hidden": "Vis skjulte komponenter",
|
||||
"ui.history_show_names": "Vis navne",
|
||||
"ui.history_sketch_listed_under": "Vises under {feature}",
|
||||
"ui.history_title": "Historik",
|
||||
"ui.history_toggle_names": "Vis eller skjul featurenavne",
|
||||
"ui.history_waits_for_editor": "Fortryd, Gentag og Historik venter, indtil featureeditoren er åbnet",
|
||||
"ui.hole": "Hul",
|
||||
"ui.hole_tap_type": "Hullets gevindtype",
|
||||
"ui.hole_type": "Hultype",
|
||||
@@ -5051,7 +4975,6 @@
|
||||
"ui.showDimension": "Vis mål",
|
||||
"ui.showOrigin": "Vis nulpunkt",
|
||||
"ui.showPlanes": "Vis planer",
|
||||
"ui.show_component_history": "Vis dens historik",
|
||||
"ui.show_constraints": "Bindinger",
|
||||
"ui.show_construction_geometries": "Konstruktionsgeometri",
|
||||
"ui.show_dimensions": "Mål",
|
||||
@@ -5234,7 +5157,7 @@
|
||||
"update.check_unavailable": "Søgning efter opdateringer er ikke tilgængelig for denne installation.",
|
||||
"update.checking": "Søger…",
|
||||
"update.copy": "Kopiér",
|
||||
"update.dmg_opened": "Diskbilledet er åbnet og verificeret: Afslut SolidCraft, træk det nye SolidCraft til Programmer, erstat den gamle kopi, og åbn derefter SolidCraft igen.",
|
||||
"update.dmg_opened": "Diskbilledet er åbnet og verificeret: Træk SolidCraft til Programmer, erstat den gamle kopi, og genstart SolidCraft.",
|
||||
"update.download": "Download",
|
||||
"update.download_open": "Download og åbn",
|
||||
"update.downloading": "Downloader og verificerer… {done} af {total} MB",
|
||||
@@ -5278,7 +5201,6 @@
|
||||
"web.open_link": "Åbn linket design?",
|
||||
"web.open_link_confirm": "Der er ændringer, som ikke er gemt. Skal det linkede design åbnes i en ny fane?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ vælg · Enter / Tab udfyld · Esc luk",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ vælg · Enter anvender denne værdi",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ vælg · Enter anvender det indtastede · Esc lukker",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter fortsætter på GitHub."
|
||||
}
|
||||
|
||||
@@ -379,7 +379,7 @@
|
||||
"Move Type": "Verschiebungstyp",
|
||||
"Move down": "Nach unten",
|
||||
"Move earlier": "Nach vorn verschieben",
|
||||
"Move holes by clicking or dragging": "Durch Klicken oder Ziehen verschieben",
|
||||
"Move holes by clicking or dragging": "Bohrungen durch Anklicken oder Ziehen verschieben",
|
||||
"Move later": "Nach hinten verschieben",
|
||||
"Move up": "Nach oben",
|
||||
"Name": "Name",
|
||||
@@ -496,7 +496,6 @@
|
||||
"Render Stage": "Renderphase",
|
||||
"Reopen designs": "Designs erneut öffnen",
|
||||
"Reopen the designs that were open when SolidCraft was last closed": "Die zuletzt geöffneten Entwürfe erneut öffnen",
|
||||
"Repair Part": "Teil reparieren",
|
||||
"Replace existing library file": "Vorhandene Bibliotheksdatei ersetzen",
|
||||
"Report a bug — review a screenshot and open a GitHub draft": "Fehler melden — Screenshot prüfen und einen GitHub-Entwurf öffnen",
|
||||
"Report copied. Keep this text to submit on GitHub when you are online.": "Bericht kopiert. Bewahren Sie diesen Text auf, um ihn auf GitHub einzureichen, sobald Sie online sind.",
|
||||
@@ -585,7 +584,7 @@
|
||||
"Show ghost": "Geistermodell anzeigen",
|
||||
"Show in folder": "Im Ordner anzeigen",
|
||||
"Show the pointer an AI agent moves (#35)": "Den Zeiger anzeigen, den ein KI-Agent bewegt (#35)",
|
||||
"Show tool at cursor": "Werkzeug am Mauszeiger",
|
||||
"Show tool at cursor": "Werkzeug am Cursor anzeigen",
|
||||
"Signed axes": "Vorzeichenbehaftete Achsen",
|
||||
"Size": "Größe",
|
||||
"Sketch constraints": "Skizzenbedingungen",
|
||||
@@ -596,7 +595,7 @@
|
||||
"Sketch points": "Skizzenpunkte",
|
||||
"Slider": "Schubgelenk",
|
||||
"Slot width": "Langlochbreite",
|
||||
"Snap to face and edge centres": "An Mittelpunkten einrasten",
|
||||
"Snap to face and edge centres": "An Flächen- und Kantenmittelpunkten einrasten",
|
||||
"SolidCraft cannot submit reports directly. Submit on GitHub; no token is needed in the app.": "SolidCraft kann Berichte nicht direkt einreichen. Reichen Sie Ihren Bericht auf GitHub ein. In der App wird dafür kein Token benötigt.",
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "SolidCraft wurde beendet, bevor diese Designs gespeichert wurden. Ihr letzter Autosave kann geöffnet werden.",
|
||||
"Sort A to Z": "Von A bis Z sortieren",
|
||||
@@ -739,36 +738,36 @@
|
||||
"a picked edge's body is gone": "der Körper einer gewählten Kante existiert nicht mehr",
|
||||
"add a key for a joint first": "zuerst einen Schlüssel für ein Gelenk hinzufügen",
|
||||
"ai.connected_toast": "SolidCraft ist jetzt in {clients} verfügbar. Starten Sie diese neu, um es zu nutzen (Einstellungen > Assistent).",
|
||||
"ai.prefs.allow_control": "KI-Agenten verbinden lassen",
|
||||
"ai.prefs.allow_control_note": "Ein lokaler Kanal (MCP). Nichts verlässt diesen Computer.\n\nÖffnet einen Steuerkanal nur auf diesem Computer (127.0.0.1, Port 27878 oder der nächste freie). Ein verbindendes Programm muss zusätzlich ein geheimes Token vorweisen, das nur Ihr Benutzerkonto lesen kann und bei jedem Start neu erzeugt wird. Wirkt ab dem nächsten Start von SolidCraft. Aus: Es wartet nichts auf Verbindungen, außer Sie starten SolidCraft mit --control.",
|
||||
"ai.prefs.availability": "Verfügbarkeit",
|
||||
"ai.prefs.background_commands": "Langsame Befehle im Hintergrund ausführen",
|
||||
"ai.prefs.connect_apps": "SolidCraft zu KI-Apps hinzufügen",
|
||||
"ai.prefs.connect_apps_note": "Aus entfernt die Einträge wieder.\n\nTrägt SolidCraft beim Start als MCP-Server in die auf diesem Computer gefundenen KI-Apps ein (OpenCode, Claude Desktop, Claude Code, Cursor, Codex, Gemini CLI, Copilot CLI, Windsurf, Zed, VS Code), damit ein Agent es ohne Einrichtung nutzen kann. Nur der Eintrag „solidcraft“ wird hinzugefügt oder sein Pfad aktualisiert; ein von Ihnen geänderter Eintrag bleibt unberührt, und die Datei wird vorher gesichert. Aus: Die hinzugefügten Einträge werden entfernt. Hilfe > KI-Assistenten verbinden… erledigt das von Hand.",
|
||||
"ai.prefs.connect_at_start": "Letzten Agenten beim Start verbinden",
|
||||
"ai.prefs.allow_control": "Verbindungen von KI-Agenten zulassen (Steuerkanal / MCP)",
|
||||
"ai.prefs.allow_control_note": "Öffnet einen Steuerkanal nur auf diesem Computer (127.0.0.1, Port 27878 oder der nächste freie). Ein verbindendes Programm muss zusätzlich ein geheimes Token vorweisen, das nur Ihr Benutzerkonto lesen kann und bei jedem Start neu erzeugt wird. Wirkt ab dem nächsten Start von SolidCraft. Aus: Es wartet nichts auf Verbindungen, außer Sie starten SolidCraft mit --control.",
|
||||
"ai.prefs.availability": "Assistenten-Steuerung",
|
||||
"ai.prefs.background_commands": "Agentenbefehle im Hintergrund ausführen",
|
||||
"ai.prefs.connect_apps": "SolidCraft für KI-Apps auf diesem Computer verfügbar machen",
|
||||
"ai.prefs.connect_apps_note": "Trägt SolidCraft beim Start als MCP-Server in die auf diesem Computer gefundenen KI-Apps ein (OpenCode, Claude Desktop, Claude Code, Cursor, Codex, Gemini CLI, Copilot CLI, Windsurf, Zed, VS Code), damit ein Agent es ohne Einrichtung nutzen kann. Nur der Eintrag „solidcraft“ wird hinzugefügt oder sein Pfad aktualisiert; ein von Ihnen geänderter Eintrag bleibt unberührt, und die Datei wird vorher gesichert. Aus: Die hinzugefügten Einträge werden entfernt. Hilfe > KI-Assistenten verbinden… erledigt das von Hand.",
|
||||
"ai.prefs.connect_at_start": "Zuletzt verwendeten Agenten beim Start von SolidCraft verbinden",
|
||||
"ai.prefs.connect_at_start_note": "Verbindet beim Start von SolidCraft automatisch den zuletzt verwendeten Agenten (oder den Standardagenten) im Hintergrund, damit er für den Chat bereitsteht. Diese Funktion ist deaktiviert, wenn SolidCraft mit --control gestartet wird. Kann keine Verbindung hergestellt werden, zeigt der Assistent „Verbindung fehlgeschlagen“ und eine Schaltfläche zum erneuten Verbindungsversuch an.",
|
||||
"ai.prefs.follow_camera": "Der Kamera folgen",
|
||||
"ai.prefs.follow_camera": "Kamera zu Skizzen drehen",
|
||||
"ai.prefs.follow_camera_off": "Die automatische Kameraführung ist aus.",
|
||||
"ai.prefs.follow_camera_on": "Die Kamera dreht sich zu Skizzen, die der Agent erstellt.",
|
||||
"ai.prefs.intro": "KI-Agenten in SolidCraft.",
|
||||
"ai.prefs.make_default": "Als Standard festlegen",
|
||||
"ai.prefs.intro": "Agenten, Berechtigungen und wie ihre Arbeit angezeigt wird.",
|
||||
"ai.prefs.make_default": "Standardmäßig verwenden",
|
||||
"ai.prefs.no_agents": "Noch keine Agenten. Fügen Sie einen Agenten hinzu oder aktualisieren Sie, um installierte Agenten zu finden.",
|
||||
"ai.prefs.no_saved_approvals": "Keine gespeicherten Freigaben.",
|
||||
"ai.prefs.open_at_start": "Chat beim Start öffnen",
|
||||
"ai.prefs.open_at_start": "Assistentenbereich beim Start von SolidCraft öffnen",
|
||||
"ai.prefs.open_at_start_note": "Öffnet beim Start von SolidCraft automatisch den Assistentenbereich oder dessen separates Fenster. Diese Funktion ist deaktiviert, wenn SolidCraft mit --control gestartet wird.",
|
||||
"ai.prefs.override_all_warning": "Dieser Agent darf das Design ohne Rückfrage speichern, überschreiben, exportieren oder ersetzen.",
|
||||
"ai.prefs.permission_all": "Alles ausführen",
|
||||
"ai.prefs.permission_all_summary": "Fragt nie nach",
|
||||
"ai.prefs.permission_ask": "Zuerst fragen",
|
||||
"ai.prefs.permission_ask_summary": "Fragt bei jeder Änderung",
|
||||
"ai.prefs.permission_edits": "Änderungen vornehmen",
|
||||
"ai.prefs.permission_edits_summary": "Ändert frei, fragt beim Rest",
|
||||
"ai.prefs.permission_all": "Alles erlauben",
|
||||
"ai.prefs.permission_all_summary": "Verfügbare SolidCraft-Werkzeuge werden ohne Rückfrage ausgeführt.",
|
||||
"ai.prefs.permission_ask": "Vor Änderungen fragen",
|
||||
"ai.prefs.permission_ask_summary": "Jede Designänderung und Dateiaktion bestätigen.",
|
||||
"ai.prefs.permission_edits": "Modelländerungen erlauben",
|
||||
"ai.prefs.permission_edits_summary": "Modelländerungen werden ohne Rückfrage ausgeführt; Datei- und destruktive Aktionen fragen weiterhin nach.",
|
||||
"ai.prefs.permission_scope": "Gilt nur für SolidCraft-Werkzeuge. Shell und Dateiänderungen des Agenten unterliegen dessen eigenen Berechtigungen.",
|
||||
"ai.prefs.pointer_preview_hidden": "Zeiger ausgeblendet",
|
||||
"ai.prefs.pointer_preview_speed": "Agentenzeiger · {speed}",
|
||||
"ai.prefs.pointer_speed": "Geschwindigkeit",
|
||||
"ai.prefs.pointer_speed": "Bewegungsgeschwindigkeit",
|
||||
"ai.prefs.privacy_note": "Die Unterhaltung geht nur an den von Ihnen gewählten Agenten oder API-Endpunkt. API-Schlüssel werden im Schlüsselbund des Systems gespeichert.",
|
||||
"ai.prefs.show_pointer": "Zeiger des Agenten anzeigen",
|
||||
"ai.prefs.show_pointer": "Agentenzeiger anzeigen",
|
||||
"ai.prefs.turn_limit_hint": "Höchstzahl designändernder Werkzeugaufrufe, die der Assistent für eine Nachricht ausführen darf.",
|
||||
"angle, height": "Winkel, Höhe",
|
||||
"appearance.dielectric_ior": "Brechungsindex des Dielektrikums",
|
||||
@@ -791,7 +790,6 @@
|
||||
"browser.edit_canvas_title": "Leinwand bearbeiten: {name}",
|
||||
"browser.edit_point_cloud_title": "Punktwolke bearbeiten: {name}",
|
||||
"browser.move_title": "Verschieben: {name}",
|
||||
"browser.needs_repair": "{name}: muss repariert werden",
|
||||
"browser.parameters": "Parameter ({count})",
|
||||
"browser.precision": "Genauigkeit: {precision}",
|
||||
"browser.sketch_attachment_lost": "{name} (Bezug verloren)",
|
||||
@@ -836,7 +834,6 @@
|
||||
"cmd.assembly_mass_properties": "Masseeigenschaften",
|
||||
"cmd.body.print_face": "Auflagefläche",
|
||||
"cmd.body_rename": "Körper umbenennen",
|
||||
"cmd.body_repair": "Teil reparieren",
|
||||
"cmd.browser_collapse_all": "Alles einklappen",
|
||||
"cmd.browser_expand_all": "Alles ausklappen",
|
||||
"cmd.browser_group": "Neue Gruppe",
|
||||
@@ -919,8 +916,6 @@
|
||||
"cmd.diagnostics_crash_reports": "Absturzberichte",
|
||||
"cmd.diagnostics_crash_reports_clear": "Absturzberichte löschen",
|
||||
"cmd.diagnostics_crash_test": "Absturztest",
|
||||
"cmd.diagnostics_profile": "Profil",
|
||||
"cmd.diagnostics_validation": "Prüfung",
|
||||
"cmd.doc_recover": "Konstruktion wiederherstellen",
|
||||
"cmd.doc_recovery_discard": "Wiederhergestellte Konstruktion verwerfen",
|
||||
"cmd.doc_recovery_list": "Wiederherstellbare Konstruktionen",
|
||||
@@ -960,6 +955,8 @@
|
||||
"cmd.inspect_accessibility": "Zugänglichkeitsanalyse",
|
||||
"cmd.inspect_center_of_mass": "Massenschwerpunkt",
|
||||
"cmd.inspect_clearance": "Freiraum",
|
||||
"cmd.inspect_colored_components": "Komponentenfarben einschalten",
|
||||
"cmd.inspect_colored_components_disable": "Komponentenfarben ausschalten",
|
||||
"cmd.inspect_compare": "Körper vergleichen",
|
||||
"cmd.inspect_continuity": "Kontinuitätsprüfung",
|
||||
"cmd.inspect_curvature_comb": "Krümmungskamm-Analyse",
|
||||
@@ -990,11 +987,6 @@
|
||||
"cmd.joint_screw": "Schraube",
|
||||
"cmd.joint_solve": "Gelenke lösen",
|
||||
"cmd.joint_sweep": "Gelenk bis zum Kontakt durchfahren",
|
||||
"cmd.layout.apply": "Layout anwenden",
|
||||
"cmd.layout.get": "Layout prüfen",
|
||||
"cmd.layout.reset": "Layout zurücksetzen",
|
||||
"cmd.layout.save": "Layout speichern",
|
||||
"cmd.layout.workspace.select": "Layout-Arbeitsbereich auswählen",
|
||||
"cmd.library_apply": "Lokalen Bibliothekseintrag anwenden",
|
||||
"cmd.library_export": "Designbibliothek exportieren",
|
||||
"cmd.library_inspect": "Lokale Bibliothek prüfen",
|
||||
@@ -1028,21 +1020,6 @@
|
||||
"cmd.occurrence_move": "Instanz verschieben",
|
||||
"cmd.occurrence_properties": "Physikalische Eigenschaften der Instanz",
|
||||
"cmd.occurrence_reparent": "In andere Komponente verschieben",
|
||||
"cmd.panel.activate": "Panel-Registerkarte aktivieren",
|
||||
"cmd.panel.close": "Panel schließen",
|
||||
"cmd.panel.collapse": "Panel-Bereich einklappen",
|
||||
"cmd.panel.dismiss": "Panel-Vorschau schließen",
|
||||
"cmd.panel.float": "Panel frei platzieren",
|
||||
"cmd.panel.focus": "Panel fokussieren",
|
||||
"cmd.panel.list": "Panels auflisten",
|
||||
"cmd.panel.move": "Panel verschieben",
|
||||
"cmd.panel.native_close": "Nativen Panel-Bereich wieder andocken",
|
||||
"cmd.panel.open": "Panel öffnen",
|
||||
"cmd.panel.peek": "Eingeklapptes Panel anzeigen",
|
||||
"cmd.panel.popout": "Panel in eigenes Fenster verschieben",
|
||||
"cmd.panel.reset_pair": "Panel-Trennlinie zurücksetzen",
|
||||
"cmd.panel.resize": "Panel-Bereich skalieren",
|
||||
"cmd.panel.resize_pair": "Panel-Trennlinie verschieben",
|
||||
"cmd.parameters_add": "Benutzerparameter hinzufügen",
|
||||
"cmd.parameters_change": "Parameter ändern",
|
||||
"cmd.parameters_comment": "Parameter kommentieren",
|
||||
@@ -1289,7 +1266,6 @@
|
||||
"cmd.timeline_stop": "Verlauf anhalten",
|
||||
"cmd.timeline_suppress": "Merkmal unterdrücken",
|
||||
"cmd.ui.assistant": "Assistenten ein-/ausblenden",
|
||||
"cmd.view.navigator": "Eigener Ansichtsnavigator",
|
||||
"cmd.view.render_browser": "Render-Browser",
|
||||
"cmd.view.render_dock": "Render-Panel verschieben",
|
||||
"cmd.view.render_focus": "Render-Fokusansicht",
|
||||
@@ -1380,7 +1356,6 @@
|
||||
"crash.unclean_explanation": "Beim späteren Start erkannt; der genaue Zeitpunkt des Beendens wurde nicht aufgezeichnet. Auch ein Herunterfahren des Systems oder ein erzwungener Stopp kann ein unsauberes Beenden verursachen.",
|
||||
"delete.and_more": "… und {count} weitere",
|
||||
"diagnostic.empty_boolean_component": "In Komponente '{component}' gibt es keinen Körper zum Abziehen oder für eine Schnittmenge; das Werkzeug überschneidet sich eventuell mit '{body}' in Komponente '{other}' – aktivieren Sie diese Komponente oder wählen Sie den Körper als Ziel",
|
||||
"diagnostic.reselect_reference": "{feature}: Die Referenz `{reference}` existiert nicht mehr so, wie sie ausgewählt wurde; wählen Sie sie erneut aus",
|
||||
"dialog.bodies_count": "{count} Körper",
|
||||
"dialog.clearance_invalid": "der Abstand muss eine Länge von 0 oder mehr sein",
|
||||
"dialog.compute": "Berechnen",
|
||||
@@ -1388,17 +1363,11 @@
|
||||
"dialog.computing": "Wird berechnet… {seconds} s",
|
||||
"dialog.could_not_be_checked": "nicht prüfbar",
|
||||
"dialog.custom_axis": "↖ Eigene Achse",
|
||||
"dialog.cylinder_face_tip": "Die Fläche oder der Eckpunkt des Modells, auf dem der Zylinder steht: hier klicken, dann in der Ansicht darauf klicken. Leer gelassen platzieren ihn X, Y und Z.",
|
||||
"dialog.dimension_insert_hint": "Doppelklicken Sie auf eine andere steuernde Bemaßung, um ihren Namen einzufügen",
|
||||
"dialog.dimension_name": "Bemaßung: {name}",
|
||||
"dialog.dimension_step_down": "Um {step} verringern",
|
||||
"dialog.dimension_step_expression": "Nur eine Zahl lässt sich schrittweise ändern; ein Ausdruck bleibt, wie er eingegeben ist",
|
||||
"dialog.dimension_step_up": "Um {step} erhöhen",
|
||||
"dialog.edit_title": "{title} bearbeiten",
|
||||
"dialog.no_shown_body": "Kein sichtbarer Körper zum Ändern vorhanden. Blenden Sie einen Körper ein oder wählen Sie „Neuer Körper“.",
|
||||
"dialog.pattern_sketch_not_object": "Körper oder Funktionen wählen: Eine Skizze ist keines von beiden. Ihre Kurven mit Muster oder Spiegeln in der Skizze wiederholen oder sie zuerst extrudieren.",
|
||||
"dialog.primitive_point_tip": "Ein Punkt, der X, Y und Z füllt: hier klicken, dann einen Eckpunkt, eine Kante oder Fläche des Modells, einen Punkt einer Skizze oder den Ursprung anklicken.",
|
||||
"dialog.reference_edge_tip": "Eine gerade Kante der gewählten Fläche, von der aus der Abstand darunter gemessen wird: hier klicken, dann die Kante anklicken.",
|
||||
"dialog.select": "↖ Auswählen",
|
||||
"dialog.selected_count": "↖ {count} ausgewählt",
|
||||
"dialog.selection_constraint": "Bedingung {id}",
|
||||
@@ -1420,7 +1389,6 @@
|
||||
"document.read_only_save": "Dieses Design wurde schreibgeschützt geöffnet: Speichern Sie es als neue Datei",
|
||||
"document.save_changes": "Änderungen in {name} speichern?",
|
||||
"document.save_copy": "Kopie speichern…",
|
||||
"document.tab_unsaved_changes": "Ungespeicherte Änderungen",
|
||||
"document.unsaved_changes": "{name} enthält ungespeicherte Änderungen. Wenn Sie nicht speichern, gehen sie verloren.",
|
||||
"drive.rigid": "{joint} ist ein starres Gelenk. Es hält die verbundenen Teile fest zusammen und ermöglicht daher keine Bewegung. Um die Teile beweglich zu machen, ändern Sie den Gelenktyp unter „Gelenk bearbeiten“: Drehgelenk für Drehbewegungen, Schubgelenk für lineare Bewegungen oder Zylindergelenk für beides.",
|
||||
"drive.stopped_by_contact": "Angehalten bei {value}: {a} würde {b} durchdringen",
|
||||
@@ -1441,9 +1409,6 @@
|
||||
"each rail's curves must be in one sketch": "die Kurven jeder Führungskurve müssen in derselben Skizze liegen",
|
||||
"feature computation ended without a result": "die Berechnung des Merkmals wurde ohne Ergebnis beendet",
|
||||
"file.open.drawing_filter": "Designs, CAD-Dateien, Netze und Zeichnungen",
|
||||
"file.open.portal_fallback.no_portal_service": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Der Dateiauswahl-Dienst des Desktops ist nicht verfügbar.",
|
||||
"file.open.portal_fallback.no_session_bus": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Es gibt keinen D-Bus-Sitzungsbus.",
|
||||
"file.open.portal_fallback.xwayland": "Die geöffnete Dateiauswahl ist nicht die des Desktops: Unter XWayland lehnt das Portal das Elternfenster ab.",
|
||||
"file_load.built": "Bisher erstellt: {built} von {total} Merkmalen.",
|
||||
"file_load.converting": "Volumenkörper {done} von {total} wird konvertiert",
|
||||
"file_load.inserting": "Geometrie wird eingefügt",
|
||||
@@ -1456,14 +1421,6 @@
|
||||
"file_load.reading_percent": "Datei wird gelesen... {percent} %",
|
||||
"file_load.rebuilding": "Merkmal {done} von {total} wird neu aufgebaut",
|
||||
"file_load.recovering": "Design wird wiederhergestellt",
|
||||
"file_load.step_build": "Modell wird aufgebaut",
|
||||
"file_load.step_feature": "Merkmal {done} von {total}",
|
||||
"file_load.step_part": "Teil {done} von {total}",
|
||||
"file_load.step_percent": "{percent} %",
|
||||
"file_load.step_read": "Datei wird gelesen",
|
||||
"file_load.step_rebuild": "Merkmale werden neu aufgebaut",
|
||||
"file_load.step_repair": "Teile werden geprüft und repariert",
|
||||
"file_load.step_show": "Ansicht wird vorbereitet",
|
||||
"file_load.stopping": "Warten, bis der vorherige Vorgang beendet ist...",
|
||||
"file_load.updating_links": "Verknüpfungen werden aktualisiert",
|
||||
"file_load.waiting": "Warten auf Dateivorgang",
|
||||
@@ -1560,11 +1517,6 @@
|
||||
"help.joint.reach": "Bewegt einen Punkt eines Teils auf ein Ziel zu, indem die Gelenke seiner Kette innerhalb ihrer Grenzen verstellt werden. Damit stellen Sie einen Mechanismus über die Lage seines Endes ein, etwa der Spitze eines Roboterarms.",
|
||||
"help.joint.rigid_group": "Verbindet die gewählten Teile starr in ihrer jetzigen Lage, sodass sie sich als Einheit bewegen. Für Teile, die zusammengehören und sich gegeneinander nicht bewegen.",
|
||||
"help.joint.screw": "Koppelt Drehen und Schieben über eine Steigung, sodass jede Umdrehung um diesen Weg vorrückt. Für Gewindespindeln, Schrauben und Trapezspindeln.",
|
||||
"help.layout.apply": "Ein gespeichertes Layout auf den aktuellen Arbeitsbereich anwenden. Geöffnete Editoren behalten ihre Entwürfe.",
|
||||
"help.layout.get": "Das übernommene Layout lesen und registrierte Arbeitsbereiche anzeigen.",
|
||||
"help.layout.reset": "Das registrierte Standardlayout des aktuellen Arbeitsbereichs wiederherstellen. Eigene Vorlagen bleiben erhalten.",
|
||||
"help.layout.save": "Das vollständige Layout unter einem Namen speichern. Eine vorhandene eigene Vorlage wird nur mit ausdrücklicher Erlaubnis überschrieben.",
|
||||
"help.layout.workspace.select": "Einen registrierten Layout-Arbeitsbereich auswählen und das bisherige Layout merken.",
|
||||
"help.library.apply": "Wendet ein Material oder eine Darstellung aus einer lokalen Bibliotheksdatei auf Körper, Flächen oder Komponenten an. Damit nutzen Sie Materialien aus einem anderen Design wieder.",
|
||||
"help.library.export": "Speichert die Materialien und Darstellungen des Designs in einer Bibliotheksdatei. Damit teilen Sie sie oder nutzen sie in anderen Designs wieder.",
|
||||
"help.library.inspect": "Prüft eine Bibliotheksdatei und listet ihre Materialien und Darstellungen auf, ohne das Design zu ändern. Damit sehen Sie vor dem Anwenden, was eine Bibliothek enthält.",
|
||||
@@ -1578,21 +1530,6 @@
|
||||
"help.notice": "NOTICE",
|
||||
"help.occurrence.ground": "Setzt ein Teil an seiner Stelle fest oder gibt es wieder frei, sodass Gelenke die übrigen Teile um es herum bewegen. Für den Rahmen oder Sockel, auf dem der Rest der Baugruppe aufbaut.",
|
||||
"help.occurrence.move": "Bewegt eine Baugruppeninstanz ausschließlich entlang der Freiheitsgrade, die ihre Gelenke zulassen. Wählen Sie eine Fläche des Bauteils aus und ziehen Sie anschließend einen der verfügbaren Pfeile oder Bögen.",
|
||||
"help.panel.activate": "Eine geöffnete Panel-Registerkarte auswählen, ohne den Tastaturfokus anzufordern.",
|
||||
"help.panel.close": "Ein Panel unter Beachtung seines Inhaltslebenszyklus schließen. Seine Platzierung wird gespeichert.",
|
||||
"help.panel.collapse": "Einen Panel-Bereich ein- oder ausklappen und seine gewünschten Abmessungen beibehalten.",
|
||||
"help.panel.dismiss": "Die vorübergehende Ansicht eines eingeklappten Panels schließen.",
|
||||
"help.panel.float": "Ein Panel in einen frei positionierten Bereich verschieben.",
|
||||
"help.panel.focus": "Ein Panel fokussieren, seinen gespeicherten Zustand bei Bedarf wieder öffnen und seinen Bereich ausklappen.",
|
||||
"help.panel.list": "Registrierte Panel-Arten, übernommene Instanzen, aktive Registerkarten, Verfügbarkeit und Host-Funktionen lesen.",
|
||||
"help.panel.move": "Ein Panel in einen neuen Bereich verschieben oder einer vorhandenen Registerkartengruppe hinzufügen.",
|
||||
"help.panel.native_close": "Einen nativen Panel-Bereich an seine gespeicherte Position zurückbringen, ohne seinen Inhalt abzubrechen.",
|
||||
"help.panel.open": "Eine Panel-Art mit einer ausdrücklichen Instanzkennung öffnen. Neue Instanzen benötigen eine Platzierung.",
|
||||
"help.panel.peek": "Ein eingeklapptes Panel vorübergehend anzeigen, ohne seine gespeicherte Größe oder seinen Klappzustand zu ändern.",
|
||||
"help.panel.popout": "Ein Panel bei Unterstützung in ein natives Fenster verschieben. Im Browser wird ein frei positionierter Bereich verwendet.",
|
||||
"help.panel.reset_pair": "Das Standardgrößenverhältnis zweier benachbarter Andockbereiche innerhalb ihrer gemeinsamen Grenzen wiederherstellen.",
|
||||
"help.panel.resize": "Die äußere Breite und Höhe eines Panel-Bereichs innerhalb der Grenzen aller Registerkarten festlegen.",
|
||||
"help.panel.resize_pair": "Benachbarte Andockbereiche gemeinsam skalieren und ihre gemeinsame Ausdehnung beibehalten.",
|
||||
"help.parameters.change": "Öffnet die Tabelle mit benannten Parametern und Ausdrücken, die die Abmessungen des Designs steuern. Ändern Sie einen Wert an einer zentralen Stelle, werden alle Features, die diesen Wert verwenden, entsprechend aktualisiert.",
|
||||
"help.parameters.export": "Speichert die Parameter des Designs als CSV oder JSON. Damit bearbeiten Sie sie in einer Tabellenkalkulation oder übertragen sie in ein anderes Design.",
|
||||
"help.parameters.graph": "Zeigt, welche Parameter und Elemente welche anderen nutzen, samt Schleifen oder Fehlern. Damit sehen Sie, was eine Parameteränderung beeinflusst.",
|
||||
@@ -1865,27 +1802,6 @@
|
||||
"home.start_hint": "Erstellen Sie einen Entwurf, öffnen Sie eine zuletzt verwendete Datei oder erkunden Sie ein Beispiel.",
|
||||
"home.whats_new": "Neuigkeiten",
|
||||
"home.yesterday": "Gestern",
|
||||
"import_report.details": "Details:",
|
||||
"import_report.file_one": "Die Datei hatte 1 weiteres Problem.",
|
||||
"import_report.file_other": "Die Datei hatte {count} weitere Probleme.",
|
||||
"import_report.hint": "Die Teile erneut aus dem Programm zu exportieren, mit dem sie erstellt wurden, kann das beheben. „Warnmeldung kopieren“ enthält die Details.",
|
||||
"import_report.left_out_one": "1 Fläche konnte nicht repariert werden und fehlt.",
|
||||
"import_report.left_out_other": "{count} Flächen konnten nicht repariert werden und fehlen.",
|
||||
"import_report.needs_repair_one": "1 Teil muss repariert werden, bevor daran modelliert werden kann.",
|
||||
"import_report.needs_repair_other": "{count} Teile müssen repariert werden, bevor daran modelliert werden kann.",
|
||||
"import_report.not_imported_one": "1 Teil konnte nicht importiert werden.",
|
||||
"import_report.not_imported_other": "{count} Teile konnten nicht importiert werden.",
|
||||
"import_report.not_shown_one": "1 Fläche konnte nicht gezeichnet werden und ist ausgeblendet.",
|
||||
"import_report.not_shown_other": "{count} Flächen konnten nicht gezeichnet werden und sind ausgeblendet.",
|
||||
"import_report.open_one": "1 Teil hat Lücken zwischen seinen Flächen und kam als offener Körper herein.",
|
||||
"import_report.open_other": "{count} Teile haben Lücken zwischen ihren Flächen und kamen als offene Körper herein.",
|
||||
"import_report.refused_one": "1 Teil konnte nicht zu einem Volumenkörper werden und wurde nicht importiert.",
|
||||
"import_report.refused_other": "{count} Teile konnten nicht zu Volumenkörpern werden und wurden nicht importiert.",
|
||||
"import_report.repaired_one": "Kleine Fehler wurden an 1 Teil repariert.",
|
||||
"import_report.repaired_other": "Kleine Fehler wurden an {count} Teilen repariert.",
|
||||
"import_report.solids": "{solids} von {parts} Teilen als Volumenkörper importiert.",
|
||||
"import_report.surfaces_one": "1 Teil konnte nicht zu einem Volumenkörper werden und kam als Flächen herein.",
|
||||
"import_report.surfaces_other": "{count} Teile konnten nicht zu Volumenkörpern werden und kamen als Flächen herein.",
|
||||
"install.button_tip": "SolidCraft als App installieren: eigenes Fenster, funktioniert auch offline",
|
||||
"install.dock_1": "Wählen Sie Ablage › Zum Dock hinzufügen.",
|
||||
"install.dock_2": "Klicken Sie auf „Hinzufügen“.",
|
||||
@@ -1917,21 +1833,21 @@
|
||||
"joint.rigid_group_select_components": "Wählen Sie zwei oder mehr Komponenten aus.",
|
||||
"joint_move.blocked_hint": "Prüfen Sie den Gelenktyp (z. B. „Starr“), die Fixierung, die Gelenkgrenzen und die durch Parameter gesteuerten Werte.",
|
||||
"joint_move.blocked_title": "Dieses Bauteil kann nicht bewegt werden",
|
||||
"joint_move.coarse": "Schritt nahe der Achse",
|
||||
"joint_move.coarse": "Drehschritt nahe der Achse (°)",
|
||||
"joint_move.conflict": "Ein Gelenk dieses Bauteils steht im Konflikt mit den anderen: {joint}.",
|
||||
"joint_move.conflict_hint": "Bearbeiten, unterdrücken oder löschen Sie das widersprüchliche Gelenk, damit die anderen Gelenke das Bauteil platzieren können.",
|
||||
"joint_move.distance": "Abstand vom Ausgangspunkt",
|
||||
"joint_move.done": "Fertig",
|
||||
"joint_move.fine": "Schritt fern der Achse",
|
||||
"joint_move.fine": "Drehschritt weit von der Achse entfernt (°)",
|
||||
"joint_move.fixed": "Dieses Bauteil verfügt über keine bewegliche Gelenkachse.",
|
||||
"joint_move.help": "Verschieben: Ziehen Sie einen Pfeil oder Ring. Mit Esc brechen Sie den aktuellen Ziehvorgang ab.",
|
||||
"joint_move.legacy_hint": "Aus: das bisherige Verschieben-Panel.",
|
||||
"joint_move.precision": "Für den feinen Schritt außerhalb der gestrichelten Führung ziehen.",
|
||||
"joint_move.legacy_hint": "Deaktivieren, um wieder den bisherigen Verschiebebereich zu verwenden.",
|
||||
"joint_move.precision": "Ziehen Sie außerhalb der gestrichelten Führung, um die Drehung fein einzustellen.",
|
||||
"joint_move.rotation": "Drehwinkel vom Ausgangspunkt",
|
||||
"joint_move.select": "Wählen Sie eine Fläche oder eine Baugruppenkomponente aus und anschließend „Verschieben“.",
|
||||
"joint_move.settings": "Baugruppe verschieben",
|
||||
"joint_move.step": "Schrittweite",
|
||||
"joint_move.use_handles": "Gelenkbewegungsgriffe für Verschieben verwenden",
|
||||
"joint_move.use_handles": "Gelenkgriffe zum Bewegen von Baugruppen verwenden",
|
||||
"keymap.command_id": "Befehls-ID",
|
||||
"keymap.empty": "Keine passenden Befehle",
|
||||
"keymap.use_anyway": "{key} hier trotzdem verwenden",
|
||||
@@ -1991,7 +1907,6 @@
|
||||
"material.selection.mixed": "Unterschiedliche Materialien",
|
||||
"measure.at": "Bei",
|
||||
"measure.item": "Element {number}",
|
||||
"measure.line_to_centre": "Linie zum Mittelpunkt",
|
||||
"measure.quick_copied": "Messwert kopiert",
|
||||
"measure.quick_copy": "Klicken, um den Messwert zu kopieren",
|
||||
"measure.quick_edges": "{count} Kanten",
|
||||
@@ -2022,8 +1937,6 @@
|
||||
"nav.blender.help": "Mittlere Maustaste dreht, Umschalt+Mitte verschiebt, Strg+Mitte zoomt; rechtes Ziehen lässt die Kamera unverändert",
|
||||
"nav.creo.help": "Ziehen mit der mittleren Maustaste dreht, Umschalt+Mitte verschiebt, Strg+Mitte zoomt beim vertikalen Ziehen (nach unten zum Vergrößern); das Mausrad zoomt",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.drag_keeps_mode.help": "Ein Ziehen mit der mittleren oder rechten Taste dreht, verschiebt oder zoomt weiter wie begonnen, bis die Taste losgelassen wird, egal welche Tasten dabei gedrückt oder losgelassen werden. Aus: Die gehaltenen Tasten entscheiden in jedem Moment des Ziehens.",
|
||||
"nav.drag_keeps_mode.label": "Ziehen behält die Navigation bei, mit der es begann",
|
||||
"nav.free_orbit.help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollneigung bei. Das Einrasten mit Umschalt ist deaktiviert.",
|
||||
"nav.free_orbit.label": "Freie Drehung",
|
||||
"nav.free_orbit.snap_help": "Maus und 3D-Maus drehen über beide Pole hinweg und behalten die Rollausrichtung bei. Halten Sie beim Ziehen zum Drehen die Umschalttaste gedrückt, um Blickrichtung und Rollausrichtung einrasten zu lassen.",
|
||||
@@ -2052,12 +1965,6 @@
|
||||
"navbar.assistant": "Assistent",
|
||||
"navbar.custom.add_slot": "{slot} hinzufügen",
|
||||
"navbar.custom.always": "Immer sichtbar",
|
||||
"navbar.custom.anchor.bottom_centre": "Unten mittig",
|
||||
"navbar.custom.anchor.bottom_left": "Unten links",
|
||||
"navbar.custom.anchor.bottom_right": "Unten rechts",
|
||||
"navbar.custom.anchor.top_centre": "Oben mittig",
|
||||
"navbar.custom.anchor.top_left": "Oben links",
|
||||
"navbar.custom.anchor.top_right": "Oben rechts",
|
||||
"navbar.custom.available": "{available} · auf die Leiste ziehen oder auf Hinzufügen klicken",
|
||||
"navbar.custom.buttons": "Schaltflächen",
|
||||
"navbar.custom.compact": "Kompakt",
|
||||
@@ -2094,7 +2001,6 @@
|
||||
"navbar.custom.reset_default": "Auf Standard zurücksetzen",
|
||||
"navbar.custom.right": "Nach rechts",
|
||||
"navbar.custom.select": "Kein Feld ausgewählt. Wählen Sie ein Feld in der Leiste, um es zu verschieben, zu entfernen oder sein Aussehen zu ändern.",
|
||||
"navbar.custom.separator_hint": "Eine Trennlinie zwischen Gruppen von Schaltflächen. Auf die Leiste ziehen oder auf Hinzufügen klicken; beliebig oft verwendbar.",
|
||||
"navbar.custom.separators_full": "Die Leiste hat bereits vier Trennlinien.",
|
||||
"navbar.custom.show": "Navigationsleiste anzeigen",
|
||||
"navbar.custom.shows": "Zeigt",
|
||||
@@ -2234,55 +2140,23 @@
|
||||
"prefs.alt_frees_manipulators": "Alt / Option schaltet auch an Manipulatorpfeilen das Einrasten ab",
|
||||
"prefs.anti_aliasing_amd_dx12": "Die Kantenglättung verwendet 1×, da der Starttest mit Multisampling keine Ausgabe erzeugt hat, während der Test mit 1× erfolgreich war. Sie können oben eine andere Einstellung wählen. Starten Sie SolidCraft neu, damit die Änderung übernommen wird, sofern der Test erfolgreich ist.",
|
||||
"prefs.anti_aliasing_note": "Glättet gezackte Kanten.",
|
||||
"prefs.appearance.colour_vision_help": "Ändert die bedeutungstragenden Farben: Skizzenzustände, Achsen, Biegungen, Analysen und Symbole.",
|
||||
"prefs.appearance.desktop_only": "Nur Desktop",
|
||||
"prefs.appearance.follow_help": "Wird neu geladen, wenn ein anderes Werkzeug die Datei überschreibt.",
|
||||
"prefs.appearance.interface_scale": "Oberflächenskalierung",
|
||||
"prefs.appearance.next_themes": "Nächste Designs",
|
||||
"prefs.appearance.option_count": "{count} Optionen",
|
||||
"prefs.appearance.option_count_one": "{count} Option",
|
||||
"prefs.appearance.previous_themes": "Vorherige Designs",
|
||||
"prefs.appearance.theme_count": "{count} Designs",
|
||||
"prefs.appearance.theme_own": "Eigene Designfarben",
|
||||
"prefs.appearance.viewport_colours": "Farben der 3D-Ansicht",
|
||||
"prefs.appearance.viewport_logo": "Ansichtsfenster und Logo",
|
||||
"prefs.applies_on_restart": "Wird nach einem Neustart übernommen",
|
||||
"prefs.assistant": "Assistent",
|
||||
"prefs.assistant_connecting": "Verbindung",
|
||||
"prefs.assistant_follow_help": "Look At bei Skizzen bewegt die Ansicht mit dem Zeiger.",
|
||||
"prefs.assistant_limit": "Limit",
|
||||
"prefs.assistant_limited": "Begrenzt",
|
||||
"prefs.assistant_pointer_speed": "Zeigergeschwindigkeit",
|
||||
"prefs.assistant_seconds": "Sekunden",
|
||||
"prefs.assistant_seconds_aria": "Sekunden",
|
||||
"prefs.assistant_use_care": "Mit Vorsicht verwenden",
|
||||
"prefs.auto_operation": "Operation automatisch auswählen",
|
||||
"prefs.auto_operation_note": "Vereinigung, Neuer Körper oder Schnitt, je nachdem, was das Material berührt.",
|
||||
"prefs.auto_operation_note": "Die Feature-Dialoge wählen automatisch zwischen „Verbinden“, „Neuer Körper“ und „Ausschneiden“, abhängig davon, was das neue Material berührt. Sobald Sie selbst eine Operation auswählen, wird diese Einstellung nicht mehr angewendet. Deaktiviert: Die beim Öffnen des Dialogs voreingestellte Operation wird beibehalten.",
|
||||
"prefs.axes": "Achsen",
|
||||
"prefs.axis": "Achse",
|
||||
"prefs.blur_cost": "Relativer Renderaufwand. Mehr Balken bedeuten meist mehr Arbeit.",
|
||||
"prefs.blur_device": "Auf diesem Grafikgerät oder Renderpfad nicht verfügbar",
|
||||
"prefs.blur_guard": "Die Unschärfequalität wurde für flüssigere Bilder verringert. Der Radius bleibt unverändert.",
|
||||
"prefs.blur_off": "Aus",
|
||||
"prefs.blur_reduce_transparency": "Durch die Systemeinstellung zur Transparenzreduzierung deaktiviert",
|
||||
"prefs.blur_software": "Mit Softwaregrafik nicht verfügbar (gpu-software)",
|
||||
"prefs.blur_source": "In dieser Arbeitsbereichsansicht nicht verfügbar",
|
||||
"prefs.blur_web": "Auf diesem Web-Renderpfad nicht verfügbar",
|
||||
"prefs.bodies_folders": "Körperordner",
|
||||
"prefs.bodies_folders_always": "Immer",
|
||||
"prefs.bodies_folders_never": "Nie",
|
||||
"prefs.bodies_folders_several": "Mehrere",
|
||||
"prefs.bodies_folders_several": "Nur bei mehreren Körpern",
|
||||
"prefs.bottom": "Unten",
|
||||
"prefs.bottom_centre": "Unten mittig",
|
||||
"prefs.bottom_right": "Unten rechts",
|
||||
"prefs.browser": "Browser",
|
||||
"prefs.browser_components_open": "Komponenten zu Beginn aufgeklappt",
|
||||
"prefs.browser_components_open_note": "Jede Komponente im Browser ist wie bisher von Anfang an aufgeklappt, statt zugeklappt zu bleiben, bis du sie aufklappst.",
|
||||
"prefs.browser_eye_left": "Auge neben dem Aufklapppfeil",
|
||||
"prefs.browser_eye_left_note": "Browserzeilen zeigen ihr Auge zum Ein- und Ausblenden wie bisher neben dem Aufklapppfeil statt in einer Spalte am rechten Zeilenende.",
|
||||
"prefs.browser_reveal_on_hover": "Beim Überfahren einblenden",
|
||||
"prefs.browser_rows_start_closed": "Browser-Zeilen beginnen geschlossen",
|
||||
"prefs.browser_rows_start_closed_note": "Platzierungen und Ordner im Browser beginnen geschlossen, sodass ein Entwurf mit vielen Komponenten seine obersten Zeilen zeigt. Jede öffnet sich mit ihrem Pfeil, „Alle ausklappen“ oder „Im Browser suchen“, das nur die Zeilen bis zu seinem Körper öffnet und die von der letzten Suche geöffneten wieder schließt. Ein in der 3D-Ansicht gewählter Körper lässt den Browser, wie er ist. Ist es aus, beginnen die Ordner geöffnet, Platzierungen so, wie „Komponenten zu Beginn aufgeklappt“ es festlegt, und eine Auswahl in der 3D-Ansicht öffnet den Browser bis zu ihrem Körper, wie bisher.",
|
||||
"prefs.browser_show_note": "Angezeigt lässt den Browser auf dem Bildschirm. Beim Überfahren einblenden blendet ihn aus, bis der Zeiger über seinen Platz oder die schmale Lasche an seinem linken Rand kommt; er bleibt offen, solange der Zeiger darin ist oder ein Menü daraus offen ist.",
|
||||
"prefs.browser_shown": "Angezeigt",
|
||||
"prefs.built_in": "Integriert",
|
||||
@@ -2309,32 +2183,14 @@
|
||||
"prefs.dark_parts_shading_help": "Die Ansicht in linearem Licht mit sanftem Himmelslicht schattieren, damit dunkle Flächen einen Verlauf behalten statt schwarz zu werden",
|
||||
"prefs.dead_zone_note": "Der schraffierte Bereich kennzeichnet die Totzone. Bewegungen innerhalb dieses Bereichs werden ignoriert.",
|
||||
"prefs.default": "Standard",
|
||||
"prefs.design": "Konstruktion",
|
||||
"prefs.design_note": "Womit neue Konstruktionen beginnen.",
|
||||
"prefs.design_tools.ask": "Fragen",
|
||||
"prefs.design_tools.degrees": "Grad",
|
||||
"prefs.design_tools.double_click_help": "Oder zum Bearbeiten einer verknüpften Komponente an Ort und Stelle.",
|
||||
"prefs.design_tools.ghost": "Alte Position verschobener Teile als Geisterbild zeigen",
|
||||
"prefs.design_tools.hole_moves_help": "Erneutes Auswählen der Fläche verschiebt die Bohrung, statt sie zu entfernen.",
|
||||
"prefs.design_tools.hole_snaps_help": "Die Mitte einer Fläche und die Mittelpunkte ihrer kreisförmigen Kanten.",
|
||||
"prefs.design_tools.imported_files": "Importierte Dateien",
|
||||
"prefs.design_tools.note": "Hinweis",
|
||||
"prefs.design_tools.off": "Aus",
|
||||
"prefs.design_tools.placement_group": "Auflage, Zapfen, Schnappverbindung",
|
||||
"prefs.design_tools.rows": "Zeilen",
|
||||
"prefs.design": "Design-Vorgaben",
|
||||
"prefs.design_note": "Legt fest, mit welchen Einstellungen neue Designs beginnen. Bereits geöffnete Designs behalten ihre eigenen Einstellungen.",
|
||||
"prefs.devices": "3D-Maus",
|
||||
"prefs.dialog_buttons": "Dialogschaltflächen",
|
||||
"prefs.dialogs": "Dialoge",
|
||||
"prefs.dialogs_edge": "Am Rand der Ansicht",
|
||||
"prefs.dialogs_left_column": "In der linken Spalte",
|
||||
"prefs.dialogs_note": "Wo Befehlsdialoge öffnen: am Rand der Ansicht angedockt oder in der linken Spalte anstelle von Browser und Verlauf, die beim Schließen des Dialogs wie zuvor zurückkehren.",
|
||||
"prefs.dimension_step_angle": "Was die Pfeile des Fensters, Auf/Ab und das Mausrad zu einem Winkel addieren",
|
||||
"prefs.dimension_step_length": "Was die Pfeile des Fensters, Auf/Ab und das Mausrad zu einer Länge addieren",
|
||||
"prefs.dimension_steps": "Schritte im Fenster",
|
||||
"prefs.dimension_values": "Bemaßungswerte",
|
||||
"prefs.dimension_values_note": "Wo der Wert einer Bemaßung eingegeben wird. Ein Fenster hat Pfeile, die ihn schrittweise ändern; das Bemaßungswerkzeug platziert zuerst den Text.",
|
||||
"prefs.dimension_values_view": "In der Ansicht",
|
||||
"prefs.dimension_values_window": "In einem Fenster",
|
||||
"prefs.display": "Anzeige",
|
||||
"prefs.edges": "Kanten und Schattierung",
|
||||
"prefs.extend": "Erweitern",
|
||||
@@ -2370,23 +2226,6 @@
|
||||
"prefs.graphics_card_restart": "Diese Einstellung wird nach einem Neustart von SolidCraft wirksam. Die Umgebungsvariable WGPU_ADAPTER_NAME hat Vorrang vor dieser Auswahl.",
|
||||
"prefs.graphics_card_unused": "Die gespeicherte Grafikkarte wurde für dieses Fenster nicht verwendet; die tatsächlich genutzte Karte steht unter Hilfe › Info und in der Statuszeile. Versuchen Sie Automatisch, oder setzen Sie WGPU_ADAPTER_NAME auf einen eindeutigen Teil des Kartennamens.",
|
||||
"prefs.graphics_note": "Wägen Sie Leistung und Darstellungsqualität gegeneinander ab. Jede Option veranschaulicht ihre Wirkung anhand eines Beispiels.",
|
||||
"prefs.graphics_page.current_view": "Aktuelle Ansicht",
|
||||
"prefs.graphics_page.fastest": "Am schnellsten",
|
||||
"prefs.graphics_page.fold_items": "Elemente einklappen",
|
||||
"prefs.graphics_page.in_view": "In der Ansicht",
|
||||
"prefs.graphics_page.live_details": "Live-Details",
|
||||
"prefs.graphics_page.own_mix": "Eigene Mischung",
|
||||
"prefs.graphics_page.quick_measure": "Schnellmessung",
|
||||
"prefs.graphics_page.reference_geometry_through_bodies": "Referenzgeometrie durch Körper hindurch",
|
||||
"prefs.graphics_page.restart": "Neustart erforderlich",
|
||||
"prefs.graphics_page.sharpest": "Am schärfsten",
|
||||
"prefs.graphics_page.single_surface_analysis": "Einzelne Flächenanalyse",
|
||||
"prefs.graphics_page.status_chips": "Status-Chips",
|
||||
"prefs.graphics_page.subtitle": "Geschwindigkeit gegen Glätte abwägen.",
|
||||
"prefs.graphics_page.supersampling": "Supersampling",
|
||||
"prefs.graphics_page.supersampling_adaptive": "Nur solange die Ansicht stillsteht",
|
||||
"prefs.graphics_page.surfaces_edges": "Flächen und Kanten",
|
||||
"prefs.graphics_page.view_bar": "Auch in der Ansichtsleiste",
|
||||
"prefs.graphics_presets_note": "Mit den Profilen werden die unten aufgeführten Optionen gemeinsam eingestellt.",
|
||||
"prefs.hide_constraint_glyphs_zoomed_out": "Bindungssymbole bei starkem Herauszoomen ausblenden",
|
||||
"prefs.hide_constraint_glyphs_zoomed_out_note": "Bindungssymbole verschwinden, wenn die Skizze so weit herausgezoomt ist, dass sie sie überladen würden. Aus: sie behalten bei jedem Zoom eine feste Bildschirmgröße.",
|
||||
@@ -2395,7 +2234,7 @@
|
||||
"prefs.history_origin_bodies_note": "Der Verlauf links oder unter dem Browser beginnt mit dem Körper-Ordner der aktiven Komponente und dem Ursprung, mit Augen und Menüs wie im Browser. Der Verlauf unten bleibt unverändert.",
|
||||
"prefs.history_play_button": "Schaltfläche „Verlauf abspielen“ in den Verlaufssteuerelementen",
|
||||
"prefs.history_play_button_note": "Fügt „Verlauf abspielen“ zwischen „Einen Schritt zurück“ und „Einen Schritt vor“ in die Verlaufssteuerelemente ein. Mit einem Rechtsklick auf die Steuerelemente lässt sich die Schaltfläche auch ein- oder ausblenden.",
|
||||
"prefs.history_sketches": "Skizzen im Verlauf",
|
||||
"prefs.history_sketches": "Skizzen",
|
||||
"prefs.history_sketches_in_order": "In Erstellungsreihenfolge",
|
||||
"prefs.history_sketches_note": "Wenn „Skizzen“ auf „Unter dem zugehörigen Feature“ eingestellt ist, wird jede Skizze im linken Verlauf eine Ebene unter dem ersten daraus erstellten Feature angezeigt, sofern der Bereich breit genug für die Namen ist. Die Timeline unten zeigt weiterhin die Reihenfolge, in der die Elemente erstellt wurden.",
|
||||
"prefs.history_sketches_under_feature": "Unter dem zugehörigen Feature",
|
||||
@@ -2418,22 +2257,6 @@
|
||||
"prefs.language_system": "System",
|
||||
"prefs.layout": "Layout",
|
||||
"prefs.layout_note": "Im Layout „Kompakt“ werden Bereiche an den Fensterrändern angedockt. Transparenz für den Browser ist nur im Layout „Schwebend“ verfügbar. Die Position des Assistenten ändern Sie auf der Seite „Assistent“.",
|
||||
"prefs.layout_page.active_commands": "Aktive Befehle hervorheben",
|
||||
"prefs.layout_page.always": "Immer",
|
||||
"prefs.layout_page.card": "Karte",
|
||||
"prefs.layout_page.column": "Spalte",
|
||||
"prefs.layout_page.hover": "Bei Mauszeiger",
|
||||
"prefs.layout_page.icons_labels": "Symbole + Beschriftungen",
|
||||
"prefs.layout_page.ok_cancel": "OK und Abbrechen",
|
||||
"prefs.layout_page.on_hover": "Bei Mauszeiger",
|
||||
"prefs.layout_page.open": "Öffnen",
|
||||
"prefs.layout_page.origin_bodies": "Ursprung und Körper oben",
|
||||
"prefs.layout_page.panels": "Bereiche",
|
||||
"prefs.layout_page.pinned_menus": "Angeheftete Befehle aus Menüs ausblenden",
|
||||
"prefs.layout_page.play_history": "Verlaufswiedergabe",
|
||||
"prefs.layout_page.slot_count": "{count} von {max} Plätzen",
|
||||
"prefs.layout_page.view_corner": "Auch in der Ansichtsecke",
|
||||
"prefs.layout_page.window": "Fenster",
|
||||
"prefs.left": "Links",
|
||||
"prefs.left_click": "Linksklick",
|
||||
"prefs.line_drag_draws_one": "Drücken und Ziehen zeichnet eine Linie",
|
||||
@@ -2462,13 +2285,6 @@
|
||||
"prefs.mouse_heading": "Maus",
|
||||
"prefs.mouse_mapping": "Mausbelegung",
|
||||
"prefs.navigation": "Navigation",
|
||||
"prefs.navigation.axes_signed": "Achsen",
|
||||
"prefs.navigation.cube_compact": "Würfel",
|
||||
"prefs.navigation.device": "Gerät",
|
||||
"prefs.navigation.free": "Frei",
|
||||
"prefs.navigation.orbit_style": "Drehstil",
|
||||
"prefs.navigation.radial_help": "Die rechte Taste halten oder kurz bewegen, um einen Werkzeugring zu öffnen.",
|
||||
"prefs.navigation.turntable": "Drehteller",
|
||||
"prefs.navigation_note": "Legt fest, wie Maus, Trackpad und 3D-Maus die Ansicht steuern.",
|
||||
"prefs.navigator_fit": "Modell nach einem Ansichtswechsel am Navigator einpassen",
|
||||
"prefs.navigator_fit_note": "Eine Fläche, Ecke oder ein Pfeil des Würfels oder eine Achse des Gizmos blickt auch auf die Mitte des Modells, aus dem einen Abstand, der es von jeder Seite einpasst (seine Hüllkugel). Home nutzt denselben Zoom und dieselbe Mitte, solange dies an ist, sodass danach gewählte Ansichten sich nur drehen. Aus: Die Ansicht dreht sich an Ort und Stelle und behält Ziel und Zoom.",
|
||||
@@ -2485,15 +2301,11 @@
|
||||
"prefs.origin_plane_names_note": "Namen in Browser, Dialogen und Listen. {top} ist XY, {front} ist XZ und {right} ist YZ, mit Z nach oben. Befehle, Skripte und gespeicherte Konstruktionen behalten XY, XZ und YZ.",
|
||||
"prefs.overall_speed": "Gesamtgeschwindigkeit",
|
||||
"prefs.parameter_list_rows": "Zeilen der Parameterliste",
|
||||
"prefs.parameter_list_rows_tip": "Maximale Anzahl der Parameter, die Pfeil abwärts in einem leeren Ausdrucksfeld auflistet.",
|
||||
"prefs.parameter_list_rows_tip": "Der Pfeil nach unten in einem leeren Wertefeld listet die Parameter auf, Favoriten zuerst, bis zu dieser Anzahl. Weniger hält die Liste in einem großen Design übersichtlich.",
|
||||
"prefs.persistent_ribbon_pressed": "Aktive Menübandbefehle hervorheben",
|
||||
"prefs.persistent_ribbon_pressed_help": "Hebt Befehle im Menüband hervor, solange ihre Effekte eingeschaltet sind. Deaktivieren Sie diese Option, um die bisherige neutrale Darstellung und Befehlshilfe beizubehalten.",
|
||||
"prefs.perspective_fov": "Sichtfeld",
|
||||
"prefs.placement": "Platzierung",
|
||||
"prefs.plugins_enabled": "Aktiviert",
|
||||
"prefs.plugins_installed": "Installiert",
|
||||
"prefs.plugins_intro": "Isolierte Erweiterungen.",
|
||||
"prefs.plugins_show_folder": "Plugin-Ordner anzeigen",
|
||||
"prefs.prefer_free_sketch_points": "Bei überlappenden Punkten bewegliche Skizzenpunkte bevorzugen",
|
||||
"prefs.preview_choices": "Vorschau Ihrer Einstellungen",
|
||||
"prefs.preview_tint_new_faces": "Neue Fläche in der Vorschau einfärben",
|
||||
@@ -2508,8 +2320,6 @@
|
||||
"prefs.profile_exported": "Profil exportiert.",
|
||||
"prefs.profile_imported": "Profil importiert.",
|
||||
"prefs.profile_name_hint": "Profilname, z. B. Laptop",
|
||||
"prefs.profile_operations": "Vorgänge profilieren",
|
||||
"prefs.profile_operations_note": "Zeichnet auf, wie lange SMLib-Aufrufe, Kernel-Vorgänge, Features, Befehle und Importe dauern. Agenten lesen es mit diagnostics.profile.",
|
||||
"prefs.profile_rename": "Umbenennen",
|
||||
"prefs.profile_renamed": "Profil umbenannt.",
|
||||
"prefs.profile_save": "Speichern",
|
||||
@@ -2540,8 +2350,6 @@
|
||||
"prefs.search": "Einstellungen suchen",
|
||||
"prefs.search_empty": "Keine passenden Einstellungen",
|
||||
"prefs.select": "Auswählen",
|
||||
"prefs.select_hidden_edges": "Verdeckte Kanten in der schattierten Ansicht mit verdeckten Kanten auswählen",
|
||||
"prefs.select_hidden_edges_help": "Kanten durch Flächen auswählen, wenn die schattierte Ansicht mit verdeckten Kanten aktiv ist, auch zum Verrunden. Aus behält das Auswahlverhalten von v0.30.1 bei. Ausgeblendete Körper bleiben ausgeschlossen.",
|
||||
"prefs.select_one_item": "Ein Element auswählen",
|
||||
"prefs.select_other_hidden": "Verborgene Geometrie mit „Andere auswählen“ auswählen",
|
||||
"prefs.select_other_hidden_help": "Ein: Verborgene Körper, Skizzen und verdeckte Kanten nahe dem Zeiger einbeziehen. Aus: Die ursprüngliche Liste sichtbarer Geometrie direkt unter dem Zeiger verwenden.",
|
||||
@@ -2550,45 +2358,8 @@
|
||||
"prefs.shift_add_ctrl_toggle": "Umschalt fügt nur hinzu; Strg / Cmd schaltet die Auswahl um",
|
||||
"prefs.shift_add_note": "Aktiviert: Umschalt fügt Elemente zur Auswahl hinzu, Strg / Cmd schaltet ihre Auswahl um. Deaktiviert (Standard): Beide Tasten schalten die Auswahl um.",
|
||||
"prefs.shortcuts": "Tastenkürzel",
|
||||
"prefs.shortcuts_import": "Importieren",
|
||||
"prefs.shortcuts_intro": "Tasten für jeden Befehl.",
|
||||
"prefs.shortcuts_not_assigned": "Nicht zugewiesen",
|
||||
"prefs.shortcuts_press": "Taste drücken...",
|
||||
"prefs.single_surface_analysis": "Nur eine Flächenanalyse verwenden",
|
||||
"prefs.single_surface_analysis_help": "Wenn deaktiviert, können Flächenschattierung und Stetigkeitsanalyse gleichzeitig aktiv bleiben. Beim Aktivieren bleibt die zuletzt gestartete oder geänderte Analyse erhalten.",
|
||||
"prefs.sketch.align_points": "An anderen Skizzenpunkten ausrichten",
|
||||
"prefs.sketch.align_points_note": "Beim Zeichnen richten sich Linien gleichzeitig an Punkten auf beiden Achsen aus.",
|
||||
"prefs.sketch.auto_dimensions": "Neue Formen vollständig bemaßen",
|
||||
"prefs.sketch.auto_dimensions_note": "Eingegebene Werte und alle Maße, die die Form festlegen.",
|
||||
"prefs.sketch.auto_locate": "Vom Skizzenursprung aus positionieren",
|
||||
"prefs.sketch.auto_locate_note": "Mit vollständiger Bemaßung.",
|
||||
"prefs.sketch.center_arc": "Mittelpunktbogen folgt dem Zeiger",
|
||||
"prefs.sketch.click_selects": "Klick wählt",
|
||||
"prefs.sketch.conflict_colour": "Konfliktfarbe",
|
||||
"prefs.sketch.drawing": "Zeichnen",
|
||||
"prefs.sketch.ellipse_axes": "Ellipsen mit ihren Achsen zeichnen",
|
||||
"prefs.sketch.ellipse_axes_note": "Haupt- und Nebenachse als Konstruktionslinien, damit Halbachsen und Achsen bemaßt werden können. Aus: nur die Ellipse, und D darauf ergibt ihren Nebenradius, wohin der Wert auch gezogen wird, gezeichnet und mit R beschriftet wie bisher.",
|
||||
"prefs.sketch.exit_keep": "Beibehalten",
|
||||
"prefs.sketch.exit_previous": "Zur Ansicht davor zurückkehren",
|
||||
"prefs.sketch.exit_unless_rotated": "Außer bei Drehung",
|
||||
"prefs.sketch.faces_edges": "Flächen und Kanten",
|
||||
"prefs.sketch.hide_glyphs": "Symbole beim Herauszoomen ausblenden",
|
||||
"prefs.sketch.marks": "Markierungen in der Skizze",
|
||||
"prefs.sketch.note": "Zeichnen, Fangen und Auswählen.",
|
||||
"prefs.sketch.opening_closing": "Öffnen und Schließen",
|
||||
"prefs.sketch.prefer_free_points": "Bei Überlappung bewegliche Punkte bevorzugen",
|
||||
"prefs.sketch.project_options": "Zuerst Projektionsoptionen abfragen",
|
||||
"prefs.sketch.recent_snap": "Am Schnittpunkt zweier Hilfslinien fangen",
|
||||
"prefs.sketch.select_other_hidden": "Andere auswählen erfasst verdeckte Geometrie",
|
||||
"prefs.sketch.shortcut_cycles_modes": "Erneuter Kurzbefehl wechselt den Modus",
|
||||
"prefs.sketch.shortcut_cycles_modes_tip": "Aus: Das Werkzeug startet erneut.",
|
||||
"prefs.sketch.snapping_guides": "Fangen und Hilfslinien",
|
||||
"prefs.sketch.state_icons": "Statussymbole",
|
||||
"prefs.sketch.tracking_guides": "Hilfslinien für Mittelpunkte und Kantenmitten",
|
||||
"prefs.sketch.tracking_guides_note": "Temporäre waagerechte und senkrechte Hilfslinien von überfahrenen Mittelpunkten und Kantenmitten.",
|
||||
"prefs.sketch.tracking_points": "Hilfslinien auch von Skizzenpunkten",
|
||||
"prefs.sketch.unless_turned_note": "zurück, wenn nicht gedreht",
|
||||
"prefs.sketch.whole_bodies": "Ganze Körper",
|
||||
"prefs.sketch_align_points": "Beim Zeichnen an anderen Skizzenpunkten ausrichten",
|
||||
"prefs.sketch_align_points_note": "Beim Zeichnen richtet sich ein Punkt zugleich waagerecht an einem Skizzenpunkt und senkrecht an einem anderen aus, mit einer gepunkteten Hilfslinie; der Klick behält beide Ausrichtungen. Aus: nur am vorherigen Punkt, jeweils eine Achse.",
|
||||
"prefs.sketch_conflict_colour": "Widersprüchliche Geometrie in der Konfliktfarbe",
|
||||
@@ -2621,11 +2392,11 @@
|
||||
"prefs.source": "Quelle",
|
||||
"prefs.speed": "Geschwindigkeit",
|
||||
"prefs.stand_up_note": "Wenn eine STEP- oder IGES-Datei mit der Z-Achse nach oben geöffnet wird, wird eine Vierteldrehung um die X-Achse angeboten.",
|
||||
"prefs.stand_up_unknown": "Dateien aus unbekannten Programmen",
|
||||
"prefs.stand_up_unknown": "Für Dateien aus unbekannten Programmen",
|
||||
"prefs.stand_up_unknown.ask": "In einem Dialog fragen",
|
||||
"prefs.stand_up_unknown.note": "Einen Hinweis zeigen",
|
||||
"prefs.stand_up_unknown.off": "Nicht anbieten",
|
||||
"prefs.stand_up_unknown_note": "Wie der Vorschlag erscheint, wenn die Datei kein bekanntes Programm nennt.",
|
||||
"prefs.stand_up_unknown_note": "Wie das Aufrichten angeboten wird, wenn der Dateikopf kein Programm nennt, das bekanntlich mit Y nach oben speichert. Ein Hinweis erscheint nur, wenn die Datei auf dem Rücken zu liegen scheint; „In einem Dialog fragen“ fragt wie bisher bei jeder solchen Datei.",
|
||||
"prefs.status_bar": "Statusleiste",
|
||||
"prefs.status_bar_note": "Eine schmale Leiste am unteren Fensterrand zeigt, was gerade bearbeitet wird, den Zustand der offenen Skizze in seiner Farbe und die Einheiten der Konstruktion; ein Klick auf die Einheiten ändert sie. Der Zustand der Skizze steht dann nicht mehr in der Ecke der Ansicht.",
|
||||
"prefs.status_done": "Fertig",
|
||||
@@ -2661,10 +2432,6 @@
|
||||
"prefs.theme_follow_off": "Aus",
|
||||
"prefs.theme_follow_stop": "Nicht mehr folgen",
|
||||
"prefs.theme_import_hint": "Klicken, um eine Designdatei zu wählen, eine hierher ziehen oder mit kopiertem Design Strg+V (Cmd+V auf dem Mac) drücken",
|
||||
"prefs.theme_notice_dismiss": "Ausblenden",
|
||||
"prefs.theme_option_blur": "Unschärfe hinter Panels",
|
||||
"prefs.theme_option_blur_help": "Unschärfe hinter durchscheinenden Panels in physischen Pixeln. Null bedeutet aus.",
|
||||
"prefs.theme_option_invalid": "Der gespeicherte Wert für {option} ist ungültig; der Designstandard wird verwendet. Der gespeicherte Wert bleibt erhalten.",
|
||||
"prefs.theme_option_opacity": "Deckkraft",
|
||||
"prefs.theme_option_opacity_help": "Deckkraft schwebender Bedienfelder anpassen. Eine Mindestdeckkraft schützt die Lesbarkeit über den Ansichtsfarben.",
|
||||
"prefs.theme_option_tab_colours": "Tabfarben",
|
||||
@@ -2673,7 +2440,6 @@
|
||||
"prefs.theme_option_tab_fade_ms_help": "Panel- und Ansichtsfarben beim Tabwechsel überblenden. Null wechselt sofort.",
|
||||
"prefs.theme_options_badge": "Optionen",
|
||||
"prefs.theme_options_more": "Mehr · {count}",
|
||||
"prefs.theme_options_newer": "Dieses Design benötigt eine neuere Version von SolidCraft. Nach dem Upgrade das Originaldesign erneut importieren, um seine Optionsdefinitionen wiederherzustellen.",
|
||||
"prefs.theme_options_reset": "Zurücksetzen",
|
||||
"prefs.theme_options_unavailable": "Auf diesem Gerät nicht verfügbar",
|
||||
"prefs.theme_paste": "Design einfügen",
|
||||
@@ -2697,14 +2463,8 @@
|
||||
"prefs.under_cursor": "Unter dem Cursor",
|
||||
"prefs.units_precision": "Einheiten und Genauigkeit",
|
||||
"prefs.updates": "Updates",
|
||||
"prefs.validation": "Körperprüfung",
|
||||
"prefs.validation.each_feature": "Einmal je Feature",
|
||||
"prefs.validation.each_step": "Nach jedem Schritt",
|
||||
"prefs.validation.label": "Vollständige Kernel-Prüfung",
|
||||
"prefs.validation.off": "Aus (experimentell)",
|
||||
"prefs.validation_note": "Nach jedem Schritt: prüft jeden Sweep, jede Boolesche Operation und jede Rundung.\nEinmal je Feature: prüft das Ergebnis jedes Features einmal.\nAus (experimentell): keine Prüfung.",
|
||||
"prefs.value_boxes_in_view": "Werte neben ihren Griffen anzeigen",
|
||||
"prefs.value_boxes_in_view_tip": "Abstand beim Extrudieren, Radius beim Verrunden … als Feld in der Ansicht.",
|
||||
"prefs.value_boxes_in_view": "Werte in der Ansicht neben ihren Griffen zeigen",
|
||||
"prefs.value_boxes_in_view_tip": "Der Wert eines Befehls (Abstand, Radius …) in einem Feld neben seinem Griff, über dem Modell. Aus: Sie geben ihn im Befehlsfenster ein, das nach einer Auswahl die Tastatur erhält, und nichts verdeckt das Modell.",
|
||||
"prefs.view": "Ansichtsverhalten",
|
||||
"prefs.view_confirm_corner": "OK und Abbrechen in der Ansichtsecke",
|
||||
"prefs.view_confirm_corner_note": "Solange ein Dialog oder eine Skizze geöffnet ist, übernehmen ein Häkchen und ein Kreuz oben rechts in der Ansicht dieselben Funktionen wie „OK“ und „Abbrechen“ des Dialogs. Eine Skizze wird damit beendet oder verworfen. Der Ansichtsnavigator rückt darunter.",
|
||||
@@ -2737,23 +2497,12 @@
|
||||
"recovery.just_now": "gerade eben",
|
||||
"recovery.min_ago": "vor {count} Min.",
|
||||
"recovery.never_saved": "nie gespeichert",
|
||||
"refused.change": "{body} konnte nicht geändert werden.",
|
||||
"refused.cut": "{body} konnte nicht geschnitten werden.",
|
||||
"refused.face_curved": "Eine seiner gekrümmten Flächen kann nicht zum Modellieren verwendet werden.",
|
||||
"refused.face_flat": "Eine seiner ebenen Flächen kann nicht zum Modellieren verwendet werden.",
|
||||
"refused.face_freeform": "Eine seiner Freiformflächen kann nicht zum Modellieren verwendet werden.",
|
||||
"refused.face_rounded": "Eine seiner gerundeten Flächen kann nicht zum Modellieren verwendet werden.",
|
||||
"refused.faces_apart": "Seine Flächen treffen nicht genau genug aufeinander, um damit zu modellieren.",
|
||||
"refused.intersect": "Schnittmenge mit {body} nicht möglich.",
|
||||
"refused.join": "Konnte nicht mit {body} vereinigt werden.",
|
||||
"refused.repair": "Teil reparieren",
|
||||
"refused.show_face": "Fläche zeigen",
|
||||
"render.adaptive_absolute": "Absolute RGB-Fehleruntergrenze",
|
||||
"render.adaptive_coverage": "Zielwert für den Abdeckungsfehler",
|
||||
"render.adaptive_enable": "Samples mithilfe unabhängiger Pilot-Samples zuteilen",
|
||||
"render.adaptive_help": "Unabhängige Pilotsamples werden verworfen und bestimmen dann mindestens 16 Produktionssamples pro Pixel bis zum Wert Samples pro Pixel. CPU-Ressourcengrenzen umfassen beide Phasen. RGB- und Abdeckungsvarianz steuern die Verteilung; Pixel am Limit können verrauscht bleiben. Der Abschlussbericht zählt die tatsächlichen Samples.",
|
||||
"render.adaptive_help": "Nur GPU. Unabhängige Pilot-Samples werden verworfen. Anschließend werden pro Pixel 16 oder mehr Produktions-Samples zugeteilt, bis zu den Samples pro Pixel. Die Zuteilung richtet sich nach der RGB- und Abdeckungsvarianz; seltene Pfade können übersehen werden, und Pixel, die ihr Limit erreicht haben, können verrauscht bleiben. Der Abschlussbericht zählt den tatsächlichen Aufwand. Keine zertifizierte Garantie für ein bestimmtes Rauschniveau oder eine bestimmte Renderzeit.",
|
||||
"render.adaptive_minimum": "Minimale Produktions-Samples",
|
||||
"render.adaptive_pending": "Adaptive Verteilung mit unabhängigen Pilotsamples ist für CPU und GPU verfügbar. Bedingte Fresnel-Schichten erfordern die GPU. Die statistische Verteilung bestätigt keine Konvergenz.",
|
||||
"render.adaptive_pending": "Die adaptive Zuteilung mit unabhängigen GPU-Pilot-Samples und bedingte Fresnel-Schichten sind verfügbar. Zertifizierte Konvergenz, Garantien für seltene Ereignisse und adaptiver Lichttransport auf der CPU bleiben nicht verfügbar.",
|
||||
"render.adaptive_pilots": "Pilot-Samples pro Pixel",
|
||||
"render.adaptive_relative": "Zielwert für den relativen RGB-Fehler",
|
||||
"render.add_ground": "Add fitted ground to setup",
|
||||
@@ -2814,7 +2563,7 @@
|
||||
"render.appearance_editor.replace": "Gespeicherte Optik dieses Körpers ersetzen",
|
||||
"render.appearance_editor.revalidate": "Diesen Körper erneut prüfen",
|
||||
"render.appearance_editor.save": "Diese Optik speichern",
|
||||
"render.appearance_editor.save_reason": "Zum Speichern Glas an einem aktuellen CPU- oder GPU-Körper aktivieren. Für Klarlack „Klarlack in Aufnahme speichern“ verwenden. Lokale Bilddateien bleiben temporär.",
|
||||
"render.appearance_editor.save_reason": "Zum Speichern wird physikalisches Glas auf einem aktuellen GPU-Körper benötigt. Klarlack und lokale Bildtexturen bleiben vorübergehend.",
|
||||
"render.appearance_editor.saved": "Mit dieser Aufnahme gespeichert",
|
||||
"render.appearance_editor.saved_state": "Diese Optik ist mit dieser Aufnahme gespeichert.",
|
||||
"render.appearance_editor.scatter_reason": "Innere Streuung benötigt einen geschlossenen Körper.",
|
||||
@@ -2910,7 +2659,7 @@
|
||||
"render.children.copy_request": "Anfrage-JSON kopieren",
|
||||
"render.children.coverage_error": "Abdeckungsfehler",
|
||||
"render.children.cpu": "CPU-Raytracing",
|
||||
"render.children.denoise": "Bildrauschen reduzieren",
|
||||
"render.children.denoise": "Rauschen im fertigen Bild reduzieren",
|
||||
"render.children.depth": "Pfadtiefe",
|
||||
"render.children.distortion": "Verzeichnung",
|
||||
"render.children.edge_samples": "Kanten-Samples",
|
||||
@@ -3042,7 +2791,7 @@
|
||||
"render.cpu_point_help": "Bis zu acht Punktlichter, zusätzlich zur gewählten Richtungsbeleuchtung. Abstände in mm; der Mindestabstand darf den Referenzabstand nicht überschreiten.",
|
||||
"render.cpu_primary_samples": "Kanten-Samples pro Pixel",
|
||||
"render.cpu_progressive": "CPU progressiv",
|
||||
"render.cpu_progressive_help": "Neue CPU-Kamerasamples sammeln und jeden vollständigen Durchgang zeigen. Ausgewählte Reflexionen verwenden einen Versuch pro Kamerasample; zertifiziertes Glas und planare Anisotropie behalten ihre bisherigen Grenzen. Adaptive Pilot- und Produktionssamples teilen sich das CPU-Profilbudget.",
|
||||
"render.cpu_progressive_help": "Den vorhandenen CPU-Integrator in Durchgängen mit je einem Primär-Sample akkumulieren. Ausgewählte Reflexionen erfordern ein Sample; Flächenlichter, Dielektrika und Anisotropie erfordern „Einzelbild“.",
|
||||
"render.cpu_progressive_limits": "Höchstens 1.048.576 Pixel und 16.777.216 Auswertungen von Pixel × Sample. Das Ändern der Samples passt die Bildgröße nie automatisch an.",
|
||||
"render.cpu_progressive_seed": "Startwert für das CPU-Sampling",
|
||||
"render.cpu_progressive_spp": "Samples pro Pixel",
|
||||
@@ -3218,7 +2967,7 @@
|
||||
"render.export_display": "Export: Anzeige-PNG",
|
||||
"render.export_image_label": "Bild exportieren",
|
||||
"render.export_linear": "Export: lineares Roh-EXR",
|
||||
"render.exr_pending": "CPU-Raytracing und GPU-Pathtracing können rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
|
||||
"render.exr_pending": "Die GPU kann rohe lineare EXR-Dateien behalten. Kompression, mehrschichtige Bilder und zusätzliche Renderpässe bleiben nicht verfügbar.",
|
||||
"render.film_grain": "Fotografisches Korn",
|
||||
"render.fit": "Produkt einpassen",
|
||||
"render.fixed_camera": "Kamera",
|
||||
@@ -3258,11 +3007,9 @@
|
||||
"render.frame_wide": "16:9 · Breitbild",
|
||||
"render.gpu_depth": "Maximale Pfadtiefe",
|
||||
"render.gpu_options_help": "Samples, Tiefe, Startwert und Belichtung werden mit diesem Setup gespeichert. Es wird nur die aktive Bildmethode gespeichert.",
|
||||
"render.gpu_path_crashed": "SolidCraft wurde beim letzten Einrichten auf diesem Grafikadapter beendet, wahrscheinlich im Grafiktreiber, und richtet ihn deshalb nicht von selbst erneut ein. Verwenden Sie stattdessen Raytracing auf der CPU oder versuchen Sie es erneut (nach einem Treiberupdate wird es ebenfalls erneut versucht).",
|
||||
"render.gpu_path_failed": "GPU-Pathtracer nicht verfügbar",
|
||||
"render.gpu_path_first_setup": "Das erste Setup auf dieser GPU kann mehrere Minuten dauern; SolidCraft bleibt währenddessen bedienbar. Spätere Starts verwenden die kompilierten Shader erneut, sofern der Treiber sie behält.",
|
||||
"render.gpu_path_preparing": "GPU-Pathtracer: Shader werden kompiliert…",
|
||||
"render.gpu_path_retry": "Erneut versuchen",
|
||||
"render.gpu_seed": "Zufallsstartwert",
|
||||
"render.gpu_unavailable": "GPU-Pathtracing ist auf diesem Gerät nicht bereit.",
|
||||
"render.ground_apply": "Boden anpassen und speichern",
|
||||
@@ -3581,7 +3328,7 @@
|
||||
"render.method_cpu": "CPU-Raytracing",
|
||||
"render.method_gpu": "GPU-Pathtracing",
|
||||
"render.method_limit_raster": "Die Rasterung verwendet die feste undurchsichtige Studiovorschau. Wählen Sie ausdrücklich eine Raytracing-Methode für die unterstützten optischen Einstellungen.",
|
||||
"render.method_raster": "CPU-Rasterung (schematisch)",
|
||||
"render.method_raster": "CPU-Rasterung",
|
||||
"render.navigation_help": "Die Kamera bewegt sich wie im Konstruieren (Einstellungen › Navigation). Setup speichern übernimmt die aktuelle Kamera.",
|
||||
"render.nested_enable": "Zertifizierte verschachtelte Dielektrika",
|
||||
"render.nested_help": "Bis zu 16 umschließende Schichten; jedes übergeordnete Element muss konvex und jedes untergeordnete strikt darin enthalten sein. Überschneidungen, sich berührende Flächen, dünne Schichten und die Transmission gerader Schattenstrahlen werden abgelehnt. Der Zustand „Kamera im Inneren“ folgt der zertifizierten Hierarchie. Erzeugt keine Flüssigkeitsoberfläche und verschmilzt keine sich berührenden CAD-Grenzflächen.",
|
||||
@@ -3609,7 +3356,7 @@
|
||||
"render.output_exr_unavailable": "EXR-Ausgabeeinstellungen sind hier nicht verfügbar. Linearer EXR-Export erfordert ein fertiges GPU-Bild mit gespeichertem linearem Original.",
|
||||
"render.output_fit": "Einpassen",
|
||||
"render.output_format": "Format",
|
||||
"render.output_format_status": "PNG enthält das exakte fertige Anzeigebild. Optionales CPU/GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Anzeigefiltern. Der Export verwendet nie den Vorschaucache oder einen anderen Auftrag.",
|
||||
"render.output_format_status": "PNG enthält das exakte fertige Anzeigebild. Optionales GPU-EXR bewahrt rohe lineare FLOAT32-Strahldichte vor Filtern. Export ersetzt nie durch Vorschaucache oder anderen Auftrag.",
|
||||
"render.output_grading": "Farbkorrektur",
|
||||
"render.output_image": "Bild",
|
||||
"render.output_largest": "{width} × {height} max.",
|
||||
@@ -3694,7 +3441,7 @@
|
||||
"render.quality_custom": "Benutzerdefiniert",
|
||||
"render.quality_draft": "Entwurf",
|
||||
"render.quality_high": "Hoch",
|
||||
"render.quality_projection_help": "Diese Prüfung ändert nur Auflösung, Abtastbudget, Pfadtiefe, Startwert und Rauschreduzierung. Andere Einstellungen bleiben erhalten; das Budget garantiert keine Konvergenz.",
|
||||
"render.quality_projection_help": "Q-Vorgaben enthalten künftige Anforderungen. Diese Aktion projiziert nur ein unterstütztes PNG-Budget mit festen Samples und lässt adaptives Stoppen, rohe lineare Ausgabe und unabhängige Transmissionslimits ausdrücklich aus.",
|
||||
"render.quality_review": "Sampling-Budget prüfen",
|
||||
"render.queue_pending": "In dieser Oberfläche gibt es keine automatische Produktionswarteschlange und keinen Animations-Renderer. Fertige Einzelbilder bleiben separat exportierbar.",
|
||||
"render.radial_assign": "Erscheinungsbild zuweisen",
|
||||
@@ -3725,7 +3472,7 @@
|
||||
"render.result_navigation": "Verschieben und Zoomen folgen den Navigationseinstellungen. Leertaste + Ziehen verschiebt.",
|
||||
"render.result_open": "Das fertige Bild in der Hauptansicht öffnen.",
|
||||
"render.result_preview": "Live-Vorschau",
|
||||
"render.result_provisional": "Vorläufiges Bild",
|
||||
"render.result_provisional": "Vorläufiges CPU-Bild",
|
||||
"render.result_provisional_help": "Dieser Zwischenstand ist nicht exportierbar. Der Export behält das letzte fertige Ergebnis; ein Abbruch kehrt zu diesem Ergebnis zurück.",
|
||||
"render.result_reduced_display": "Anzeigevorschau auf die Geräte-/Cachegrenze reduziert. PNG und Export behalten die volle Auflösung; native 100%-Ansicht ist hier nicht verfügbar.",
|
||||
"render.result_retained": "Das zuletzt fertiggestellte Bild wird angezeigt; ein neueres Rendering hat es noch nicht ersetzt.",
|
||||
@@ -3889,7 +3636,7 @@
|
||||
"render.stage_panels.prepare_plan": "Erstellungsplan vorbereiten",
|
||||
"render.stage_panels.projects": "Projiziert",
|
||||
"render.stage_panels.quality_ack": "Ich bestätige das feste Budget und seine Auslassungen",
|
||||
"render.stage_panels.quality_omissions": "Dieses Budget legt keine adaptiven Fehlerziele, eigenen Transmissionsgrenzen, Russian-Roulette-Regeln oder Speicherung des linearen Masters fest und garantiert keine Konvergenz.",
|
||||
"render.stage_panels.quality_omissions": "Adaptives Stoppen und Fehlerziele, separate Transmissionstiefe, Russian-Roulette-Regeln, lineare Rohausgabe, EXR-Master und Konvergenznachweis.",
|
||||
"render.stage_panels.reference": "Referenz",
|
||||
"render.stage_panels.references": "Referenzen",
|
||||
"render.stage_panels.remove_optics": "Optik entfernen",
|
||||
@@ -3933,7 +3680,7 @@
|
||||
"render.stage_panels.subtitle_environment": "Originale HDR-Umgebung · gesamte Szene",
|
||||
"render.stage_panels.subtitle_maps": "Erstellte räumliche Texturen · temporäre Vorschau",
|
||||
"render.stage_panels.subtitle_physical": "Schreibgeschützter Plan · kein Material erstellt oder zugewiesen",
|
||||
"render.stage_panels.subtitle_quality": "GPU-Abtastbudget · nur aufgeführte Einstellungen",
|
||||
"render.stage_panels.subtitle_quality": "Festes GPU-Budget · zukünftige Anforderungen ausgelassen",
|
||||
"render.stage_panels.subtitle_rig": "Erstellter Lichtaufbau · Lichtquellen und passive Flächen",
|
||||
"render.stage_panels.subtitle_saved_maps": "Erstellte Texturen · mit dieser Aufnahme gespeichert",
|
||||
"render.stage_panels.subtitle_stage": "Physischer Boden und Bühne · an das Produkt angepasst",
|
||||
@@ -3958,7 +3705,7 @@
|
||||
"render.studio_area_help": "Bis zu vier einseitige Rechtecke. Orthogonale Halbkanten U und V bestimmen die Größe; U × V zeigt zur beleuchteten Seite. Die Koordinaten sind Weltmillimeter.",
|
||||
"render.studio_center": "Mittelpunkt X, Y, Z (mm)",
|
||||
"render.studio_denoise": "Rauschen im fertigen Bild reduzieren",
|
||||
"render.studio_denoise_help": "Kantenerhaltender Filter für progressive und fertige Bilder; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
|
||||
"render.studio_denoise_help": "Abschließender kantenerhaltender Filter; feine Details können weicher werden. Glas und scharfe spiegelnde Oberflächen umgehen den Filter.",
|
||||
"render.studio_direction": "Richtung zur Sonne (X, Y, Z)",
|
||||
"render.studio_environment": "Umgebung",
|
||||
"render.studio_environment_bounds": "Die Umgebungsdrehung muss endlich und innerhalb von ±1.000.000 Radiant liegen, die Intensität zwischen 0 und 100.",
|
||||
@@ -4107,11 +3854,11 @@
|
||||
"select two components": "zwei Komponenten auswählen",
|
||||
"set by the sketch's lines": "durch die Skizzenlinien festgelegt",
|
||||
"settings.display_quality_note": "Feinere Stufen zeigen gekrümmte Flächen und Kanten glatter und brauchen länger zum Vernetzen. Nur die Ansicht ändert sich, nie das Design oder Exporte.",
|
||||
"settings.double_click_components": "Komponente zum Aktivieren doppelklicken",
|
||||
"settings.double_click_components": "Doppelklick auf eine Komponente aktiviert sie oder bearbeitet eine verknüpfte an Ort und Stelle",
|
||||
"settings.frame_rate_details_note": "Fügt ein Diagramm der Bildzeiten der letzten Bilder hinzu, darüber die gezeichneten Dreiecke, dazu ein Histogramm der Bildzeiten und was die GPU gezeichnet hat. Ein Klick auf die Bildrate in der Ansicht blendet sie ein oder aus.",
|
||||
"settings.frame_rate_note": "Zeigt die Bilder pro Sekunde unten rechts in der Ansicht.",
|
||||
"settings.insert_lists_open_designs": "Konstruktion einfügen listet offene Konstruktionen zuerst",
|
||||
"settings.measure_axis_distances": "Achsabstände beim Messen",
|
||||
"settings.insert_lists_open_designs": "Konstruktion einfügen zeigt zuerst die in anderen Tabs geöffneten Konstruktionen",
|
||||
"settings.measure_axis_distances": "X/Y/Z-Maße anzeigen",
|
||||
"settings.measure_axis_distances_help": "Bei geöffneter Messung die Abstände entlang der Weltachsen zusätzlich zum Gesamtabstand anzeigen.",
|
||||
"settings.msaa_applies_on_restart": "Die Kantenglättung wird beim nächsten Start von SolidCraft übernommen",
|
||||
"settings.msaa_reset": "{setting}: {requested}× wird von diesem Grafikgerät nicht unterstützt; jetzt {reset}× (unterstützt: {supported}).",
|
||||
@@ -4121,7 +3868,7 @@
|
||||
"settings.reset_intro": "Diese Einstellungen funktionieren auf diesem Computer nicht und würden den Start von SolidCraft verhindern. Sie wurden daher geändert:",
|
||||
"settings.reset_title": "Einstellungen wurden geändert",
|
||||
"settings.supersampling_note": "Zeichnet die Ansicht in höherer Auflösung und rechnet sie herunter: glattere Kanten, Linien und Raster auf Bildschirmen unter 4K. Kostet GPU-Zeit: etwa 2,25-mal so viele Pixel bei 1,5× und 4-mal bei 2×.",
|
||||
"settings.update_changed_links": "Geänderte Verknüpfungen bei Rückkehr zum Tab aktualisieren",
|
||||
"settings.update_changed_links": "Geänderte verknüpfte Designs aktualisieren, wenn ein Tab wieder angezeigt wird",
|
||||
"settings.window_system_details": "Details",
|
||||
"settings.window_system_note_now": "Wird nach einem Neustart wirksam. Aktuell: {now}.",
|
||||
"settings.window_system_wayland_limited": "Einige Funktionen sind unter nativem Wayland eingeschränkt.",
|
||||
@@ -4217,19 +3964,11 @@
|
||||
"the section's profiles must be in one sketch": "die Profile des Querschnitts müssen in einer Skizze liegen",
|
||||
"the two origins are on the same component": "die beiden Ursprünge liegen auf derselben Komponente",
|
||||
"thread.automatic_size": "Automatische Größe",
|
||||
"thread.chamfer_depth_needed": "für diese Form erforderlich",
|
||||
"thread.designation": "Bezeichnung",
|
||||
"thread.drill_range": "Der Bohrer des Gewindebohrerherstellers ist {drill} mm. Der genormte Kerndurchmesser des Innengewindes ist {minor} mm. Ein Bohrer von {low} bis {high} mm wird angenommen.",
|
||||
"thread.drill_range_custom": "Der Bohrer ist der Nenndurchmesser minus Steigung, {drill} mm. Der genormte Kerndurchmesser des Innengewindes ist {minor} mm. Ein Bohrer von {low} bis {high} mm wird angenommen.",
|
||||
"thread.drill_range_taper": "Der Bohrer des Gewindebohrerherstellers ist {drill} mm. Der genormte Kerndurchmesser am Gewindeende ist {minor} mm. Ein Bohrer von {low} bis {high} mm wird angenommen.",
|
||||
"thread.end_square": "Rechtwinklig",
|
||||
"thread.fade_short": "1,25 P (ISO 3508)",
|
||||
"thread.fade_standard": "2,5 P (ISO 3508)",
|
||||
"thread.form_bspp": "Rohrgewinde G (ISO 228-1)",
|
||||
"thread.form_bspt": "Kegeliges Rohrgewinde R, Rc (ISO 7-1)",
|
||||
"thread.form_npt": "Kegeliges Rohrgewinde NPT (ASME B1.20.1)",
|
||||
"thread.form_rp": "Zylindrisches Innenrohrgewinde Rp (ISO 7-1)",
|
||||
"thread.direction": "Gewinderichtung",
|
||||
"thread.iso_metric": "ISO metrisch",
|
||||
"thread.left_hand": "Linksgewinde",
|
||||
"thread.right_hand": "Rechtsgewinde",
|
||||
"thread.select_size": "Größe wählen",
|
||||
"timeline.delete_after_marker_one": "1 Merkmal nach der Verlaufsmarkierung löschen",
|
||||
"timeline.delete_after_marker_other": "{count} Merkmale nach der Verlaufsmarkierung löschen",
|
||||
@@ -4481,7 +4220,6 @@
|
||||
"ui.agent_chat_no_images": "Dieser Agent nimmt keine Bilder an",
|
||||
"ui.agent_chat_no_picture": "In der Zwischenablage befindet sich kein Bild",
|
||||
"ui.agent_chat_no_tool_call": "Das Modell hat die Werkzeuge von SolidCraft nicht verwendet, daher wurde das Design nicht geändert. Wählen Sie ein Modell, das Werkzeugaufrufe unterstützt; siehe „Lokale Modelle“ im Leitfaden zum Agenten-Chat.",
|
||||
"ui.agent_chat_node_too_old": "Node.js {version} ist für diesen Agenten zu alt. Installieren Sie Node.js 20.10 oder neuer und klicken Sie dann auf Aktualisieren.",
|
||||
"ui.agent_chat_none_found": "Kein Coding-Agent gefunden. Installieren Sie einen davon und klicken Sie dann auf Aktualisieren:",
|
||||
"ui.agent_chat_none_found_short": "Kein Agent gefunden",
|
||||
"ui.agent_chat_not_installed": "Nicht installiert",
|
||||
@@ -4522,7 +4260,7 @@
|
||||
"ui.agent_chat_send_now_tip": "Den aktuellen Schritt des Agenten stoppen und dies als Nächstes senden (Strg+Eingabe)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft kommuniziert mit einem auf diesem Computer installierten Programmieragenten. Wählen Sie einen unter Einstellungen › KI aus.",
|
||||
"ui.agent_chat_setup_title": "Assistenten einrichten",
|
||||
"ui.agent_chat_side": "Chat andocken",
|
||||
"ui.agent_chat_side": "Seite des Agenten-Chats",
|
||||
"ui.agent_chat_signed_in": "angemeldet",
|
||||
"ui.agent_chat_state_auth": "Anmeldung nötig",
|
||||
"ui.agent_chat_state_busy": "Arbeitet…",
|
||||
@@ -4541,7 +4279,7 @@
|
||||
"ui.agent_chat_undone": "(rückgängig gemacht)",
|
||||
"ui.agent_chat_view_attached": "Ansicht angehängt",
|
||||
"ui.agent_chat_waiting": "Wartet auf Sie",
|
||||
"ui.agent_cursor": "Agentenzeiger",
|
||||
"ui.agent_cursor": "Agent-Cursor",
|
||||
"ui.agent_cursor_speed": "Agent-Cursor-Geschwindigkeit",
|
||||
"ui.agent_off_thread": "Fenster bedienbar halten, während Agenten Befehle ausführen",
|
||||
"ui.agent_off_thread_note": "Langsame Befehle eines Agenten (aus dem Assistentenbereich oder von einem MCP-Client) werden neben dem Fenster berechnet: Sie können die Ansicht weiter drehen und prüfen und einen Befehl über den Chip in der Ansicht stoppen. Aus: Jeder läuft im Thread des Fensters, und das Fenster wartet darauf.",
|
||||
@@ -4587,14 +4325,14 @@
|
||||
"ui.ai_key_note": "Im Schlüsselbund des Systems gespeichert, nie in den Einstellungen von SolidCraft. Für einen lokalen Server leer lassen.",
|
||||
"ui.ai_keychain_error": "Der Schlüsselbund des Systems hat abgelehnt: {error}",
|
||||
"ui.ai_kind": "Art",
|
||||
"ui.ai_mode_all": "Alles ausführen",
|
||||
"ui.ai_mode_all": "Alles erlauben",
|
||||
"ui.ai_mode_all_note": "Nichts fragt vorher. Absturzberichte bleiben tabu, und die Ordnergrenzen gelten weiter.",
|
||||
"ui.ai_mode_all_warning": "Der Agent kann Ihr Design ohne Nachfrage speichern, überschreiben, exportieren und ersetzen. Nutzen Sie das nur mit einem Agenten, dem Sie vertrauen, und mit gespeicherten Designs.",
|
||||
"ui.ai_mode_ask": "Zuerst fragen",
|
||||
"ui.ai_mode_ask": "Jedes Mal fragen",
|
||||
"ui.ai_mode_ask_note": "Jede Änderung am Design und jede Dateiaktion wartet auf Ihr Erlauben.",
|
||||
"ui.ai_mode_edits": "Änderungen vornehmen",
|
||||
"ui.ai_mode_edits": "Modelländerungen automatisch erlauben",
|
||||
"ui.ai_mode_edits_note": "Modellierbefehle laufen ohne Nachfrage; Speichern, Öffnen, Exportieren und andere Datei- oder destruktive Aktionen fragen weiterhin.",
|
||||
"ui.ai_mode_global": "Wie oben",
|
||||
"ui.ai_mode_global": "Wie oben eingestellt",
|
||||
"ui.ai_model": "Modell",
|
||||
"ui.ai_model_hint": "qwen3:14b",
|
||||
"ui.ai_modes_scope": "Diese Einstellungen gelten für die Werkzeuge von SolidCraft. Die eigenen Werkzeuge eines Agenten (seine Shell, seine Dateiänderungen) folgen seinem eigenen Modus, den Sie im Kopf des Chat-Panels wählen, wenn der Agent Modi hat.",
|
||||
@@ -4620,7 +4358,7 @@
|
||||
"ui.ai_save": "Speichern",
|
||||
"ui.ai_settings": "KI-Einstellungen…",
|
||||
"ui.ai_show_features": "KI-Funktionen anzeigen",
|
||||
"ui.ai_show_features_note": "Aus blendet den Assistenten überall aus und startet nichts. Alles darunter wird gedimmt.\n\nDer Agenten-Chat: ein Panel, in dem ein Coding-Agent, den Sie schon nutzen, oder ein Modell-Endpunkt Ihrer Wahl am geöffneten Design arbeitet. Aus: keine KI-Schaltfläche, kein Kürzel, kein Menüeintrag, kein Panel, und nichts wird gesucht oder gestartet.",
|
||||
"ui.ai_show_features_note": "Der Agenten-Chat: ein Panel, in dem ein Coding-Agent, den Sie schon nutzen, oder ein Modell-Endpunkt Ihrer Wahl am geöffneten Design arbeitet. Aus: keine KI-Schaltfläche, kein Kürzel, kein Menüeintrag, kein Panel, und nichts wird gesucht oder gestartet.",
|
||||
"ui.ai_switch_tip": "Den Agenten für dieses Gespräch wechseln",
|
||||
"ui.ai_test": "Testen",
|
||||
"ui.ai_test_acp_ok": "Antwortet: {agent}",
|
||||
@@ -4654,7 +4392,7 @@
|
||||
"ui.ai_tool_sum_then": "{first}, dann {next}",
|
||||
"ui.ai_tool_unfold": "Klicken, um das Ergebnis zu zeigen",
|
||||
"ui.ai_tool_waiting": "wartet",
|
||||
"ui.ai_toolbar_button": "Assistent-Schaltfläche in der Anwendungsleiste",
|
||||
"ui.ai_toolbar_button": "Schaltfläche in der Werkzeugleiste anzeigen",
|
||||
"ui.ai_toolbar_button_note": "Die KI-Schaltfläche am rechten Ende der Werkzeugleiste. Strg+Umschalt+A und das Ansicht-Menü öffnen das Panel weiterhin.",
|
||||
"ui.ai_undo_tip": "Alles, was der Agent für diese Nachricht geändert hat, in einem Schritt rückgängig machen",
|
||||
"ui.ai_url_hint": "https://example.com/v1",
|
||||
@@ -4731,10 +4469,6 @@
|
||||
"ui.colour_vision_sample": "Beispiel",
|
||||
"ui.colour_vision_tritan": "Tritan",
|
||||
"ui.colour_vision_tritan_tip": "Bei schwachem oder fehlendem Blausinn",
|
||||
"ui.comb_length": "Kammlänge",
|
||||
"ui.comb_length_tip": "Wie lang die Zähne sind: ein Vielfaches der automatischen Länge, bei der der längste Zahn ein Fünftel der Größe der gezeigten Kurven ist.",
|
||||
"ui.comb_teeth": "Zähne",
|
||||
"ui.comb_teeth_tip": "Wie viele Zähne jeder Kamm hat, gleichmäßig entlang der Kurve verteilt. Mehr Zähne zeigen genauer, wie sich die Krümmung ändert.",
|
||||
"ui.combine": "Kombinieren",
|
||||
"ui.coming_later": "bald verfügbar",
|
||||
"ui.command_done": "{command} fertig",
|
||||
@@ -4759,24 +4493,11 @@
|
||||
"ui.construction.ribbon_label": "Hilfslinien",
|
||||
"ui.construction.side_hint": "Konstruktionslinie: die Seite anklicken, zu der die Linie läuft; Rechtsklick beginnt neu",
|
||||
"ui.context_menu": "Kontextmenü",
|
||||
"ui.continuity_crease": "Knick",
|
||||
"ui.continuity_curvature": "Krümmung",
|
||||
"ui.continuity_curvature_change": "Krümmungsänderung",
|
||||
"ui.continuity_curvature_change_tip": "Krümmungsänderung (G3): Auch die Krümmung ändert sich an der Naht glatt.",
|
||||
"ui.continuity_curvature_tip": "Krümmungsstetig (G2): Spiegelungen laufen ohne Knick hindurch.",
|
||||
"ui.continuity_g0": "G0 Knick",
|
||||
"ui.continuity_g1": "G1 Tangente",
|
||||
"ui.continuity_g2": "G2 Krümmung",
|
||||
"ui.continuity_gap": "Lücke",
|
||||
"ui.continuity_position": "Position",
|
||||
"ui.continuity_position_tip": "Position (G0): Die Flächen treffen sich, mit einem Knick an der Naht.",
|
||||
"ui.continuity_smooth_curvature_change": "Glatte Krümmungsänderung",
|
||||
"ui.continuity_smooth_curvature_change_tip": "Glatte Krümmungsänderung (G4): Selbst die Art, wie sich die Krümmung ändert, läuft an der Naht glatt weiter.",
|
||||
"ui.continuity_tangent": "Tangential",
|
||||
"ui.continuity_tangent_tip": "Tangential (G1): kein Knick, aber die Krümmung springt an der Naht.",
|
||||
"ui.copy": "Kopieren",
|
||||
"ui.copy_details": "Details kopieren",
|
||||
"ui.copy_details_tip": "Die vollständige Meldung kopieren, um sie nachzuschlagen oder zu melden.",
|
||||
"ui.corner": "Ecke",
|
||||
"ui.corners": "Ecken",
|
||||
"ui.cosmetic_thread_markings": "Kosmetische Gewindemarkierungen",
|
||||
@@ -4799,7 +4520,6 @@
|
||||
"ui.default_modelling_orientation": "Standard-Modellierungsorientierung",
|
||||
"ui.delete": "Löschen",
|
||||
"ui.delete_select_first": "Wählen Sie zuerst aus, was gelöscht werden soll: Features, Körper, Komponenten oder Skizzengeometrie",
|
||||
"ui.dependents_select_first": "Wählen Sie zuerst aus, wessen Abhängige aufgelistet werden sollen",
|
||||
"ui.depth": "Tiefe",
|
||||
"ui.design": "Design",
|
||||
"ui.design_properties.author": "Autor",
|
||||
@@ -4895,7 +4615,6 @@
|
||||
"ui.edit_sketch_empty": "Keine Skizzen zum Bearbeiten",
|
||||
"ui.ellipse": "Ellipse",
|
||||
"ui.emboss_wrap_note": "Senkrechte Linienprofile, die einen unterstützten Zylinder berühren, werden automatisch aufgewickelt. Eine Wand auswählen, um ein Ziel festzulegen.",
|
||||
"ui.end": "Ende",
|
||||
"ui.end_radius": "Endradius",
|
||||
"ui.end_scale": "Endskalierung",
|
||||
"ui.entities": "Elemente",
|
||||
@@ -4923,45 +4642,13 @@
|
||||
"ui.extract_done": "Die Komponente ist jetzt ein eigenes Design und hier verknüpft.",
|
||||
"ui.extrude": "Extrudieren",
|
||||
"ui.extrude_auto_to_object": "Bis zu einem gewählten Ziel extrudieren",
|
||||
"ui.extrude_auto_to_object_tip": "Eine Fläche, ein Scheitelpunkt oder eine parallele Ebene stellt die Ausdehnung auf Bis Objekt um.",
|
||||
"ui.extrude_auto_to_object_tip": "Wenn in „Extrudieren“ ein Profil gewählt ist, schaltet das Wählen einer Fläche, eines Eckpunkts oder einer parallelen Ebene außerhalb der Profilebene die Ausdehnung auf „Bis Objekt“ um",
|
||||
"ui.factor": "Faktor",
|
||||
"ui.fade": "Auslauf",
|
||||
"ui.fade_length": "Auslauflänge",
|
||||
"ui.feature_history": "Feature-Verlauf",
|
||||
"ui.feature_options": "Funktionsoptionen",
|
||||
"ui.features": "Funktionen",
|
||||
"ui.file": "Datei",
|
||||
"ui.fillet": "Vollrundung",
|
||||
"ui.fillet_corner_ball": "Kugel",
|
||||
"ui.fillet_corner_ball_tip": "Ein Stück der Kugel des rollenden Balls, tangential (G1) zu jeder Rundung.",
|
||||
"ui.fillet_corner_curvature_tip": "Krümmung (G2): ein glattes Flächenstück. Die Ecke wird entlang jeder Kante um den Rücksprung zurückgesetzt ({factor} × der Radius, sofern Sie keinen eingeben) und mit einem Flächenstück gefüllt, sodass Spiegelungen ohne Knick hindurchlaufen.",
|
||||
"ui.fillet_corner_needs_constant": "Ecken mit glattem Flächenstück (G2) brauchen einen konstanten Radius und exakte Radiusanpassung.",
|
||||
"ui.fillet_corner_patch": "Glattes Flächenstück",
|
||||
"ui.fillet_corner_patch_tip": "Krümmungsstetig (G2): Die Ecke wird entlang jeder Kante zurückgesetzt und mit einem Flächenstück gefüllt, sodass Spiegelungen ohne Knick hindurchlaufen.",
|
||||
"ui.fillet_corner_tangent_tip": "Tangential (G1): eine Kugelecke, ein Stück der Kugel des rollenden Balls, tangential zu jeder Rundung.",
|
||||
"ui.fillet_error_corner": "Die glatte Ecke ließ sich hier nicht bauen. Versuchen Sie einen anderen Rücksprung oder tangentiale Ecken.",
|
||||
"ui.fillet_error_failed": "Diese Rundung ließ sich an diesen Kanten nicht bauen. Versuchen Sie einen anderen Radius oder runden Sie weniger Kanten auf einmal.",
|
||||
"ui.fillet_error_setback_short": "Der Rücksprung ist für das Eckstück zu kurz: Es lässt sich ab {least} bauen. Versuchen Sie einen längeren Rücksprung.",
|
||||
"ui.fillet_error_too_large": "Der Radius ist zu groß für die Flächen neben diesen Kanten. Versuchen Sie einen kleineren Radius oder stellen Sie „Flächengrenzen“ auf „Größter Radius“.",
|
||||
"ui.fillet_fit": "Flächengrenzen",
|
||||
"ui.fillet_fit_exact": "Exakter Radius",
|
||||
"ui.fillet_fit_needs_constant": "Radiusanpassung braucht einen konstanten Radius.",
|
||||
"ui.fillet_fit_reduce": "Größter Radius",
|
||||
"ui.fillet_fit_taper": "Verjüngter Radius",
|
||||
"ui.fillet_fit_tip": "Wenn der Radius größer ist, als die Flächen neben einer Kante zulassen.\nExakter Radius: der angegebene Radius, oder keine Rundung und eine Meldung mit dem Grund.\nGrößter Radius: jede Kante mit dem größten passenden Radius gerundet.\nVerjüngter Radius: der Radius bleibt, die Rundung wird nur dort verjüngt, wo die Flächen enden.",
|
||||
"ui.fillet_options_tip": "Tangentenkette: auch die Kanten runden, die glatt an die gewählten anschließen.\nVorschau: das Ergebnis beim Bearbeiten in der Ansicht zeigen.",
|
||||
"ui.fillet_setback": "Rücksprung",
|
||||
"ui.fillet_setback_hint": "{factor} × Radius",
|
||||
"ui.fillet_setback_least": "Mehr als {least}",
|
||||
"ui.fillet_setback_tip": "Wo das glatte Eckflächenstück beginnt: wie weit von der Ecke entlang jeder Kante. Standard ist {factor} × der Radius. Er muss größer sein als der kleinste Rücksprung, wo sich die Rundungen ohne ihn treffen: der Radius, wo die Flächen rechtwinklig zueinander stehen.",
|
||||
"ui.fillet_smooth_radius": "Radius glatt ändern",
|
||||
"ui.fillet_turn": "Wendungen",
|
||||
"ui.fillet_turn_mitre": "Gehrung",
|
||||
"ui.fillet_turn_swept": "Geschwenkt",
|
||||
"ui.fillet_turn_tip": "Wo zwei Rundungen um eine scharfe Eckkante zwischen ihnen herumlaufen, etwa die Rundungen am Fuß eines Zapfens an seinen senkrechten Kanten.\nGehrung: Die Rundungen laufen weiter, bis sie sich in einer Gehrung treffen.\nGeschwenkt: Die Rundungen enden an der Eckkante und schwenken um sie herum; die Ecke ist ein Torus, tangential zu beiden. Nur für Rundungen mit einem Radius um eine gerade, konvexe Eckkante.",
|
||||
"ui.fillet_type_choices_tip": "Konstanter Radius: ein Radius entlang der ganzen Kante.\nSehnenlänge: die Breite der Rundung bleibt gleich, in welchem Winkel die Flächen auch aufeinandertreffen.\nVariabler Radius: ein Radius an jedem Ende und weitere entlang der Kante, wenn Sie sie hinzufügen.\nKrümmungsstetig: ein Querschnitt ohne Knick in den Spiegelungen, wo er auf ebene Flächen trifft (G2).\nSpline: der Radius entlang der Kante folgt Steuerradien, die Sie festlegen.",
|
||||
"ui.fillet_type_curvature": "Krümmungsstetig",
|
||||
"ui.fillet_type_tip": "Der Querschnitt über die Kante. Krümmungsstetig: ein krümmungsstetiger Querschnitt (G2) zwischen ebenen Flächen, sodass Spiegelungen ohne Knick von den Flächen in die Rundung laufen.",
|
||||
"ui.filter_commands_or_keys": "Befehle oder Tasten filtern",
|
||||
"ui.findInTimeline": "In der Zeitleiste suchen",
|
||||
"ui.findInWindow": "Im Fenster suchen",
|
||||
@@ -5004,7 +4691,6 @@
|
||||
"ui.heading_construct": "KONSTRUKTION",
|
||||
"ui.heading_create": "ERSTELLEN",
|
||||
"ui.heading_create_sketch": "SKIZZE ERSTELLEN",
|
||||
"ui.heading_custom_view_navigator": "EIGENER ANSICHTSNAVIGATOR",
|
||||
"ui.heading_cylinder": "ZYLINDER",
|
||||
"ui.heading_design": "DESIGN",
|
||||
"ui.heading_draft": "ABZIEHEN",
|
||||
@@ -5071,7 +4757,7 @@
|
||||
"ui.hideOrigin": "Ursprung ausblenden",
|
||||
"ui.hidePlanes": "Ebenen ausblenden",
|
||||
"ui.hideProfile": "Profil ausblenden",
|
||||
"ui.hide_origin_by_default": "Ursprungsordner in neuen Konstruktionen ausblenden",
|
||||
"ui.hide_origin_by_default": "Ursprung standardmäßig ausblenden",
|
||||
"ui.hide_origin_note": "Neue Designs beginnen mit ausgeblendetem Ordner „Ursprung“. Ein Design behält seine eigene Ursprungseinstellung, sobald Sie ihn ein- oder ausblenden.",
|
||||
"ui.history_back": "Einen Schritt zurück",
|
||||
"ui.history_below_browser": "Unter dem Browser",
|
||||
@@ -5211,7 +4897,6 @@
|
||||
"ui.part_size_needs_a_part": "Wählen Sie ein eingefügtes Standardbauteil (Schraube, Mutter, Scheibe oder Stift) aus, um seine Größe zu ändern. Einen Körper vergrößern oder verkleinern Sie mit Skalieren.",
|
||||
"ui.part_size_shared": "Baut das Teil neu auf und ersetzt alle hinzugefügten Merkmale. Alle Vorkommen dieser Komponente werden aktualisiert.",
|
||||
"ui.paste": "Einfügen",
|
||||
"ui.patch_continuity_corner_tip": "Der Patch schließt entlang jeder Seite mit der gewählten Stetigkeit an die umgebenden Flächen an. An einer Ecke, an der diese Flächen mit unterschiedlicher Krümmung aufeinandertreffen, bleibt er dort tangential (G1).",
|
||||
"ui.path": "Pfad",
|
||||
"ui.pattern_object_type_tip": "Körper: feste Formen, in der Ansicht oder im Browser gewählt; jeder wird ganz kopiert.\nFunktionen: Schritte im Verlauf, etwa eine Extrusion oder eine Bohrung, im Verlauf oder auf einer von ihnen erzeugten Fläche gewählt; der Schritt wird wiederholt.\nEine Skizze ist keines von beiden: Ihre Kurven mit Muster oder Spiegeln in der Skizze wiederholen oder sie zuerst extrudieren.",
|
||||
"ui.pattern_on_path": "Pfadmuster",
|
||||
@@ -5228,7 +4913,7 @@
|
||||
"ui.pitch": "Steigung",
|
||||
"ui.placement": "Platzierung",
|
||||
"ui.placement_handles": "Platzierungsgriffe",
|
||||
"ui.placement_handles_tip": "Eine Markierung zum Verschieben des Features über das Modell und ein Ring zum Drehen.",
|
||||
"ui.placement_handles_tip": "Griffe an der Vorschau einer Auflage, eines Ansatzes oder einer Rastverbindung: Ziehen Sie die Markierung, um sie über das Modell zu bewegen, und drehen Sie sie am Ring",
|
||||
"ui.plane": "Ebene",
|
||||
"ui.plane_at_angle": "Ebene im Winkel",
|
||||
"ui.plugins": "Plugins",
|
||||
@@ -5312,8 +4997,6 @@
|
||||
"ui.repeat_nothing": "Noch nichts zu wiederholen: Starten Sie zuerst ein Werkzeug oder einen Dialog.",
|
||||
"ui.replace_with_link_done": "Die Kopien sind jetzt Platzierungen des verknüpften Designs.",
|
||||
"ui.report_a_bug": "Fehler melden",
|
||||
"ui.report_characters_left_one": "{count} Zeichen übrig",
|
||||
"ui.report_characters_left_other": "{count} Zeichen übrig",
|
||||
"ui.reset": "Zurücksetzen",
|
||||
"ui.reset_all": "Alle zurücksetzen",
|
||||
"ui.reset_the_settings_on_this_page": "Einstellungen auf dieser Seite zurücksetzen",
|
||||
@@ -5425,8 +5108,8 @@
|
||||
"ui.start": "Start",
|
||||
"ui.start_over": "Von vorn beginnen",
|
||||
"ui.start_page": "Startseite",
|
||||
"ui.step_values": "Werte mit Pfeilen und Mausrad ändern",
|
||||
"ui.step_values_tip": "Pfeil auf/ab und das Mausrad über einem Wertefeld ändern dessen Zahl.",
|
||||
"ui.step_values": "Werte mit Pfeiltasten und Mausrad schrittweise ändern",
|
||||
"ui.step_values_tip": "In einem Wertefeld, in das Sie gerade tippen, ändern Auf/Ab eine einfache Zahl um 1 (Umschalt: 10, Alt: 0,1), ebenso das Mausrad über dem Feld. Ausdrücke bleiben wie eingegeben.",
|
||||
"ui.stitch_tolerance_hint": "eng",
|
||||
"ui.stretch": "Strecken",
|
||||
"ui.supersampling": "Supersampling (SSAA)",
|
||||
@@ -5557,7 +5240,7 @@
|
||||
"update.check_unavailable": "Für diese Installation ist keine Update-Prüfung verfügbar.",
|
||||
"update.checking": "Suche läuft…",
|
||||
"update.copy": "Kopieren",
|
||||
"update.dmg_opened": "Das Disk-Image ist geöffnet und geprüft: SolidCraft beenden, das neue SolidCraft in „Programme“ ziehen, die alte Version ersetzen und SolidCraft wieder öffnen.",
|
||||
"update.dmg_opened": "Das Disk-Image ist geöffnet und geprüft: SolidCraft in „Programme“ ziehen, die alte Version ersetzen und SolidCraft neu starten.",
|
||||
"update.download": "Herunterladen",
|
||||
"update.download_open": "Herunterladen und öffnen",
|
||||
"update.downloading": "Herunterladen und prüfen… {done} von {total} MB",
|
||||
@@ -5595,14 +5278,6 @@
|
||||
"values must be numbers or expressions": "Werte müssen Zahlen oder Ausdrücke sein",
|
||||
"view.auto_ortho_faces": "Perspektivisch beim Drehen, orthografisch auf Flächen",
|
||||
"view.render_inspector": "Render-Inspektor",
|
||||
"view_gizmo.arrows": "Drehpfeile",
|
||||
"view_gizmo.home": "Schaltfläche „Ausgangsansicht“",
|
||||
"view_gizmo.no_such_body": "Diese Konstruktion hat keinen Körper mit diesem Namen.",
|
||||
"view_gizmo.note": "Wählen Sie einen Körper in der Ansicht. Er wird mit Ihren Einstellungen und Einstellungsprofilen gespeichert und in jeder Konstruktion angezeigt. Klicken Sie auf eine seiner Flächen, um direkt darauf zu blicken.",
|
||||
"view_gizmo.texture": "Textur",
|
||||
"view_gizmo.themed": "Design",
|
||||
"view_gizmo.too_detailed": "Zu detailliert für den Ansichtsnavigator (über 10.000 Dreiecke)",
|
||||
"view_gizmo.too_thin": "Zu lang und dünn für den Ansichtsnavigator: Die dünnste Seite muss mindestens ein Fünftel der längsten sein",
|
||||
"web.files.insert_drawings_hint": "CAD-Dateien, Netze, DXF oder SVG in das geöffnete Design",
|
||||
"web.files.insert_stored_drawings_hint": "In das geöffnete Design (CAD-Dateien, Netze, DXF, SVG)",
|
||||
"web.open_full": "In SolidCraft öffnen",
|
||||
|
||||
@@ -16,13 +16,14 @@
|
||||
"Mass Center": "Mass Centre",
|
||||
"Modeled": "Modelled",
|
||||
"Select the centre the pattern turns around first": "Select the centre the pattern turns around first",
|
||||
"Snap to face and edge centres": "Snap to centres",
|
||||
"Snap to face and edge centres": "Snap to face and edge centres",
|
||||
"Specialise": "Specialise",
|
||||
"cmd.appearance_favorite": "Favourite Look",
|
||||
"cmd.component_cycle_color": "Cycle Component Colour",
|
||||
"cmd.construct_point_center": "Point At Centre Of Circle/Sphere/Torus",
|
||||
"cmd.inspect_center_of_mass": "Centre of Mass",
|
||||
"cmd.inspect_colored_components": "Component Colours",
|
||||
"cmd.inspect_colored_components": "Enable Component Colours",
|
||||
"cmd.inspect_colored_components_disable": "Disable Component Colours",
|
||||
"cmd.parameters_favorite": "Favourite Parameter",
|
||||
"cmd.render_aging_describe": "Layered Surface Aging",
|
||||
"cmd.render_effects_describe": "Lens and Creative Effects",
|
||||
@@ -68,17 +69,11 @@
|
||||
"help.solid.split_face": "Divides the selected faces along a plane, the face of another body or sketch curves, without cutting the body. Use it to give part of a face its own draft, colour or offset.",
|
||||
"help.third_party": "Third-party licences",
|
||||
"home.new_mm_hint": "Millimetres · opens in Design",
|
||||
"measure.line_to_centre": "Line to centre",
|
||||
"navbar.custom.anchor.bottom_centre": "Bottom centre",
|
||||
"navbar.custom.anchor.top_centre": "Top centre",
|
||||
"navbar.custom.customise": "Customise navigation bar…",
|
||||
"navbar.custom.position_centre": "Centre",
|
||||
"navbar.description.separator": "A thin line that groups neighbouring slots.",
|
||||
"pattern.alignment_hint": "Counts include the original. Centre balances copies; Flip chooses the extra side for even totals.",
|
||||
"prefs.appearance.colour_vision_help": "Changes the colours that carry a meaning: sketch states, axes, bends, analyses and icons.",
|
||||
"prefs.appearance.viewport_colours": "3D view colours",
|
||||
"prefs.bottom_centre": "Bottom centre",
|
||||
"prefs.design_tools.hole_snaps_help": "The middle of a face and the centres of its round edges.",
|
||||
"prefs.logo_theme": "Theme's colours",
|
||||
"prefs.max_file_size": "Largest file to open",
|
||||
"prefs.max_file_size_flag": "This run started with --max-file-size {size}, which applies until SolidCraft closes. These settings apply when it starts without it.",
|
||||
@@ -86,11 +81,6 @@
|
||||
"prefs.max_file_size_web": "The web app opens STEP and import files up to 512 MB and designs up to 256 MB: a browser tab can't hold larger files. The desktop app can raise these limits.",
|
||||
"prefs.megabytes": "MB",
|
||||
"prefs.network_locations_note": "For organisations that keep designs on file servers. Off: only servers you allow.",
|
||||
"prefs.select_hidden_edges_help": "Select edges through faces when Shaded with hidden edges is active, including for Fillet. Off keeps v0.30.1 selection behaviour. Hidden bodies stay unselectable.",
|
||||
"prefs.sketch.center_arc": "Centre-point arc follows the cursor",
|
||||
"prefs.sketch.conflict_colour": "Conflict colour",
|
||||
"prefs.sketch.tracking_guides": "Centre and midpoint guides",
|
||||
"prefs.sketch.tracking_guides_note": "Temporary horizontal and vertical guides from hovered centres and edge midpoints.",
|
||||
"prefs.sketch_conflict_colour": "Conflicting geometry in the conflict colour",
|
||||
"prefs.sketch_conflict_colour_note": "While a sketch's constraints conflict, the curves and points a failing constraint holds are drawn in the conflict colour too, not only its glyph or dimension. Off: they keep their usual colours.",
|
||||
"prefs.sketch_construction_lines": "Extended construction line tools",
|
||||
@@ -235,10 +225,6 @@
|
||||
"render.studio_area_help": "Up to four one-sided rectangles. Orthogonal half-edges U and V define size; U × V faces the lit side. Coordinates are world-space millimetres.",
|
||||
"render.studio_center": "Centre X, Y, Z (mm)",
|
||||
"render.studio_hdr_help": "Radiance .hdr, 2:1 panorama, up to 2048 × 1024. EXR is unavailable. The colour assumption applies only when primaries are unspecified.",
|
||||
"sketch.hint.arc.center_point": "Arc: click centre and start, go round either way and click the end, or type a signed sweep",
|
||||
"sketch.hint.circle.center": "Circle: click the centre, then a point on the circle",
|
||||
"sketch.hint.constraint.midpoint": "MidPoint: pick a point and a line, or two lines to centre the second on the first",
|
||||
"sketch.hint.rectangle.center": "Centre rectangle: click the centre, then a corner",
|
||||
"sketch_text.align_center": "Centre",
|
||||
"ui.3d_mouse_kind_cap": "Return to Centre",
|
||||
"ui.3d_mouse_looks_like_cap": "The reading stays put while you hold it, the way a Return to Centre device does.",
|
||||
|
||||
@@ -556,7 +556,6 @@
|
||||
"Section": "Coupe",
|
||||
"Section (optional)": "Section (facultative)",
|
||||
"Section position": "Position de la section",
|
||||
"Sections": "Sections",
|
||||
"Sections (one sketch each)": "Sections (une esquisse chacune)",
|
||||
"Select Other": "Sélectionner un autre",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Sélectionner un autre élément : maintenir le bouton gauche immobile sur le modèle (ou clic droit › Sélectionner un autre…) pour lister tout ce qui se trouve sous le curseur ; cliquer de nouveau au même endroit pour sélectionner l'élément suivant. Double-cliquer avec le bouton du milieu pour revenir à l'accueil (Préférences › Navigation).",
|
||||
@@ -794,13 +793,6 @@
|
||||
"browser.sketch_attachment_lost": "{name} (attache perdue)",
|
||||
"browser.sketch_editing": "{name} (en cours de modification)",
|
||||
"browser.sketch_name": "Esquisse{number}",
|
||||
"browser.sketches_to_parts_already_in_part": "déjà dans leur pièce",
|
||||
"browser.sketches_to_parts_design_body": "sur un corps de la conception elle-même",
|
||||
"browser.sketches_to_parts_moved": "{moved} esquisses déplacées vers la pièce sur laquelle elles se trouvent",
|
||||
"browser.sketches_to_parts_no_exact_attachment": "sur une face sans rattachement exact",
|
||||
"browser.sketches_to_parts_not_on_face": "pas sur la face d'une pièce",
|
||||
"browser.sketches_to_parts_part_missing": "leur pièce n'existe plus",
|
||||
"browser.sketches_to_parts_result": "{moved} esquisses déplacées vers la pièce sur laquelle elles se trouvent ; {stayed} sont restées en place : {why}",
|
||||
"browser.units": "Unités : {units}",
|
||||
"cam.flat": "Plateau",
|
||||
"cam.follower": "Suiveur",
|
||||
@@ -860,7 +852,6 @@
|
||||
"cmd.component_extract": "Extraire dans un fichier",
|
||||
"cmd.component_finish_edit": "Terminer la modification",
|
||||
"cmd.component_from_bodies": "Créer des composants à partir des corps",
|
||||
"cmd.component_hide_history": "Masquer l'historique du composant",
|
||||
"cmd.component_insert_design": "Insérer une conception",
|
||||
"cmd.component_links": "Lister les liens",
|
||||
"cmd.component_list": "Lister les composants",
|
||||
@@ -869,7 +860,6 @@
|
||||
"cmd.component_move_bodies": "Déplacer les corps vers le composant",
|
||||
"cmd.component_move_features": "Déplacer les fonctions vers le composant",
|
||||
"cmd.component_move_sketches": "Déplacer les esquisses vers le composant",
|
||||
"cmd.component_move_sketches_to_parts": "Déplacer les esquisses vers leurs pièces",
|
||||
"cmd.component_paste_from_design": "Coller depuis une autre conception",
|
||||
"cmd.component_paste_new": "Coller comme nouveau",
|
||||
"cmd.component_properties": "Propriétés physiques du composant",
|
||||
@@ -877,7 +867,6 @@
|
||||
"cmd.component_rename": "Renommer le composant",
|
||||
"cmd.component_replace_with_link": "Remplacer par un lien",
|
||||
"cmd.component_revert_position": "Rétablir la position",
|
||||
"cmd.component_show_history": "Afficher l'historique du composant",
|
||||
"cmd.component_unlock": "Déverrouiller le contenu",
|
||||
"cmd.component_update_links": "Mettre à jour les liens",
|
||||
"cmd.config_activate": "Activer la configuration",
|
||||
@@ -888,7 +877,6 @@
|
||||
"cmd.config_rule": "Règle de configuration",
|
||||
"cmd.config_rules": "Règles de configuration",
|
||||
"cmd.config_table": "Afficher la table des configurations",
|
||||
"cmd.construct.plane.axis_point": "Plan passant par un axe et un point",
|
||||
"cmd.construct_axis_edge": "Axe passant par une arête",
|
||||
"cmd.construct_axis_normal_to_face": "Axe perpendiculaire à une face",
|
||||
"cmd.construct_axis_through_cylinder": "Axe d'un cylindre/cône/tore",
|
||||
@@ -937,11 +925,6 @@
|
||||
"cmd.explode_show": "Afficher la vue éclatée",
|
||||
"cmd.expr_evaluate": "Évaluer une expression",
|
||||
"cmd.face_delete": "Supprimer la face",
|
||||
"cmd.file.export_3mf": "Exporter en 3MF",
|
||||
"cmd.file.export_obj": "Exporter en OBJ",
|
||||
"cmd.file.export_step": "Exporter en STEP",
|
||||
"cmd.file.export_stl": "Exporter en STL",
|
||||
"cmd.file.export_urdf": "Exporter en URDF",
|
||||
"cmd.file_export": "Exporter",
|
||||
"cmd.file_insert_mesh": "Insérer un maillage",
|
||||
"cmd.file_insert_step": "Insérer un STEP",
|
||||
@@ -956,8 +939,9 @@
|
||||
"cmd.inspect_accessibility": "Analyse d'accessibilité",
|
||||
"cmd.inspect_center_of_mass": "Centre de gravité",
|
||||
"cmd.inspect_clearance": "Jeu",
|
||||
"cmd.inspect_colored_components": "Activer les couleurs des composants",
|
||||
"cmd.inspect_colored_components_disable": "Désactiver les couleurs des composants",
|
||||
"cmd.inspect_compare": "Comparer les corps",
|
||||
"cmd.inspect_compare_picture": "Comparer les images",
|
||||
"cmd.inspect_continuity": "Analyse de continuité",
|
||||
"cmd.inspect_curvature_comb": "Analyse par peigne de courbure",
|
||||
"cmd.inspect_curvature_map": "Analyse par carte de courbure",
|
||||
@@ -1048,9 +1032,6 @@
|
||||
"cmd.pointcloud_edit": "Modifier le nuage de points",
|
||||
"cmd.pointcloud_insert": "Insérer un nuage de points",
|
||||
"cmd.pointcloud_list": "Nuages de points",
|
||||
"cmd.render.compare.divider": "Déplacer le séparateur de comparaison des rendus",
|
||||
"cmd.render.result.discard": "Écarter le résultat de rendu",
|
||||
"cmd.render.result.select": "Sélectionner le résultat de rendu",
|
||||
"cmd.render_aging_describe": "Vieillissement de surface en couches",
|
||||
"cmd.render_backends": "Moteurs de rendu",
|
||||
"cmd.render_batch": "Rendre toutes les prises de vue",
|
||||
@@ -1265,7 +1246,6 @@
|
||||
"cmd.timeline_rollback": "Déplacer le marqueur de chronologie",
|
||||
"cmd.timeline_stop": "Arrêter l'historique",
|
||||
"cmd.timeline_suppress": "Désactiver la fonction",
|
||||
"cmd.ui.assistant": "Afficher/masquer l'assistant",
|
||||
"cmd.view.render_browser": "Navigateur de rendu",
|
||||
"cmd.view.render_dock": "Déplacer le panneau de rendu",
|
||||
"cmd.view.render_focus": "Vue de rendu ciblée",
|
||||
@@ -1300,11 +1280,9 @@
|
||||
"cmd.view_tool": "Outil de navigation",
|
||||
"cmd.view_top": "Vue de dessus",
|
||||
"cmd.view_visual_style": "Style visuel",
|
||||
"color.blue": "Bleu",
|
||||
"color.copy": "Cliquer pour copier les valeurs de couleur",
|
||||
"color.edit": "Cliquer pour modifier la couleur",
|
||||
"color.gamma": "Valeurs de couleur affichées en espace gamma 0-255",
|
||||
"color.green": "Vert",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Saisissez #rrggbb ou #rrggbbaa ; six chiffres conservent l'opacité",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1312,7 +1290,6 @@
|
||||
"color.linear": "Valeurs de couleur affichées en espace linéaire 0-1",
|
||||
"color.recent": "Couleurs récentes",
|
||||
"color.recent_help": "Les couleurs choisies dans n'importe quel champ de couleur apparaissent ici",
|
||||
"color.red": "Rouge",
|
||||
"color.saturation": "Saturation (pour cent)",
|
||||
"color.saturation_value": "Saturation et valeur",
|
||||
"color.selected": "Couleur sélectionnée",
|
||||
@@ -1320,8 +1297,6 @@
|
||||
"color.step_up": "Augmenter d'un pas",
|
||||
"color.value": "Valeur, la luminosité (pour cent)",
|
||||
"config.column_filter_empty": "Aucune colonne correspondante",
|
||||
"config.rule_parameter_columns": "Seuls les paramètres ayant une colonne dans la table sont listés. Ajouter une colonne avec Ajouter une colonne (+) pour l'utiliser ici.",
|
||||
"config.rule_suppression_columns": "Seules les fonctions ayant une colonne Désactiver dans la table sont listées. Ajouter une colonne avec Ajouter une colonne (+) pour la piloter ici.",
|
||||
"contact.components_count": "{count} composants",
|
||||
"contact.enabled_label": "activé",
|
||||
"coupling.gear_1": "Engrenage 1",
|
||||
@@ -1363,21 +1338,14 @@
|
||||
"dialog.computing": "Calcul… {seconds} s",
|
||||
"dialog.could_not_be_checked": "non vérifiable",
|
||||
"dialog.custom_axis": "↖ Axe personnalisé",
|
||||
"dialog.cylinder_face_tip": "La face ou le sommet du modèle sur lequel le cylindre est posé : cliquer ici, puis dessus dans la vue. Laissé vide, X, Y et Z le placent.",
|
||||
"dialog.dimension_insert_hint": "Double-cliquez sur une autre cote pilotante pour insérer son nom",
|
||||
"dialog.dimension_name": "Cote : {name}",
|
||||
"dialog.edit_title": "MODIFIER {title}",
|
||||
"dialog.no_shown_body": "Aucun corps affiché à modifier : affichez-en un ou choisissez Nouveau corps",
|
||||
"dialog.pattern_sketch_not_object": "Choisissez des corps ou des fonctions : une esquisse n'est ni l'un ni l'autre. Répétez ses courbes avec la Répétition ou la Symétrie de l'esquisse, ou extrudez-la d'abord.",
|
||||
"dialog.primitive_point_tip": "Un point qui remplit X, Y et Z : cliquer ici, puis sur un sommet, une arête ou une face du modèle, sur un point d'une esquisse ou sur l'origine.",
|
||||
"dialog.reference_edge_tip": "Une arête droite de la face choisie, depuis laquelle la distance ci-dessous est mesurée : cliquer ici, puis sur l'arête.",
|
||||
"dialog.select": "↖ Sélectionner",
|
||||
"dialog.selected_count": "↖ {count} sélectionné(s)",
|
||||
"dialog.selection_constraint": "Contrainte {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · courbe {id}",
|
||||
"dialog.selection_guidance": "Sélectionner une entrée pour la modifier. Cliquer sur un objet sélectionné pour le retirer ; Ctrl/Cmd-clic le bascule aussi. Vérifier l'aperçu, puis appuyer sur OK.",
|
||||
"dialog.selection_midpoint": "Point milieu · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step} sur {total} : sélectionner {input}",
|
||||
"dialog.step": "Étape",
|
||||
"document.changed_on_disk": "{name} a été modifié sur le disque",
|
||||
@@ -1392,7 +1360,6 @@
|
||||
"document.read_only_save": "Cette conception a été ouverte en lecture seule : enregistrez-la dans un nouveau fichier",
|
||||
"document.save_changes": "Enregistrer les modifications de {name} ?",
|
||||
"document.save_copy": "Enregistrer une copie…",
|
||||
"document.tab_unsaved_changes": "Modifications non enregistrées",
|
||||
"document.unsaved_changes": "{name} contient des modifications non enregistrées. Si vous n'enregistrez pas, elles seront perdues.",
|
||||
"drive.rigid": "{joint} est une liaison rigide : elle maintient ses pièces immobiles, il n'y a donc rien à piloter. Pour les laisser bouger, changer son type dans Modifier la liaison : Revolute pour tourner, Slider pour coulisser, Cylindrical pour les deux.",
|
||||
"drive.stopped_by_contact": "Arrêté à {value} : {a} traverserait {b}",
|
||||
@@ -1461,7 +1428,6 @@
|
||||
"help.construct.axis.two_points": "Crée un axe de construction passant par deux points, sommets ou points d'esquisse. À utiliser pour un axe qui relie deux positions connues.",
|
||||
"help.construct.plane.along_path": "Crée un plan de construction perpendiculaire à une arête ou une courbe, à une position choisie le long de celle-ci. À utiliser pour esquisser le profil d'un balayage ou une coupe d'une trajectoire.",
|
||||
"help.construct.plane.at_angle": "Crée un plan de construction tourné d'un angle par rapport à un plan ou une face, autour d'un axe ou d'une arête. À utiliser pour des esquisses inclinées, par exemple une équerre à angle.",
|
||||
"help.construct.plane.axis_point": "Crée un plan de construction contenant un axe et un point situé hors de cet axe. Sélectionner une origine ou un axe de construction, une arête rectiligne ou une ligne d'esquisse, puis un sommet, un point d'esquisse ou un point de construction. Un point situé sur l'axe ne définit pas un plan unique.",
|
||||
"help.construct.plane.midplane": "Crée un plan de construction à mi-distance entre deux plans ou faces planes, ou bissecteur lorsqu'ils se rencontrent en formant un angle. À utiliser comme plan de symétrie.",
|
||||
"help.construct.plane.offset": "Crée un plan de construction parallèle à un plan ou une face plane, à une distance donnée ou passant par un point. À utiliser pour esquisser à une hauteur précise au-dessus d'une face.",
|
||||
"help.construct.plane.perpendicular": "Crée un plan de construction qui contient une droite ou une arête et qui est perpendiculaire à un plan ou une face. À utiliser pour esquisser à la verticale sur un modèle, le long d'une arête.",
|
||||
@@ -1838,8 +1804,6 @@
|
||||
"joint_move.blocked_hint": "Vérifiez le type de liaison (par exemple rigide), la fixation de la pièce, les limites de la liaison et les valeurs pilotées par des paramètres.",
|
||||
"joint_move.blocked_title": "Impossible de déplacer cette pièce",
|
||||
"joint_move.coarse": "Pas de rotation près de l’axe (°)",
|
||||
"joint_move.conflict": "Une liaison de cette pièce est en conflit avec les autres : {joint}.",
|
||||
"joint_move.conflict_hint": "Modifier, désactiver ou supprimer la liaison en conflit pour que les autres liaisons puissent placer la pièce.",
|
||||
"joint_move.distance": "Distance depuis le départ",
|
||||
"joint_move.done": "Terminé",
|
||||
"joint_move.fine": "Pas de rotation loin de l’axe (°)",
|
||||
@@ -1939,8 +1903,6 @@
|
||||
"motion.track_name": "Piste {number}",
|
||||
"nav.alias.help": "Maj+Alt+glisser gauche fait orbiter, Maj+Alt+milieu fait un panoramique, Maj+Alt+droit zoome ; glisser avec le milieu fait aussi un panoramique et glisser avec le droit fait orbiter",
|
||||
"nav.blender.help": "Glisser avec le milieu fait orbiter, Maj+milieu fait un panoramique, Ctrl+milieu zoome ; glisser avec le droit ne touche pas la caméra",
|
||||
"nav.creo.help": "Glisser avec le milieu fait orbiter, Maj+milieu fait un panoramique, Ctrl+glisser avec le milieu zoome verticalement (vers le bas pour zoomer) ; la molette zoome",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "La souris et la souris 3D tournent en passant par les deux pôles et conservent le roulis. L'alignement des axes avec Maj est désactivé.",
|
||||
"nav.free_orbit.label": "Orbite libre",
|
||||
"nav.free_orbit.snap_help": "La souris et la souris 3D tournent en passant par les deux pôles et conservent le roulis. Maintenir Maj pendant un glissement d'orbite pour aligner la direction de vue et le roulis.",
|
||||
@@ -1950,7 +1912,6 @@
|
||||
"nav.hfss.help": "Alt+glisser gauche fait un panoramique, Maj+Alt+gauche zoome, Ctrl+glisser gauche sur un espace vide fait orbiter",
|
||||
"nav.inventor.help": "Glisser avec le milieu fait un panoramique, Maj+milieu fait orbiter, Ctrl+milieu zoome, glisser avec le droit fait orbiter",
|
||||
"nav.maya.help": "Alt+glisser gauche fait orbiter, Alt+milieu fait un panoramique, Alt+droit zoome ; glisser avec le milieu fait aussi un panoramique",
|
||||
"nav.nx.help": "Glisser avec le milieu fait orbiter, Maj+milieu fait un panoramique (milieu + droit dans NX), Ctrl+milieu zoome (milieu + gauche dans NX) ; glisser avec le droit ne touche pas la caméra",
|
||||
"nav.openscad.help": "Glisser avec le gauche dans le vide fait orbiter, Maj+glisser gauche sélectionne par rectangle, glisser la géométrie d'esquisse la modifie ; glisser avec le droit ou le milieu fait un panoramique, Maj+droit ou Maj+milieu zoome",
|
||||
"nav.perspective.sketch": "La modification d'une esquisse utilise la vue orthographique. Terminer l'esquisse pour passer en perspective.",
|
||||
"nav.right_drag_orbits.help": "Désactivé : glisser avec le droit ne touche pas la caméra et le bouton droit ouvre seulement le menu contextuel. Choisir l'affectation des boutons de la souris SolidWorks le désactive.",
|
||||
@@ -2158,8 +2119,6 @@
|
||||
"prefs.bottom_centre": "Bas centre",
|
||||
"prefs.bottom_right": "Bas droite",
|
||||
"prefs.browser": "Navigateur",
|
||||
"prefs.browser_components_open": "Composants dépliés au départ",
|
||||
"prefs.browser_components_open_note": "Chaque composant du navigateur commence déplié, comme avant, au lieu de rester replié jusqu'à ce que vous le dépliiez.",
|
||||
"prefs.browser_eye_left": "Œil à côté de la flèche de dépliage",
|
||||
"prefs.browser_eye_left_note": "Les lignes du navigateur affichent leur œil d'affichage et de masquage à côté de la flèche de dépliage, comme avant, au lieu d'une colonne à l'extrémité droite des lignes.",
|
||||
"prefs.browser_reveal_on_hover": "Révéler au survol",
|
||||
@@ -2169,8 +2128,6 @@
|
||||
"prefs.button_left": "Glisser gauche",
|
||||
"prefs.button_middle": "Glisser milieu",
|
||||
"prefs.button_right": "Glisser droit",
|
||||
"prefs.carousel_next": "Suivant",
|
||||
"prefs.carousel_previous": "Précédent",
|
||||
"prefs.choose_font": "Choisir un fichier de police…",
|
||||
"prefs.click": "Clic",
|
||||
"prefs.contact_shading": "Ombrage de contact doux",
|
||||
@@ -2183,10 +2140,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Surfaces courbes",
|
||||
"prefs.curves": "Surfaces courbes",
|
||||
"prefs.cut_preview_tint": "Afficher en rouge la matière retirée",
|
||||
"prefs.cut_preview_tint_help": "Un aperçu remplit la matière qu'une fonction retire, et la forme qu'elle remplace, d'un fantôme rouge translucide. Désactivée, ces remplissages sont entièrement transparents et seuls leurs contours sont tracés.",
|
||||
"prefs.dark_parts_shading": "Conserver la forme des pièces sombres",
|
||||
"prefs.dark_parts_shading_help": "Ombrer la vue en lumière linéaire avec un léger éclairage de ciel, pour que les faces sombres gardent un dégradé au lieu de devenir noires",
|
||||
"prefs.dead_zone_note": "La bande hachurée est la zone morte : les mouvements à l'intérieur sont ignorés.",
|
||||
"prefs.default": "par défaut",
|
||||
"prefs.design": "Valeurs par défaut des conceptions",
|
||||
@@ -2201,8 +2154,6 @@
|
||||
"prefs.edges": "Arêtes et ombrage",
|
||||
"prefs.extend": "Étendre",
|
||||
"prefs.extra_navigation": "Ctrl+glisser gauche dans le vide fait orbiter ; maintenir Espace pour un panoramique.",
|
||||
"prefs.extrude_view_isometric": "Passer la vue en isométrique à l'ouverture de l'extrusion",
|
||||
"prefs.extrude_view_return": "Revenir à la vue précédente à la fermeture de l'extrusion",
|
||||
"prefs.follows_os": "Suit le système",
|
||||
"prefs.font": "Police",
|
||||
"prefs.font_fallback": "Les caractères absents de la police choisie utilisent la police intégrée, pour qu'aucun libellé n'affiche de case vide.",
|
||||
@@ -2218,7 +2169,6 @@
|
||||
"prefs.graphics_card_auto": "Automatique",
|
||||
"prefs.graphics_card_missing": "La carte graphique enregistrée n'est pas disponible ; Automatique est utilisé.",
|
||||
"prefs.graphics_card_restart": "Prend effet après le redémarrage de SolidCraft. WGPU_ADAPTER_NAME prévaut sur ce choix.",
|
||||
"prefs.graphics_card_unused": "La carte graphique enregistrée n'a pas été utilisée pour cette fenêtre ; la carte utilisée est indiquée dans Aide › À propos et dans la barre d'état. Essayer Automatique, ou définir WGPU_ADAPTER_NAME avec une partie unique du nom de la carte.",
|
||||
"prefs.graphics_note": "Compromis entre vitesse et fluidité. Chaque choix montre son effet sur un exemple.",
|
||||
"prefs.graphics_presets_note": "Les préréglages définissent ensemble les choix ci-dessous.",
|
||||
"prefs.hide_constraint_glyphs_zoomed_out": "Masquer les symboles de contraintes en cas de zoom arrière",
|
||||
@@ -2233,8 +2183,6 @@
|
||||
"prefs.history_sketches_note": "Avec Esquisses réglé sur Sous leur fonction, l'historique de gauche liste chaque esquisse un niveau en retrait, sous la première fonction construite à partir d'elle, dès qu'il est assez large pour les noms. La chronologie en bas garde toujours l'ordre de création.",
|
||||
"prefs.history_sketches_under_feature": "Sous leur fonction",
|
||||
"prefs.hold_stop_snapping": "Maintenir pour désactiver l'accrochage",
|
||||
"prefs.home_fit_size": "Taille de Accueil et Cadrer",
|
||||
"prefs.home_fit_size_note": "Part de la vue que remplit le modèle quand Accueil, Cadrer, l'ouverture d'une conception ou une vue du repère de navigation recadre la vue, que le réglage ci-dessous soit activé ou non. 100 % : remplit la vue entre les panneaux, la boussole et la barre de navigation.",
|
||||
"prefs.icons_only": "Icônes seules",
|
||||
"prefs.imported_file": "Fichier importé",
|
||||
"prefs.in_browser": "Dans le navigateur",
|
||||
@@ -2243,22 +2191,15 @@
|
||||
"prefs.interface_language": "Langue de l'interface",
|
||||
"prefs.interface_note": "L'apparence de SolidCraft et la place de ses éléments.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Inverser les flèches courbes de roulis",
|
||||
"prefs.invert_curved_roll_arrows_note": "Les deux flèches courbes au coin du cube font rouler la vue dans le plan de l'écran. Activé : la flèche horaire fait rouler dans le sens antihoraire et la flèche antihoraire dans le sens horaire. Désactivé : chaque flèche fait rouler la vue dans le sens de sa courbure.",
|
||||
"prefs.invert_navigator_arrows": "Inverser les flèches de rotation du cube de vue",
|
||||
"prefs.invert_navigator_arrows_note": "Les flèches autour du cube font orbiter la caméra dans le sens où elles pointent, comme dans Fusion et Inventor : la flèche de gauche pointe vers la droite et fait pivoter la caméra vers la droite. Désactivé : une flèche fait tourner le cube vers le côté où elle se trouve, donc la flèche de gauche fait pivoter la caméra vers la gauche. Les flèches courbes de roulis ne sont pas concernées.",
|
||||
"prefs.keep_work_on_tab_switch": "Garder dialogues et outils ouverts en changeant de conception",
|
||||
"prefs.keep_work_on_tab_switch_note": "Chaque onglet de conception garde son dialogue ou son outil ouvert, avec ce que vous y avez sélectionné et saisi, ainsi que son onglet de barre d'outils. Vous les retrouvez en revenant à la conception. Désactivé : changer de conception les annule et affiche l'onglet Modèle.",
|
||||
"prefs.labels": "Libellés",
|
||||
"prefs.language_system": "Système",
|
||||
"prefs.layout": "Disposition",
|
||||
"prefs.layout_note": "Compacte : ancre les panneaux aux bords de la fenêtre. La transparence du navigateur est disponible en mode Flottante. Changez le côté de l'assistant dans la page Assistant.",
|
||||
"prefs.left": "Gauche",
|
||||
"prefs.left_click": "Clic gauche",
|
||||
"prefs.line_drag_draws_one": "Appuyer et glisser trace une seule ligne",
|
||||
"prefs.line_drag_draws_one_note": "Dans l'outil Ligne, appuyer, faire glisser puis relâcher trace une seule ligne, du point d'appui au point de relâchement, et le tracé s'arrête là. Les clics tracent toujours une chaîne. Désactivé : un glissement place un point là où il est relâché, comme un clic.",
|
||||
"prefs.line_hover_tangent_arc": "S'arrêter sur le dernier point d'une ligne pour passer à un arc tangent",
|
||||
"prefs.line_hover_tangent_arc_note": "Dans l'outil Ligne, une fois un segment tracé, éloigner le pointeur du dernier point de la chaîne, revenir et le maintenir immobile dessus : le clic suivant termine un arc tangent, et le même geste revient aux lignes. Désactivé : s'arrêter là ne fait rien.",
|
||||
"prefs.linked_designs": "Conceptions liées",
|
||||
"prefs.live_input": "Saisie en direct",
|
||||
"prefs.live_preview": "Aperçu en direct",
|
||||
@@ -2282,8 +2223,6 @@
|
||||
"prefs.mouse_mapping": "Affectation des boutons de la souris",
|
||||
"prefs.navigation": "Navigation",
|
||||
"prefs.navigation_note": "Comment la souris, le pavé tactile et la souris 3D déplacent la vue.",
|
||||
"prefs.navigator_fit": "Cadrer le modèle après un changement de vue du repère de navigation",
|
||||
"prefs.navigator_fit_note": "Une face, un coin ou une flèche du cube de vue, ou un axe du gizmo, regarde aussi le milieu du modèle depuis la distance unique qui le cadre sous tous les côtés (sa sphère englobante). Accueil utilise le même zoom et le même centre tant que cette option est activée, de sorte que les vues choisies ensuite ne font que tourner. Désactivé : la vue tourne sur place, en gardant sa cible et son zoom.",
|
||||
"prefs.network_host_remove": "Retirer",
|
||||
"prefs.network_locations": "Autoriser les emplacements réseau dans les conceptions",
|
||||
"prefs.network_locations_note": "Désactivé : les fichiers liés d'une conception situés sur un autre ordinateur (\\\\server\\share) ne sont ouverts que depuis les serveurs listés ici, ou après en avoir autorisé un sur demande.",
|
||||
@@ -2304,8 +2243,6 @@
|
||||
"prefs.placement": "Placement",
|
||||
"prefs.prefer_free_sketch_points": "Privilégier les points d'esquisse déplaçables quand des points se superposent",
|
||||
"prefs.preview_choices": "Aperçu de vos choix",
|
||||
"prefs.preview_tint_new_faces": "Teinter la nouvelle surface dans un aperçu",
|
||||
"prefs.preview_tint_new_faces_help": "L'aperçu d'une fonction qui modifie un corps peut tracer la surface qu'elle ajoute dans la couleur d'aperçu. Désactivée, le corps est tracé tel quel.",
|
||||
"prefs.print_face_highlight": "Mise en évidence de la face d’impression",
|
||||
"prefs.print_face_highlight_tip": "Intensité de la teinte et du contour de la face d’impression d’un corps dans la vue. 0 % n’affiche aucune mise en évidence.",
|
||||
"prefs.profile_active": "Profil actif",
|
||||
@@ -2334,8 +2271,6 @@
|
||||
"prefs.render_previous_geometry_budget": "Utiliser l'ancien budget de géométrie de l'aperçu Rendu",
|
||||
"prefs.render_previous_geometry_budget_help": "Rétablir l'ancienne limite fixe de géométrie (512 Mio sur un ordinateur 64 bits, 128 Mio ailleurs) au lieu du budget basé sur l'appareil.",
|
||||
"prefs.reports_saved": "Enregistrés : {n}",
|
||||
"prefs.ribbon_click_ends_tool": "Cliquer sur le bouton de l'outil en cours le termine",
|
||||
"prefs.ribbon_click_ends_tool_note": "Un clic sur le bouton allumé du ruban de l'outil d'esquisse en cours termine l'outil, et les clics dans la vue sélectionnent de nouveau. Désactivé : le clic relance l'outil.",
|
||||
"prefs.ribbon_reveal": "Montrer les résultats de recherche dans le ruban",
|
||||
"prefs.ribbon_reveal_note": "Survoler un résultat dans Rechercher des commandes montre où se trouve la commande dans le ruban : son onglet, son bouton ou sa ligne dans le menu déroulant d'un panneau. Une commande lancée depuis la recherche y clignote, et une commande en cours issue d'un menu déroulant signale son panneau.",
|
||||
"prefs.right_click": "Clic droit",
|
||||
@@ -2365,8 +2300,6 @@
|
||||
"prefs.sketch_exit_view.previous": "Revenir à la vue précédente",
|
||||
"prefs.sketch_exit_view.unless_rotated": "Revenir, sauf si la vue a été tournée",
|
||||
"prefs.sketch_exit_view_note": "Où va la vue après Terminer l'esquisse, Annuler ou un double Échap.",
|
||||
"prefs.sketch_face_activates_part": "Esquisser sur la face d'une pièce active cette pièce",
|
||||
"prefs.sketch_face_activates_part_note": "Une esquisse commencée sur une face du corps d'un autre composant active d'abord ce composant, de sorte que l'esquisse lui appartient et reste avec cette pièce dans le navigateur. La pièce reste ensuite active. Désactivé : l'esquisse appartient au composant actif.",
|
||||
"prefs.sketch_open_look_at": "Se placer face à l'esquisse à son ouverture",
|
||||
"prefs.sketch_open_look_at_note": "Désactivé : créer ou modifier une esquisse laisse la vue telle quelle. Vue normale place la vue face à l'esquisse quand vous le souhaitez.",
|
||||
"prefs.sketch_recent_snap": "Accrocher automatiquement aux centres et points milieux récents",
|
||||
@@ -2398,24 +2331,7 @@
|
||||
"prefs.status_error": "Erreur",
|
||||
"prefs.status_selected": "Sélectionné",
|
||||
"prefs.status_warning": "Avertissement",
|
||||
"prefs.structure.appearance": "Apparence",
|
||||
"prefs.structure.appearance_note": "Couleurs, texte et accessibilité.",
|
||||
"prefs.structure.in_profile": "Dans le profil",
|
||||
"prefs.structure.layout": "Disposition",
|
||||
"prefs.structure.layout_note": "Où se placent les parties de la fenêtre.",
|
||||
"prefs.structure.look": "Aspect",
|
||||
"prefs.structure.measure_pointer": "Mesure et pointeur",
|
||||
"prefs.structure.mouse3d": "Souris 3D",
|
||||
"prefs.structure.mouse3d_note": "SpaceMouse et SpaceNavigator.",
|
||||
"prefs.structure.navigation_note": "Souris et pavé tactile. La souris 3D a sa propre page.",
|
||||
"prefs.structure.profile": "Profil",
|
||||
"prefs.structure.profile_edited": "Modifié depuis l'enregistrement",
|
||||
"prefs.structure.profile_hover": "Enregistré dans les profils de réglages",
|
||||
"prefs.structure.scale_percent": "{percent} %",
|
||||
"prefs.structure.sketch": "Esquisse",
|
||||
"prefs.structure.tools": "Outils",
|
||||
"prefs.structure.tools_note": "Comportement des outils de modélisation.",
|
||||
"prefs.structure.work": "Travail",
|
||||
"prefs.supersampling_brief": "Dessine la vue en plus grand puis la réduit par filtrage : très net, mais plus lourd.",
|
||||
"prefs.tab_accent_note": "La couleur d'accent suit l'onglet actif",
|
||||
"prefs.theme_file": "fichier de thème",
|
||||
@@ -2603,10 +2519,6 @@
|
||||
"render.base_color": "Couleur de base · sRVB",
|
||||
"render.base_metallic": "Fraction métallique",
|
||||
"render.base_opaque_only": "Cet éditeur conserve la sémantique des corps opaques. Utilisez les commandes de verre dédiées pour la transmission optique.",
|
||||
"render.batch_all_shots": "Toutes les prises de vue ({count})",
|
||||
"render.batch_empty": "Enregistrer une prise de vue avant de rendre toutes les prises de vue.",
|
||||
"render.batch_limit": "Le rendu de toutes les prises de vue est limité à 32 prises de vue.",
|
||||
"render.batch_status": "{running} en cours · {waiting} en attente",
|
||||
"render.bloom_enable": "Halo lumineux (bloom) à l'affichage",
|
||||
"render.bloom_help": "Lueur créative appliquée à la sortie seulement, à partir des hautes lumières linéaires positives, étalée par trois passes de flou normalisées. Le rayon est en pixels de sortie. Ajoutée en lumière d'affichage linéaire avant sRVB ; ce n'est ni un effet d'objectif, ni un effet volumétrique, ni une amélioration de la convergence. L'EXR brut est inchangé.",
|
||||
"render.bloom_opaque": "Le halo lumineux nécessite une sortie opaque. Un PNG transparent est refusé, pas aplati en silence.",
|
||||
@@ -2717,13 +2629,6 @@
|
||||
"render.choose_look": "Choisissez d'abord un aspect dans Apparences",
|
||||
"render.chromatic_offset": "Décalage chromatique des bords",
|
||||
"render.clear_stage": "Supprimer le décor enregistré",
|
||||
"render.compare_divider": "Séparateur de comparaison",
|
||||
"render.compare_live": "En direct",
|
||||
"render.compare_live_preview": "Aperçu en direct",
|
||||
"render.compare_render": "Rendu {number}",
|
||||
"render.compare_side": "Rendu {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · capturé à la révision {revision}",
|
||||
"render.compare_swap": "Inverser",
|
||||
"render.copy_request": "Copier le JSON",
|
||||
"render.cpu_aa_help": "Échantillons spatiaux : plus d'échantillons améliorent les bords et allongent le rendu.",
|
||||
"render.cpu_area_add": "Ajouter une lumière CPU rectangulaire",
|
||||
@@ -3002,11 +2907,9 @@
|
||||
"render.frame_wide": "16:9 · Écran large",
|
||||
"render.gpu_depth": "Profondeur maximale des chemins",
|
||||
"render.gpu_options_help": "Les échantillons, la profondeur, la graine et l'exposition sont enregistrés avec ce préréglage. Seule la méthode d'image active est enregistrée.",
|
||||
"render.gpu_path_crashed": "SolidCraft s’est fermé pendant sa dernière préparation sur cet adaptateur graphique, très probablement dans le pilote graphique ; il n’est donc plus préparé de lui-même. Utilisez le lancer de rayons sur le CPU, ou réessayez (une mise à jour du pilote réessaie aussi).",
|
||||
"render.gpu_path_failed": "Traceur de chemins GPU indisponible",
|
||||
"render.gpu_path_first_setup": "La première initialisation sur ce GPU peut prendre plusieurs minutes ; SolidCraft reste utilisable pendant ce temps. Les démarrages suivants réutilisent les shaders compilés lorsque le pilote les conserve.",
|
||||
"render.gpu_path_preparing": "Traceur de chemins GPU : compilation des shaders…",
|
||||
"render.gpu_path_retry": "Réessayer",
|
||||
"render.gpu_seed": "Graine aléatoire",
|
||||
"render.gpu_unavailable": "Le tracé de chemins GPU n'est pas prêt sur cet appareil.",
|
||||
"render.ground_apply": "Ajuster et enregistrer le sol",
|
||||
@@ -3475,10 +3378,6 @@
|
||||
"render.result_retained": "Affichage de la dernière image terminée ; un rendu plus récent ne l'a pas remplacée.",
|
||||
"render.result_sequence": "Point de restauration",
|
||||
"render.result_title": "Résultat du rendu",
|
||||
"render.results_choose": "Résultats conservés",
|
||||
"render.results_label": "Rendu {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [obsolète]",
|
||||
"render.results_unnamed": "Image",
|
||||
"render.return_design": "Revenir à la conception",
|
||||
"render.review_stale": "Cet examen est périmé. Résolvez ou vérifiez à nouveau avant d'appliquer.",
|
||||
"render.ribbon_aging": "Vieillissement",
|
||||
@@ -3930,7 +3829,6 @@
|
||||
"sketch_text.formula": "Commencez par = pour composer le texte avec des paramètres : ='L' + number(Largeur)",
|
||||
"sketch_text.italic": "Italique",
|
||||
"sketch_text.loading_fonts": "Recherche des polices installées…",
|
||||
"sketch_text.no_bold": "La police intégrée n'a pas de graisse grasse : choisir une police installée",
|
||||
"sketch_text.on_path": "Le texte suit sa trajectoire : la courbe le positionne",
|
||||
"sketch_text.path_clear": "Détacher le texte de sa courbe",
|
||||
"sketch_text.path_hint": "Une courbe de l'esquisse que le texte suit : cliquer ici, puis sur la courbe",
|
||||
@@ -3962,7 +3860,10 @@
|
||||
"the two origins are on the same component": "les deux origines se trouvent sur le même composant",
|
||||
"thread.automatic_size": "Taille automatique",
|
||||
"thread.designation": "Désignation",
|
||||
"thread.direction": "Sens du filetage",
|
||||
"thread.iso_metric": "Métrique ISO",
|
||||
"thread.left_hand": "À gauche",
|
||||
"thread.right_hand": "À droite",
|
||||
"thread.select_size": "Sélectionner la taille",
|
||||
"timeline.delete_after_marker_one": "Supprimer 1 fonction après le marqueur d'historique",
|
||||
"timeline.delete_after_marker_other": "Supprimer {count} fonctions après le marqueur d'historique",
|
||||
@@ -4145,13 +4046,11 @@
|
||||
"ui.agent_chat_command": "Commande de l'agent",
|
||||
"ui.agent_chat_connect": "Connecter",
|
||||
"ui.agent_chat_copy": "Copier",
|
||||
"ui.agent_chat_copy_message": "Copier le message",
|
||||
"ui.agent_chat_ctx_bodies": "Corps",
|
||||
"ui.agent_chat_ctx_elements": "Éléments",
|
||||
"ui.agent_chat_ctx_features": "Fonctions",
|
||||
"ui.agent_chat_ctx_more": "+{n} de plus",
|
||||
"ui.agent_chat_ctx_other": "Autre",
|
||||
"ui.agent_chat_custom_heading": "Modèle personnalisé",
|
||||
"ui.agent_chat_custom_hint": "Tout programme qui parle l'Agent Client Protocol sur stdin et stdout. Aucun shell ne le lance.",
|
||||
"ui.agent_chat_denied": "Refusé",
|
||||
"ui.agent_chat_deny": "Refuser",
|
||||
@@ -4195,16 +4094,8 @@
|
||||
"ui.agent_chat_mode_all": "Tout",
|
||||
"ui.agent_chat_mode_ask": "Demander",
|
||||
"ui.agent_chat_mode_edits": "Édition",
|
||||
"ui.agent_chat_model_custom": "Utiliser le modèle personnalisé : {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} ne peut pas utiliser les outils de SolidCraft : il peut seulement discuter. Choisissez un modèle qui prend en charge les outils.",
|
||||
"ui.agent_chat_model_rejected": "L'agent n'a pas accepté {model} : {error}",
|
||||
"ui.agent_chat_model_search": "Rechercher des modèles…",
|
||||
"ui.agent_chat_model_tip": "Le modèle utilisé par l'agent. Votre choix est mémorisé pour cet agent.",
|
||||
"ui.agent_chat_models_failed": "Impossible de lister les modèles : {error}",
|
||||
"ui.agent_chat_models_heading": "Modèles disponibles",
|
||||
"ui.agent_chat_models_listing": "Liste des modèles en cours…",
|
||||
"ui.agent_chat_models_none": "Le point d'accès ne liste aucun modèle",
|
||||
"ui.agent_chat_models_not_a_list": "La réponse n'était pas une liste de modèles",
|
||||
"ui.agent_chat_new": "Nouveau chat",
|
||||
"ui.agent_chat_new_confirm": "Démarrer un nouveau chat ? Le chat actuel sera effacé.",
|
||||
"ui.agent_chat_next_picture": "Image suivante",
|
||||
@@ -4340,9 +4231,6 @@
|
||||
"ui.ai_permissions": "Autorisations",
|
||||
"ui.ai_preset": "Préréglage",
|
||||
"ui.ai_protocol": "Protocole",
|
||||
"ui.ai_reasoning_effort": "Effort de raisonnement",
|
||||
"ui.ai_reasoning_effort_field_hint": "vide = valeur par défaut du fournisseur",
|
||||
"ui.ai_reasoning_effort_hint": "Facultatif. Envoyé au point d'accès sous le nom `reasoning_effort` ; les valeurs acceptées varient selon le fournisseur (par exemple `low`, `high` ou `max`). Une valeur saisie ici n'est jamais remplacée par SolidCraft. Laisser vide pour ne rien envoyer et garder la valeur par défaut du fournisseur.",
|
||||
"ui.ai_remove": "Retirer",
|
||||
"ui.ai_request_timed_out": "Le modèle a mis plus de {seconds} s. Vous pouvez augmenter le délai d'expiration des requêtes de ce point d'accès dans Préférences › Assistant.",
|
||||
"ui.ai_request_timeout": "Délai d'expiration des requêtes",
|
||||
@@ -4478,21 +4366,14 @@
|
||||
"ui.construction.contact_hint": "Tangente : sélectionnez un cercle/arc ou une spline/conique ouverte, puis le point de contact (courbes libres : extrémités)",
|
||||
"ui.construction.external_hint": "Tangente : sélectionnez un cercle ou un arc, un point extérieur, puis la solution souhaitée",
|
||||
"ui.construction.free_hint": "Droite de construction : cliquez sur deux points ; clic droit pour recommencer",
|
||||
"ui.construction.normal_needs_length": "Une ligne infinie est toujours de construction : saisir une longueur pour tracer une ligne normale",
|
||||
"ui.construction.point_hint": "Droite de construction : cliquez sur le point de passage",
|
||||
"ui.construction.reference_hint": "Droite de construction : sélectionnez une ligne droite, puis un point ; clic droit pour changer la référence",
|
||||
"ui.construction.ribbon_label": "Lignes aux.",
|
||||
"ui.construction.side_hint": "Ligne de construction : cliquer du côté vers lequel la ligne s'étend ; clic droit pour recommencer",
|
||||
"ui.context_menu": "Menu contextuel",
|
||||
"ui.continuity_crease": "Angle vif",
|
||||
"ui.continuity_curvature": "Courbure",
|
||||
"ui.continuity_curvature_change": "Variation de courbure",
|
||||
"ui.continuity_g0": "G0 angle vif",
|
||||
"ui.continuity_g1": "G1 tangence",
|
||||
"ui.continuity_g2": "G2 courbure",
|
||||
"ui.continuity_gap": "Écart",
|
||||
"ui.continuity_position": "Position",
|
||||
"ui.continuity_tangent": "Tangence",
|
||||
"ui.copy": "Copier",
|
||||
"ui.corner": "Angle",
|
||||
"ui.corners": "Angles",
|
||||
@@ -4515,7 +4396,6 @@
|
||||
"ui.dark": "Sombre",
|
||||
"ui.default_modelling_orientation": "Orientation de modélisation par défaut",
|
||||
"ui.delete": "Supprimer",
|
||||
"ui.delete_select_first": "Sélectionner d'abord ce qu'il faut supprimer : fonctions, corps, composants ou géométrie d'esquisse",
|
||||
"ui.depth": "Profondeur",
|
||||
"ui.design": "Conception",
|
||||
"ui.design_properties.author": "Auteur",
|
||||
@@ -4608,7 +4488,6 @@
|
||||
"ui.edit_in_place_saved_elsewhere": "La pièce a été enregistrée dans un autre fichier ; l'assemblage reste lié à {name}",
|
||||
"ui.edit_in_place_wait": "Attendez la fin de ce qui s'exécute dans la conception",
|
||||
"ui.edit_section_analysis": "Modifier l'analyse de coupe",
|
||||
"ui.edit_sketch_empty": "Aucune esquisse à modifier",
|
||||
"ui.ellipse": "Ellipse",
|
||||
"ui.emboss_wrap_note": "Les profils de lignes orthogonales tangents à un cylindre pris en charge s'enroulent automatiquement. Choisir une paroi pour définir une cible.",
|
||||
"ui.end_radius": "Rayon final",
|
||||
@@ -4619,9 +4498,7 @@
|
||||
"ui.equation_explicit": "Explicite",
|
||||
"ui.equation_form": "Forme",
|
||||
"ui.equation_note": "La variable est un simple nombre : sin(t) la prend en radians. Un résultat sans unité est dans l'unité de la conception.",
|
||||
"ui.equation_note_range": "La variable est une longueur, comme sa plage : la combiner avec des paramètres de longueur, et la diviser par une longueur pour sin(). Un résultat sans unité est dans l'unité de la conception.",
|
||||
"ui.equation_parametric": "Paramétrique",
|
||||
"ui.equation_variable_unit": "La variable a l'unité de la plage",
|
||||
"ui.equation_x_of_t": "x(t)",
|
||||
"ui.equation_y_of_t": "y(t)",
|
||||
"ui.equation_y_of_x": "y(x)",
|
||||
@@ -4753,7 +4630,6 @@
|
||||
"ui.hideOrigin": "Masquer l'origine",
|
||||
"ui.hidePlanes": "Masquer les plans",
|
||||
"ui.hideProfile": "Masquer le profil",
|
||||
"ui.hide_component_history": "Masquer son historique",
|
||||
"ui.hide_origin_by_default": "Masquer l'origine par défaut",
|
||||
"ui.hide_origin_note": "Les nouvelles conceptions démarrent avec le dossier Origine masqué. Une conception garde son propre réglage d'Origine dès que vous l'affichez ou la masquez.",
|
||||
"ui.history_back": "Reculer d’une étape",
|
||||
@@ -4761,7 +4637,6 @@
|
||||
"ui.history_edge_hint": "double-cliquer sur le bord",
|
||||
"ui.history_first": "Aller au début",
|
||||
"ui.history_forward": "Avancer d’une étape",
|
||||
"ui.history_hide_hidden": "Masquer les composants masqués",
|
||||
"ui.history_hide_names": "Masquer les noms",
|
||||
"ui.history_last": "Aller à la fin",
|
||||
"ui.history_marker_tip": "Marqueur d'historique : faire glisser pour ramener le modèle en arrière ; les nouvelles fonctions s'insèrent ici",
|
||||
@@ -4771,7 +4646,6 @@
|
||||
"ui.history_play_button_show": "Afficher le bouton Lire",
|
||||
"ui.history_play_finish_action": "Terminez l'action en cours avant de lire l'historique",
|
||||
"ui.history_play_roll_back": "Reculez le marqueur d'historique avant de lire",
|
||||
"ui.history_show_hidden": "Afficher les composants masqués",
|
||||
"ui.history_show_names": "Afficher les noms",
|
||||
"ui.history_sketch_listed_under": "Listé sous {feature}",
|
||||
"ui.history_title": "Historique",
|
||||
@@ -4800,14 +4674,12 @@
|
||||
"ui.joints": "Liaisons",
|
||||
"ui.keyboard_shortcuts": "Raccourcis clavier",
|
||||
"ui.language": "Langue",
|
||||
"ui.lasso_select": "Lasso (faire glisser une boucle libre : prend ce qui est entièrement à l'intérieur)",
|
||||
"ui.layout_compact": "Compacte",
|
||||
"ui.layout_floating": "Flottante",
|
||||
"ui.length": "Longueur",
|
||||
"ui.length_decimals": "Décimales des longueurs",
|
||||
"ui.light": "Clair",
|
||||
"ui.line": "Ligne",
|
||||
"ui.line_arc_hint": "Arc tangent : cliquer sur l'extrémité de l'arc. S'arrêter de nouveau sur le dernier point pour revenir aux lignes ; clic droit ou Échap pour terminer",
|
||||
"ui.link_copies": "Lier les copies identiques",
|
||||
"ui.link_copies_keep": "Conserver",
|
||||
"ui.link_copies_link": "Lier",
|
||||
@@ -5056,7 +4928,6 @@
|
||||
"ui.showDimension": "Afficher la cote",
|
||||
"ui.showOrigin": "Afficher l'origine",
|
||||
"ui.showPlanes": "Afficher les plans",
|
||||
"ui.show_component_history": "Afficher son historique",
|
||||
"ui.show_constraints": "Contraintes",
|
||||
"ui.show_construction_geometries": "Géométries de construction",
|
||||
"ui.show_dimensions": "Cotes",
|
||||
@@ -5173,15 +5044,12 @@
|
||||
"ui.view_angles": "Angles de vue",
|
||||
"ui.view_angles_convention": "Degrés autour de l'axe vertical. Lacet 0° = vue de face, 90° = vue de gauche, −90° = vue de droite, 180° = vue arrière ; tangage 90° = vue de dessus ; un roulis positif fait tourner le modèle dans le sens horaire.",
|
||||
"ui.view_angles_help": "Affiche le tangage, le lacet et le roulis de la vue en degrés, ainsi que sa projection, sous le gizmo d'orientation. Cliquez sur l'indicateur pour saisir des angles exacts.",
|
||||
"ui.view_angles_lock": "Verrouiller {axis}",
|
||||
"ui.view_angles_ortho": "Ortho",
|
||||
"ui.view_angles_persp": "Persp.",
|
||||
"ui.view_angles_pitch": "Tangage",
|
||||
"ui.view_angles_readout": "Tangage {pitch} · Lacet {yaw} · Roulis {roll}",
|
||||
"ui.view_angles_roll": "Roulis",
|
||||
"ui.view_angles_unlock": "Déverrouiller {axis}",
|
||||
"ui.view_angles_yaw": "Lacet",
|
||||
"ui.view_angles_zero": "Mettre {axis} à 0°",
|
||||
"ui.view_centre": "Centre de la vue",
|
||||
"ui.viewport_status_clear": "Désactiver {name}",
|
||||
"ui.viewport_status_clear_hint": "Désactiver {name} · Échap",
|
||||
@@ -5239,7 +5107,7 @@
|
||||
"update.check_unavailable": "La recherche de mises à jour n’est pas disponible pour cette installation.",
|
||||
"update.checking": "Vérification…",
|
||||
"update.copy": "Copier",
|
||||
"update.dmg_opened": "L'image disque est ouverte et vérifiée : quittez SolidCraft, faites glisser le nouveau SolidCraft dans Applications en remplaçant l'ancienne copie, puis rouvrez SolidCraft.",
|
||||
"update.dmg_opened": "L'image disque est ouverte et vérifiée : faites glisser SolidCraft dans Applications en remplaçant l'ancienne copie, puis redémarrez SolidCraft.",
|
||||
"update.download": "Télécharger",
|
||||
"update.download_open": "Télécharger et ouvrir",
|
||||
"update.downloading": "Téléchargement et vérification… {done} sur {total} Mo",
|
||||
@@ -5283,7 +5151,6 @@
|
||||
"web.open_link": "Ouvrir la conception liée ?",
|
||||
"web.open_link_confirm": "Il y a des modifications non enregistrées. Ouvrir la conception liée dans un nouvel onglet ?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ choisir · Entrée / Tab compléter · Échap fermer",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ choisir · Entrée applique cette valeur",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ choisir · Entrée applique la saisie · Échap fermer",
|
||||
"⌘Enter continues on GitHub.": "⌘Entrée continue sur GitHub."
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Rectangular",
|
||||
"Rectangular Tube": "Tubo rectangular",
|
||||
"Reference face": "Cara de referencia",
|
||||
"Reference plane": "Plano de referencia",
|
||||
"Relief Depth": "Profundidade do desafogo",
|
||||
"Relief Width": "Largura do desafogo",
|
||||
"Remove Tools": "Quitar as ferramentas",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Sección",
|
||||
"Section (optional)": "Sección (opcional)",
|
||||
"Section position": "Posición da sección",
|
||||
"Sections": "Seccións",
|
||||
"Sections (one sketch each)": "Seccións (un bosquexo cada unha)",
|
||||
"Select Other": "Seleccionar outro",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleccionar outro: mantén o botón esquerdo quieto sobre o modelo (ou clic dereito › Seleccionar outro…) para listar todo o que hai baixo o cursor; fai clic de novo no mesmo punto para seleccionar o seguinte. Fai dobre clic co botón central para ir ao Inicio (Preferencias › Navegación).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Extraer a un ficheiro",
|
||||
"cmd.component_finish_edit": "Rematar a edición",
|
||||
"cmd.component_from_bodies": "Crear compoñentes a partir de corpos",
|
||||
"cmd.component_hide_history": "Ocultar o historial do compoñente",
|
||||
"cmd.component_insert_design": "Inserir deseño",
|
||||
"cmd.component_links": "Listar as ligazóns",
|
||||
"cmd.component_list": "Listar os compoñentes",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Renomear o compoñente",
|
||||
"cmd.component_replace_with_link": "Substituír por unha ligazón",
|
||||
"cmd.component_revert_position": "Reverter a posición",
|
||||
"cmd.component_show_history": "Mostrar o historial do compoñente",
|
||||
"cmd.component_unlock": "Desbloquear o contido",
|
||||
"cmd.component_update_links": "Actualizar as ligazóns",
|
||||
"cmd.config_activate": "Activar a configuración",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Regra de configuración",
|
||||
"cmd.config_rules": "Regras de configuración",
|
||||
"cmd.config_table": "Mostrar a táboa de configuracións",
|
||||
"cmd.construct.plane.axis_point": "Plano por un eixe e un punto",
|
||||
"cmd.construct_axis_edge": "Eixe pola aresta",
|
||||
"cmd.construct_axis_normal_to_face": "Eixe perpendicular á cara",
|
||||
"cmd.construct_axis_through_cylinder": "Eixe por cilindro/cono/toro",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Mostrar a vista explosionada",
|
||||
"cmd.expr_evaluate": "Avaliar a expresión",
|
||||
"cmd.face_delete": "Eliminar a cara",
|
||||
"cmd.file.export_3mf": "Exportar 3MF",
|
||||
"cmd.file.export_obj": "Exportar OBJ",
|
||||
"cmd.file.export_step": "Exportar STEP",
|
||||
"cmd.file.export_stl": "Exportar STL",
|
||||
"cmd.file.export_urdf": "Exportar URDF",
|
||||
"cmd.file_export": "Exportar",
|
||||
"cmd.file_insert_mesh": "Inserir malla",
|
||||
"cmd.file_insert_step": "Inserir STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Análise de accesibilidade",
|
||||
"cmd.inspect_center_of_mass": "Centro de masas",
|
||||
"cmd.inspect_clearance": "Folga",
|
||||
"cmd.inspect_colored_components": "Activar as cores dos compoñentes",
|
||||
"cmd.inspect_colored_components_disable": "Desactivar as cores dos compoñentes",
|
||||
"cmd.inspect_compare": "Comparar corpos",
|
||||
"cmd.inspect_compare_picture": "Comparar a imaxe",
|
||||
"cmd.inspect_continuity": "Análise de continuidade",
|
||||
"cmd.inspect_curvature_comb": "Análise con pente de curvatura",
|
||||
"cmd.inspect_curvature_map": "Análise con mapa de curvatura",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Editar a nube de puntos",
|
||||
"cmd.pointcloud_insert": "Inserir nube de puntos",
|
||||
"cmd.pointcloud_list": "Nubes de puntos",
|
||||
"cmd.render.compare.divider": "Mover o divisor da comparación de renders",
|
||||
"cmd.render.result.discard": "Descartar o resultado do render",
|
||||
"cmd.render.result.select": "Seleccionar o resultado do render",
|
||||
"cmd.render_aging_describe": "Envellecemento da superficie por capas",
|
||||
"cmd.render_backends": "Motores de render",
|
||||
"cmd.render_batch": "Renderizar todas as tomas",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Mover o marcador da liña de tempo",
|
||||
"cmd.timeline_stop": "Deter a reprodución do historial",
|
||||
"cmd.timeline_suppress": "Suprimir a operación",
|
||||
"cmd.ui.assistant": "Mostrar/Ocultar o asistente",
|
||||
"cmd.view.render_browser": "Navegador de render",
|
||||
"cmd.view.render_dock": "Mover o panel de render",
|
||||
"cmd.view.render_focus": "Vista de foco do render",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Ferramenta de navegación",
|
||||
"cmd.view_top": "Vista superior",
|
||||
"cmd.view_visual_style": "Estilo visual",
|
||||
"color.blue": "Azul",
|
||||
"color.copy": "Preme para copiar os valores da cor",
|
||||
"color.edit": "Preme para editar a cor",
|
||||
"color.gamma": "Mostrando os valores da cor no espazo gamma 0-255",
|
||||
"color.green": "Verde",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Escribe #rrggbb ou #rrggbbaa; con seis díxitos consérvase a opacidade",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Mostrando os valores da cor no espazo lineal 0-1",
|
||||
"color.recent": "Cores recentes",
|
||||
"color.recent_help": "As cores que escolles en calquera campo de cor aparecen aquí",
|
||||
"color.red": "Vermello",
|
||||
"color.saturation": "Saturación (porcentaxe)",
|
||||
"color.saturation_value": "Saturación e valor",
|
||||
"color.selected": "Cor seleccionada",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Aumentar un paso",
|
||||
"color.value": "Valor, a luminosidade (porcentaxe)",
|
||||
"config.column_filter_empty": "Non hai columnas que coincidan",
|
||||
"config.rule_parameter_columns": "Só se listan os parámetros que teñen unha columna na táboa. Engade unha con Engadir columna (+) para usala aquí.",
|
||||
"config.rule_suppression_columns": "Só se listan as operacións que teñen unha columna Suprimir na táboa. Engade unha con Engadir columna (+) para controlalas desde aquí.",
|
||||
"contact.components_count": "{count} compoñentes",
|
||||
"contact.enabled_label": "activado",
|
||||
"coupling.gear_1": "Engrenaxe 1",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Escolle corpos ou operacións: un bosquexo non é ningunha das dúas cousas. Repite as súas curvas co Patrón ou a Simetría do propio bosquexo, ou extrúeo primeiro.",
|
||||
"dialog.select": "↖ Seleccionar",
|
||||
"dialog.selected_count": "↖ {count} seleccionados",
|
||||
"dialog.selection_constraint": "Restrición {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · curva {id}",
|
||||
"dialog.selection_guidance": "Selecciona unha entrada para cambiala. Fai clic nos obxectos seleccionados para quitalos; Ctrl/Cmd-clic tamén os alterna. Revisa a previsualización e preme Aceptar.",
|
||||
"dialog.selection_midpoint": "Punto medio · {id}",
|
||||
"dialog.selection_profile": "{sketch} · perfil {number}",
|
||||
"dialog.selection_step": "{step} de {total}: selecciona {input}",
|
||||
"dialog.step": "Paso",
|
||||
"document.changed_on_disk": "{name} cambiou no disco",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Crea un eixe de construción por dous puntos, vértices ou puntos do bosquexo. Úsao para un eixe que vai entre dúas posicións coñecidas.",
|
||||
"help.construct.plane.along_path": "Crea un plano de construción perpendicular a unha aresta ou curva, nunha posición ao longo dela. Úsao para debuxar o perfil dun varrido ou unha sección dunha traxectoria.",
|
||||
"help.construct.plane.at_angle": "Crea un plano de construción xirado un ángulo a partir dun plano ou dunha cara, arredor dun eixe ou dunha aresta. Úsao para bosquexos inclinados, como unha escuadra en ángulo.",
|
||||
"help.construct.plane.axis_point": "Crea un plano de construción que contén un eixe e un punto fóra dese eixe. Escolle unha orixe ou un eixe de construción, unha aresta recta ou unha liña do bosquexo e despois un vértice, un punto do bosquexo ou un punto de construción. Un punto sobre o eixe non pode definir un plano único.",
|
||||
"help.construct.plane.midplane": "Crea un plano de construción a medio camiño entre dous planos ou caras planas, ou que os bisecta onde se atopan formando un ángulo. Úsao como plano de simetría.",
|
||||
"help.construct.plane.offset": "Crea un plano de construción paralelo a un plano ou a unha cara plana, a unha distancia ou por un punto. Úsao para debuxar a unha altura fixa sobre unha cara.",
|
||||
"help.construct.plane.perpendicular": "Crea un plano de construción que contén unha liña ou aresta e é perpendicular a un plano ou a unha cara. Úsao para debuxar en vertical sobre un modelo ao longo dunha aresta.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Comproba o tipo de unión (como Ríxida), a fixación ao chan, os límites da unión e os valores controlados por parámetros.",
|
||||
"joint_move.blocked_title": "Non se pode mover esta peza",
|
||||
"joint_move.coarse": "Paso de rotación preto do eixe (°)",
|
||||
"joint_move.conflict": "Unha unión desta peza está en conflito coas outras: {joint}.",
|
||||
"joint_move.conflict_hint": "Edita, suprime ou elimina a unión en conflito para que as outras unións poidan colocar a peza.",
|
||||
"joint_move.distance": "Distancia desde o inicio",
|
||||
"joint_move.done": "Feito",
|
||||
"joint_move.fine": "Paso de rotación lonxe do eixe (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "Pista {number}",
|
||||
"nav.alias.help": "Shift+Alt+arrastrar co botón esquerdo orbita, Shift+Alt+botón central desprázase, Shift+Alt+botón dereito fai zoom; arrastrar co botón central tamén desprázase e arrastrar co botón dereito orbita",
|
||||
"nav.blender.help": "Arrastrar co botón central orbita, Shift+botón central desprázase, Ctrl+botón central fai zoom; arrastrar co botón dereito non move a cámara",
|
||||
"nav.creo.help": "Arrastrar co botón central orbita, Shift+botón central desprázase, Ctrl+botón central arrastrando en vertical fai zoom (cara abaixo para achegar); xira a roda do rato para facer zoom",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "O rato e o rato 3D xiran a vista a través dos dous polos e conservan o xiro sobre a vista. O axuste dos eixes con Shift está desactivado.",
|
||||
"nav.free_orbit.label": "Órbita libre",
|
||||
"nav.free_orbit.snap_help": "O rato e o rato 3D xiran a vista a través dos dous polos e conservan o xiro sobre a vista. Mantén Shift durante un arrastre de órbita para axustar a dirección da vista e o xiro.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Parafuso de cabeza abombada con hexágono interior",
|
||||
"parts.family.dowel_pin": "Pasador paralelo (espiga)",
|
||||
"parts.family.hex_bolt": "Parafuso / perno de cabeza hexagonal",
|
||||
"parts.family.hex_cap_screw_inch": "Parafuso de cabeza hexagonal (polgadas)",
|
||||
"parts.family.hex_nut": "Porca hexagonal",
|
||||
"parts.family.hex_nut_inch": "Porca hexagonal (polgadas)",
|
||||
"parts.family.set_screw": "Parafuso sen cabeza de hexágono interior, punta plana",
|
||||
"parts.family.socket_head_cap_screw": "Parafuso de cabeza cilíndrica con hexágono interior",
|
||||
"parts.family.washer": "Arandela plana",
|
||||
"parts.family.washer_sae": "Arandela plana, SAE (estreita)",
|
||||
"parts.family.washer_uss": "Arandela plana, USS (ancha)",
|
||||
"parts.holes": "Furados (opcional)",
|
||||
"parts.placement_hint": "escolle un ou máis furados para asentar unha peza en cada un, ou Aceptar colócaa na orixe",
|
||||
"parts.system": "Sistema de unidades",
|
||||
"parts.system.inch": "Polgadas",
|
||||
"parts.system.metric": "Métrico",
|
||||
"pattern.alignment_2": "Aliñamento 2",
|
||||
"pattern.alignment_hint": "O número inclúe o orixinal. Centrar equilibra as copias; Inverter escolle o lado da copia extra cando o total é par.",
|
||||
"pattern.direction_1": "Dirección 1",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Arrastrar co botón esquerdo",
|
||||
"prefs.button_middle": "Arrastrar co botón central",
|
||||
"prefs.button_right": "Arrastrar co botón dereito",
|
||||
"prefs.carousel_next": "Seguinte",
|
||||
"prefs.carousel_previous": "Anterior",
|
||||
"prefs.choose_font": "Escoller ficheiro de tipo de letra…",
|
||||
"prefs.click": "Clic",
|
||||
"prefs.contact_shading": "Sombreado suave de contacto",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Superficies curvas",
|
||||
"prefs.curves": "Superficies curvas",
|
||||
"prefs.cut_preview_tint": "Mostrar en vermello o material retirado",
|
||||
"prefs.cut_preview_tint_help": "Unha vista previa énchea o material que retira unha operación, e a forma que substitúe, cun fantasma vermello translúcido. Se está desactivado, eses recheos son totalmente transparentes e só se debuxan os seus contornos.",
|
||||
"prefs.dark_parts_shading": "Conservar a forma das pezas escuras",
|
||||
"prefs.dark_parts_shading_help": "Sombrea a vista en luz lineal cunha luz de recheo suave do ceo, para que as caras escuras manteñan un degradado en vez de volverse negras",
|
||||
"prefs.dead_zone_note": "A banda raiada é a zona morta: o movemento dentro dela ignórase.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Idioma da interface",
|
||||
"prefs.interface_note": "Como se ve SolidCraft e onde están as súas partes.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Inverter as frechas curvas de balanceo",
|
||||
"prefs.invert_curved_roll_arrows_note": "As dúas frechas curvas da esquina do cubo fan xirar a vista no plano da pantalla. Activado: a frecha en sentido horario xira en sentido antihorario e a frecha en sentido antihorario xira en sentido horario. Desactivado: cada frecha xira a vista no sentido en que se curva.",
|
||||
"prefs.invert_navigator_arrows": "Invertir as frechas de rotación do cubo de vistas",
|
||||
"prefs.invert_navigator_arrows_note": "As frechas arredor do cubo fan orbitar a cámara na dirección na que apuntan, como en Fusion e Inventor: a frecha da esquerda apunta á dereita e move a cámara cara á dereita. Desactivado: unha frecha xira o cubo cara ao lado no que está, así que a frecha da esquerda move a cámara cara á esquerda. As frechas curvas de balanceo non se ven afectadas.",
|
||||
"prefs.keep_work_on_tab_switch": "Manter abertos os diálogos e as ferramentas ao cambiar de deseño",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Colocación",
|
||||
"prefs.prefer_free_sketch_points": "Preferir os puntos de bosquexo móbiles cando os puntos se solapan",
|
||||
"prefs.preview_choices": "Vista previa das túas opcións",
|
||||
"prefs.preview_tint_new_faces": "Colorear a superficie nova nunha vista previa",
|
||||
"prefs.preview_tint_new_faces_help": "A vista previa dunha operación que cambia un corpo pode debuxar a superficie que engade coa cor da vista previa. Se está desactivado, o corpo debúxase tal e como é.",
|
||||
"prefs.print_face_highlight": "Resaltado da cara de impresión",
|
||||
"prefs.print_face_highlight_tip": "Intensidade da tinguidura e do contorno da cara de impresión dun corpo na vista. 0 % non mostra resaltado.",
|
||||
"prefs.profile_active": "Perfil activo",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Cor base · sRGB",
|
||||
"render.base_metallic": "Fracción metálica",
|
||||
"render.base_opaque_only": "Este editor mantén a semántica de corpo opaco. Usa os controis explícitos de vidro para a transmisión óptica.",
|
||||
"render.batch_all_shots": "Todas as tomas ({count})",
|
||||
"render.batch_empty": "Garda unha toma antes de renderizar todas as tomas.",
|
||||
"render.batch_limit": "Renderizar todas as tomas está limitado a 32 tomas.",
|
||||
"render.batch_status": "{running} en curso · {waiting} en espera",
|
||||
"render.bloom_enable": "Mostrar resplandor",
|
||||
"render.bloom_help": "Brillo creativo só de saída a partir de realces lineais positivos, esparexido con tres pasadas de caixa normalizadas. O raio está en píxeles de saída. Engádese na luz de visualización lineal antes de sRGB; non é unha lente, un efecto volumétrico nin unha mellora da converxencia. O EXR en bruto non cambia.",
|
||||
"render.bloom_opaque": "O resplandor require unha saída opaca. O PNG transparente rexéitase; non se aplana sen avisar.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Escolle primeiro un estilo en Aparencias",
|
||||
"render.chromatic_offset": "Desprazamento cromático dos bordos",
|
||||
"render.clear_stage": "Quitar o escenario gardado",
|
||||
"render.compare_divider": "Divisor da comparación",
|
||||
"render.compare_live": "En directo",
|
||||
"render.compare_live_preview": "Vista previa en directo",
|
||||
"render.compare_render": "Render {number}",
|
||||
"render.compare_side": "Render {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · capturado na revisión {revision}",
|
||||
"render.compare_swap": "Intercambiar",
|
||||
"render.copy_request": "Copiar JSON",
|
||||
"render.cpu_aa_help": "Mostras espaciais: máis mostras melloran os bordos e aumentan o tempo de renderizado.",
|
||||
"render.cpu_area_add": "Engadir luz rectangular da CPU",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Móstrase a última imaxe completada; un renderizado máis recente non a substituíu.",
|
||||
"render.result_sequence": "Punto de control",
|
||||
"render.result_title": "Resultado do renderizado",
|
||||
"render.results_choose": "Resultados conservados",
|
||||
"render.results_label": "Render {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [desactualizado]",
|
||||
"render.results_unnamed": "Imaxe",
|
||||
"render.return_design": "Volver a Deseño",
|
||||
"render.review_stale": "Esta revisión está obsoleta. Resólvea ou comproba de novo antes de aplicar.",
|
||||
"render.ribbon_aging": "Envellecemento",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "as dúas orixes están no mesmo compoñente",
|
||||
"thread.automatic_size": "Tamaño automático",
|
||||
"thread.designation": "Designación",
|
||||
"thread.direction": "Dirección da rosca",
|
||||
"thread.iso_metric": "Métrica ISO",
|
||||
"thread.left_hand": "Á esquerda",
|
||||
"thread.right_hand": "Á dereita",
|
||||
"thread.select_size": "Seleccionar o tamaño",
|
||||
"timeline.delete_after_marker_one": "Eliminar 1 operación despois do marcador de historial",
|
||||
"timeline.delete_after_marker_other": "Eliminar {count} operacións despois do marcador de historial",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Executa isto nun terminal e despois preme Conectar de novo:",
|
||||
"ui.agent_chat_auth_title": "Iniciar sesión no axente",
|
||||
"ui.agent_chat_cancel": "Cancelar",
|
||||
"ui.agent_chat_cancel_timeout": "O axente non respondeu a Deter e desconectouse. Volve conectar para continuar.",
|
||||
"ui.agent_chat_cannot_start": "Instalado, pero non se inicia: {error}",
|
||||
"ui.agent_chat_changes": "Cambios: {n}, un só paso de desfacer",
|
||||
"ui.agent_chat_changes_n": "{n} cambios",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Orde do axente",
|
||||
"ui.agent_chat_connect": "Conectar",
|
||||
"ui.agent_chat_copy": "Copiar",
|
||||
"ui.agent_chat_copy_message": "Copiar a mensaxe",
|
||||
"ui.agent_chat_ctx_bodies": "Corpos",
|
||||
"ui.agent_chat_ctx_elements": "Elementos",
|
||||
"ui.agent_chat_ctx_features": "Operacións",
|
||||
"ui.agent_chat_ctx_more": "+{n} máis",
|
||||
"ui.agent_chat_ctx_other": "Outros",
|
||||
"ui.agent_chat_custom_heading": "Modelo personalizado",
|
||||
"ui.agent_chat_custom_hint": "Calquera programa que fale o Agent Client Protocol por stdin e stdout. Non se executa en ningún shell.",
|
||||
"ui.agent_chat_denied": "Denegado",
|
||||
"ui.agent_chat_deny": "Denegar",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Todo",
|
||||
"ui.agent_chat_mode_ask": "Preguntar",
|
||||
"ui.agent_chat_mode_edits": "Edicións",
|
||||
"ui.agent_chat_model_custom": "Usar o personalizado: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} non pode usar as ferramentas de SolidCraft, así que só pode conversar. Escolle un modelo que admita ferramentas.",
|
||||
"ui.agent_chat_model_rejected": "O axente non aceptou {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Buscar modelos…",
|
||||
"ui.agent_chat_model_tip": "O modelo que usa o axente. A túa escolla lémbrase para este axente.",
|
||||
"ui.agent_chat_models_failed": "Non se puideron listar os modelos: {error}",
|
||||
"ui.agent_chat_models_heading": "Modelos dispoñibles",
|
||||
"ui.agent_chat_models_listing": "Listando os modelos…",
|
||||
"ui.agent_chat_models_none": "O punto de acceso non lista ningún modelo",
|
||||
"ui.agent_chat_models_not_a_list": "A resposta non era unha lista de modelos",
|
||||
"ui.agent_chat_new": "Chat novo",
|
||||
"ui.agent_chat_new_confirm": "Comezar un chat novo? Borrarase o actual.",
|
||||
"ui.agent_chat_next_picture": "Imaxe seguinte",
|
||||
@@ -4472,21 +4407,14 @@
|
||||
"ui.construction.contact_hint": "Tanxente: escolle un círculo/arco ou unha spline/cónica aberta e despois o punto de tanxencia (curvas de forma libre: extremos)",
|
||||
"ui.construction.external_hint": "Tanxente: escolle un círculo ou arco, un punto exterior e despois a solución desexada",
|
||||
"ui.construction.free_hint": "Recta de construción: fai clic en dous puntos; clic dereito para reiniciar",
|
||||
"ui.construction.normal_needs_length": "Unha recta infinita é sempre de construción: escribe unha lonxitude para debuxar unha liña normal",
|
||||
"ui.construction.point_hint": "Recta de construción: fai clic no punto polo que debe pasar",
|
||||
"ui.construction.reference_hint": "Recta de construción: escolle unha recta e despois un punto; clic dereito para cambiar a referencia",
|
||||
"ui.construction.ribbon_label": "Liñas aux.",
|
||||
"ui.construction.side_hint": "Recta de construción: fai clic no lado cara ao que segue a recta; clic dereito para comezar de novo",
|
||||
"ui.context_menu": "Menú contextual",
|
||||
"ui.continuity_crease": "Aresta",
|
||||
"ui.continuity_curvature": "Curvatura",
|
||||
"ui.continuity_curvature_change": "Cambio de curvatura",
|
||||
"ui.continuity_g0": "G0 aresta",
|
||||
"ui.continuity_g1": "G1 tanxente",
|
||||
"ui.continuity_g2": "G2 curvatura",
|
||||
"ui.continuity_gap": "Separación",
|
||||
"ui.continuity_position": "Posición",
|
||||
"ui.continuity_tangent": "Tanxente",
|
||||
"ui.copy": "Copiar",
|
||||
"ui.corner": "Esquina",
|
||||
"ui.corners": "Esquinas",
|
||||
@@ -4747,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Ocultar a orixe",
|
||||
"ui.hidePlanes": "Ocultar os planos",
|
||||
"ui.hideProfile": "Ocultar o perfil",
|
||||
"ui.hide_component_history": "Ocultar o seu historial",
|
||||
"ui.hide_origin_by_default": "Ocultar a orixe de forma predeterminada",
|
||||
"ui.hide_origin_note": "Os deseños novos comezan co cartafol Orixe oculto. Un deseño conserva o seu propio axuste de Orixe cando o mostras ou o ocultas.",
|
||||
"ui.history_back": "Retroceder un paso",
|
||||
@@ -4755,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "fai dobre clic na aresta",
|
||||
"ui.history_first": "Ir ao comezo",
|
||||
"ui.history_forward": "Avanzar un paso",
|
||||
"ui.history_hide_hidden": "Ocultar os compoñentes ocultos",
|
||||
"ui.history_hide_names": "Ocultar os nomes",
|
||||
"ui.history_last": "Ir ao final",
|
||||
"ui.history_marker_tip": "Marcador de historial: arrastra para retroceder o modelo; as operacións novas van aquí",
|
||||
@@ -4765,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Mostrar o botón Reproducir",
|
||||
"ui.history_play_finish_action": "Remata a acción actual antes de reproducir o historial",
|
||||
"ui.history_play_roll_back": "Move o marcador do historial cara atrás antes de reproducir",
|
||||
"ui.history_show_hidden": "Mostrar os compoñentes ocultos",
|
||||
"ui.history_show_names": "Mostrar os nomes",
|
||||
"ui.history_sketch_listed_under": "Listado en {feature}",
|
||||
"ui.history_title": "Historial",
|
||||
"ui.history_toggle_names": "Mostrar ou ocultar os nomes das operacións",
|
||||
"ui.history_waits_for_editor": "Desfacer, Refacer e Historial agardan ata que se abra o editor da operación",
|
||||
"ui.hole": "Furado",
|
||||
"ui.hole_tap_type": "Tipo de roscado do furado",
|
||||
"ui.hole_type": "Tipo de furado",
|
||||
@@ -4786,7 +4710,6 @@
|
||||
"ui.insert_design.saved_file": "{name} ten cambios sen gardar, así que se inseriu o seu ficheiro gardado. Gárdao e despois usa Actualizar as ligazóns para incorporar os cambios.",
|
||||
"ui.insert_design.unsaved": "Cambios sen gardar",
|
||||
"ui.inspect": "Inspeccionar",
|
||||
"ui.instances": "Instancias",
|
||||
"ui.intersect": "Intersecar",
|
||||
"ui.isolate": "Illar",
|
||||
"ui.join": "Unir",
|
||||
@@ -5051,7 +4974,6 @@
|
||||
"ui.showDimension": "Mostrar a cota",
|
||||
"ui.showOrigin": "Mostrar a orixe",
|
||||
"ui.showPlanes": "Mostrar os planos",
|
||||
"ui.show_component_history": "Mostrar o seu historial",
|
||||
"ui.show_constraints": "Restricións",
|
||||
"ui.show_construction_geometries": "Xeometrías de construción",
|
||||
"ui.show_dimensions": "Cotas",
|
||||
@@ -5234,7 +5156,7 @@
|
||||
"update.check_unavailable": "A busca de actualizacións non está dispoñible para esta instalación.",
|
||||
"update.checking": "Comprobando…",
|
||||
"update.copy": "Copiar",
|
||||
"update.dmg_opened": "A imaxe de disco está aberta e verificada: sae de SolidCraft, arrastra o novo SolidCraft a Aplicacións, substituíndo a copia antiga, e despois volve abrir SolidCraft.",
|
||||
"update.dmg_opened": "A imaxe de disco está aberta e verificada: arrastra SolidCraft a Aplicacións, substituíndo a copia antiga, e despois reinicia SolidCraft.",
|
||||
"update.download": "Descargar",
|
||||
"update.download_open": "Descargar e abrir",
|
||||
"update.downloading": "Descargando e verificando… {done} de {total} MB",
|
||||
@@ -5278,7 +5200,6 @@
|
||||
"web.open_link": "Abrir o deseño ligado?",
|
||||
"web.open_link_confirm": "Hai cambios sen gardar. Abrir o deseño ligado nunha pestana nova?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ escoller · Intro / Tab completar · Esc pechar",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ escoller · Enter aplica este valor",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ escoller · Enter aplica o que escribiches · Esc pechar",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter continúa en GitHub."
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Pravokutni",
|
||||
"Rectangular Tube": "Pravokutna cijev",
|
||||
"Reference face": "Referentno lice",
|
||||
"Reference plane": "Referentna ravnina",
|
||||
"Relief Depth": "Dubina rasterećenja",
|
||||
"Relief Width": "Širina rasterećenja",
|
||||
"Remove Tools": "Ukloni alate",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Presjek",
|
||||
"Section (optional)": "Presjek (neobavezno)",
|
||||
"Section position": "Položaj presjeka",
|
||||
"Sections": "Presjeci",
|
||||
"Sections (one sketch each)": "Presjeci (po jedna skica)",
|
||||
"Select Other": "Odaberi drugo",
|
||||
"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).": "Odaberi drugo: držite lijevu tipku mirno na modelu (ili desni klik › Odaberi drugo…) kako biste prikazali sve ispod pokazivača; ponovno kliknite na isto mjesto kako biste odabrali sljedeći element. Dvaput kliknite srednjom tipkom za Početni prikaz (Postavke › Navigacija).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Izdvoji u datoteku",
|
||||
"cmd.component_finish_edit": "Završi uređivanje",
|
||||
"cmd.component_from_bodies": "Izradi komponente iz tijela",
|
||||
"cmd.component_hide_history": "Sakrij vremensku traku komponente",
|
||||
"cmd.component_insert_design": "Umetni dizajn",
|
||||
"cmd.component_links": "Popis veza",
|
||||
"cmd.component_list": "Popis komponenti",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Preimenuj komponentu",
|
||||
"cmd.component_replace_with_link": "Zamijeni vezom",
|
||||
"cmd.component_revert_position": "Vrati položaj",
|
||||
"cmd.component_show_history": "Prikaži vremensku traku komponente",
|
||||
"cmd.component_unlock": "Otključaj sadržaj",
|
||||
"cmd.component_update_links": "Ažuriraj veze",
|
||||
"cmd.config_activate": "Aktiviraj konfiguraciju",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Pravilo konfiguracije",
|
||||
"cmd.config_rules": "Pravila konfiguracije",
|
||||
"cmd.config_table": "Prikaži tablicu konfiguracija",
|
||||
"cmd.construct.plane.axis_point": "Ravnina kroz os i točku",
|
||||
"cmd.construct_axis_edge": "Os kroz brid",
|
||||
"cmd.construct_axis_normal_to_face": "Os okomita na lice",
|
||||
"cmd.construct_axis_through_cylinder": "Os kroz valjak/stožac/torus",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Prikaži rastavljeni prikaz",
|
||||
"cmd.expr_evaluate": "Izračunaj izraz",
|
||||
"cmd.face_delete": "Izbriši lice",
|
||||
"cmd.file.export_3mf": "Izvezi 3MF",
|
||||
"cmd.file.export_obj": "Izvezi OBJ",
|
||||
"cmd.file.export_step": "Izvezi STEP",
|
||||
"cmd.file.export_stl": "Izvezi STL",
|
||||
"cmd.file.export_urdf": "Izvezi URDF",
|
||||
"cmd.file_export": "Izvoz",
|
||||
"cmd.file_insert_mesh": "Umetni mrežu",
|
||||
"cmd.file_insert_step": "Umetni STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Analiza dostupnosti",
|
||||
"cmd.inspect_center_of_mass": "Središte mase",
|
||||
"cmd.inspect_clearance": "Provjera zazora",
|
||||
"cmd.inspect_colored_components": "Omogući boje komponenti",
|
||||
"cmd.inspect_colored_components_disable": "Onemogući boje komponenti",
|
||||
"cmd.inspect_compare": "Usporedi tijela",
|
||||
"cmd.inspect_compare_picture": "Usporedi sliku",
|
||||
"cmd.inspect_continuity": "Analiza kontinuiteta",
|
||||
"cmd.inspect_curvature_comb": "Analiza češlja zakrivljenosti",
|
||||
"cmd.inspect_curvature_map": "Analiza mape zakrivljenosti",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Uredi oblak točaka",
|
||||
"cmd.pointcloud_insert": "Umetni oblak točaka",
|
||||
"cmd.pointcloud_list": "Oblaci točaka",
|
||||
"cmd.render.compare.divider": "Pomakni razdjelnik usporedbe renderiranja",
|
||||
"cmd.render.result.discard": "Odbaci rezultat renderiranja",
|
||||
"cmd.render.result.select": "Odaberi rezultat renderiranja",
|
||||
"cmd.render_aging_describe": "Slojevito starenje površine",
|
||||
"cmd.render_backends": "Pozadinski sustavi renderiranja",
|
||||
"cmd.render_batch": "Renderiraj sve kadrove",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Pomakni oznaku vraćanja",
|
||||
"cmd.timeline_stop": "Zaustavi vremensku traku",
|
||||
"cmd.timeline_suppress": "Potisni značajku",
|
||||
"cmd.ui.assistant": "Prikaži ili sakrij Asistenta",
|
||||
"cmd.view.render_browser": "Stablo renderiranja",
|
||||
"cmd.view.render_dock": "Premjesti panel renderiranja",
|
||||
"cmd.view.render_focus": "Fokusni prikaz renderiranja",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Alat za navigaciju",
|
||||
"cmd.view_top": "Pogled odozgo",
|
||||
"cmd.view_visual_style": "Vizualni stil",
|
||||
"color.blue": "Plava",
|
||||
"color.copy": "Kliknite za kopiranje vrijednosti boje",
|
||||
"color.edit": "Kliknite za uređivanje boje",
|
||||
"color.gamma": "Prikaz vrijednosti boje u gama prostoru 0-255",
|
||||
"color.green": "Zelena",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Unesite #rrggbb ili #rrggbbaa; šest znamenki zadržava neprozirnost",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Prikaz vrijednosti boje u linearnom prostoru 0-1",
|
||||
"color.recent": "Nedavne boje",
|
||||
"color.recent_help": "Ovdje se pojavljuju boje koje odaberete u bilo kojem polju za boju",
|
||||
"color.red": "Crvena",
|
||||
"color.saturation": "Zasićenost (postotak)",
|
||||
"color.saturation_value": "Zasićenost i vrijednost",
|
||||
"color.selected": "Odabrana boja",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Povećaj za korak",
|
||||
"color.value": "Vrijednost, svjetlina (postotak)",
|
||||
"config.column_filter_empty": "Nema odgovarajućih stupaca",
|
||||
"config.rule_parameter_columns": "Navedeni su samo parametri koji imaju stupac u tablici. Dodajte stupac pomoću Dodaj stupac (+) kako biste parametar mogli upotrijebiti ovdje.",
|
||||
"config.rule_suppression_columns": "Navedene su samo značajke koje imaju stupac Potisni u tablici. Dodajte stupac pomoću Dodaj stupac (+) kako biste ovdje mogli upravljati značajkom.",
|
||||
"contact.components_count": "Komponente: {count}",
|
||||
"contact.enabled_label": "omogućeno",
|
||||
"coupling.gear_1": "Zupčanik 1",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Odaberite tijela ili značajke: skica nije ni jedno ni drugo. Ponovite njezine krivulje pomoću alata Niz ili Zrcali u samoj skici ili je najprije ekstrudirajte.",
|
||||
"dialog.select": "↖ Odaberi",
|
||||
"dialog.selected_count": "↖ Odabrano: {count}",
|
||||
"dialog.selection_constraint": "Ograničenje {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · krivulja {id}",
|
||||
"dialog.selection_guidance": "Odaberite ulaz kako biste ga promijenili. Kliknite odabrane objekte kako biste ih uklonili; Ctrl/Cmd-klik ih također uključuje i isključuje. Pogledajte pretpregled, a zatim pritisnite U redu.",
|
||||
"dialog.selection_midpoint": "Središnja točka · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step} od {total}: Odaberite {input}",
|
||||
"dialog.step": "Korak",
|
||||
"document.changed_on_disk": "{name} je promijenjen na disku",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Izrađuje pomoćnu os kroz dvije točke, vrhove ili točke skice. Upotrijebite je za os koja prolazi između dvaju poznatih položaja.",
|
||||
"help.construct.plane.along_path": "Izrađuje pomoćnu ravninu okomitu na brid ili krivulju, na položaju duž njih. Upotrijebite je za skiciranje profila za povlačenje ili presjeka putanje.",
|
||||
"help.construct.plane.at_angle": "Izrađuje pomoćnu ravninu zakrenutu za kut u odnosu na ravninu ili lice, oko osi ili brida. Upotrijebite je za nagnute skice, primjerice kutnog nosača.",
|
||||
"help.construct.plane.axis_point": "Izrađuje pomoćnu ravninu koja sadrži os i točku izvan te osi. Odaberite os ishodišta ili pomoćnu os, ravni brid ili liniju skice, pa vrh, točku skice ili pomoćnu točku. Točka na osi ne može odrediti jedinstvenu ravninu.",
|
||||
"help.construct.plane.midplane": "Izrađuje pomoćnu ravninu na pola puta između dviju ravnina ili ravnih lica, ili ravninu koja raspolavlja kut kad se one sijeku pod kutom. Upotrijebite je kao ravninu simetrije za zrcaljenje.",
|
||||
"help.construct.plane.offset": "Izrađuje pomoćnu ravninu paralelnu s ravninom ili ravnim licem, na određenoj udaljenosti ili kroz točku. Upotrijebite je za skiciranje na zadanoj visini iznad lica.",
|
||||
"help.construct.plane.perpendicular": "Izrađuje pomoćnu ravninu koja sadrži liniju ili brid i okomita je na ravninu ili lice. Upotrijebite je za skiciranje uspravno na modelu duž brida.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Provjerite vrstu spoja (npr. Kruti), učvršćenje, granice spoja i vrijednosti kojima upravljaju parametri.",
|
||||
"joint_move.blocked_title": "Ovaj dio nije moguće pomaknuti",
|
||||
"joint_move.coarse": "Korak rotacije blizu osi (°)",
|
||||
"joint_move.conflict": "Jedan spoj ovog dijela u sukobu je s ostalima: {joint}.",
|
||||
"joint_move.conflict_hint": "Uredite, potisnite ili izbrišite spoj koji je u sukobu kako bi ostali spojevi mogli postaviti dio.",
|
||||
"joint_move.distance": "Udaljenost od početka",
|
||||
"joint_move.done": "Gotovo",
|
||||
"joint_move.fine": "Korak rotacije daleko od osi (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "Staza {number}",
|
||||
"nav.alias.help": "Shift+Alt+povlačenje lijevom tipkom orbitira, Shift+Alt+srednjom pomiče, Shift+Alt+desnom zumira; povlačenje srednjom tipkom također pomiče, a desnom orbitira",
|
||||
"nav.blender.help": "Povlačenje srednjom tipkom orbitira, Shift+srednja pomiče, Ctrl+srednja zumira; povlačenje desnom tipkom ne dira kameru",
|
||||
"nav.creo.help": "Povlačenje srednjom tipkom orbitira, Shift+srednja pomiče, Ctrl+srednja uz okomito povlačenje zumira (prema dolje za približavanje); kotačić također zumira",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "Miš i 3D miš rotiraju kroz oba pola i čuvaju nagib. Privlačenje osi tipkom Shift isključeno je.",
|
||||
"nav.free_orbit.label": "Slobodno orbitiranje",
|
||||
"nav.free_orbit.snap_help": "Miš i 3D miš rotiraju kroz oba pola i čuvaju nagib. Držite Shift tijekom povlačenja za orbitiranje kako biste privukli smjer pogleda i nagib.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Vijak s poluokruglom glavom i unutarnjim šesterokutom",
|
||||
"parts.family.dowel_pin": "Cilindrični zatik",
|
||||
"parts.family.hex_bolt": "Vijak sa šesterokutnom glavom",
|
||||
"parts.family.hex_cap_screw_inch": "Vijak sa šesterokutnom glavom (inč)",
|
||||
"parts.family.hex_nut": "Šesterokutna matica",
|
||||
"parts.family.hex_nut_inch": "Šesterokutna matica (inč)",
|
||||
"parts.family.set_screw": "Bezglavi vijak s unutarnjim šesterokutom, ravni vrh",
|
||||
"parts.family.socket_head_cap_screw": "Vijak s cilindričnom glavom i unutarnjim šesterokutom",
|
||||
"parts.family.washer": "Obična podloška",
|
||||
"parts.family.washer_sae": "Obična podloška, SAE (uska)",
|
||||
"parts.family.washer_uss": "Obična podloška, USS (široka)",
|
||||
"parts.holes": "Provrti (neobavezno)",
|
||||
"parts.placement_hint": "odaberite jedan ili više provrta kako biste u svaki smjestili dio, ili U redu postavlja dio u ishodište",
|
||||
"parts.system": "Sustav jedinica",
|
||||
"parts.system.inch": "Inčni",
|
||||
"parts.system.metric": "Metrički",
|
||||
"pattern.alignment_2": "Poravnanje 2",
|
||||
"pattern.alignment_hint": "Broj uključuje izvornik. Središte ravnomjerno raspoređuje kopije; Obrni bira dodatnu stranu za parne ukupne brojeve.",
|
||||
"pattern.direction_1": "Smjer 1",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Povlačenje lijevom tipkom",
|
||||
"prefs.button_middle": "Povlačenje srednjom tipkom",
|
||||
"prefs.button_right": "Povlačenje desnom tipkom",
|
||||
"prefs.carousel_next": "Sljedeće",
|
||||
"prefs.carousel_previous": "Prethodno",
|
||||
"prefs.choose_font": "Odaberi datoteku fonta…",
|
||||
"prefs.click": "Klik",
|
||||
"prefs.contact_shading": "Meko sjenčanje dodira",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Zakrivljene plohe",
|
||||
"prefs.curves": "Zakrivljene plohe",
|
||||
"prefs.cut_preview_tint": "Prikaži uklonjeni materijal u crvenoj boji",
|
||||
"prefs.cut_preview_tint_help": "Pregled ispunjava materijal koji značajka uklanja i oblik koji zamjenjuje crvenom prozirnom sjenom. Kada je isključeno, te su ispune potpuno prozirne i crtaju se samo njihovi obrisi.",
|
||||
"prefs.dark_parts_shading": "Zadrži oblik tamnih dijelova",
|
||||
"prefs.dark_parts_shading_help": "Sjenča prikaz u linearnom svjetlu uz meko dopunsko svjetlo neba, tako da tamna lica zadrže gradijent umjesto da pocrne",
|
||||
"prefs.dead_zone_note": "Šrafirana traka je mrtva zona: kretanje unutar nje se zanemaruje.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Jezik sučelja",
|
||||
"prefs.interface_note": "Kako SolidCraft izgleda i gdje su njegovi dijelovi.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Obrni zakrivljene strelice za valjanje",
|
||||
"prefs.invert_curved_roll_arrows_note": "Dvije zakrivljene strelice u kutu kocke valjaju prikaz unutar ravnine zaslona. Uključeno: strelica u smjeru kazaljke na satu valja prikaz suprotno od smjera kazaljke na satu, a strelica suprotno od smjera kazaljke na satu valja ga u smjeru kazaljke na satu. Isključeno: svaka strelica valja prikaz onako kako je zakrivljena.",
|
||||
"prefs.invert_navigator_arrows": "Obrni strelice za rotaciju kocke prikaza",
|
||||
"prefs.invert_navigator_arrows_note": "Strelice oko kocke orbitiraju kameru u smjeru u kojem pokazuju, kao u programima Fusion i Inventor: strelica s lijeve strane pokazuje udesno i zakreće kameru udesno. Isključeno: strelica okreće kocku prema strani na kojoj se nalazi, pa lijeva strelica zakreće kameru ulijevo. Postavka ne utječe na zakrivljene strelice za valjanje.",
|
||||
"prefs.keep_work_on_tab_switch": "Zadrži dijaloge i alate otvorenima pri prebacivanju između dizajna",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Postavljanje",
|
||||
"prefs.prefer_free_sketch_points": "Daj prednost pomičnim točkama skice kad se točke preklapaju",
|
||||
"prefs.preview_choices": "Pregled vaših odabira",
|
||||
"prefs.preview_tint_new_faces": "Oboji novu plohu u pregledu",
|
||||
"prefs.preview_tint_new_faces_help": "Pregled značajke koja mijenja tijelo može nacrtati plohu koju dodaje u boji pregleda. Kada je isključeno, tijelo se crta kakvo jest.",
|
||||
"prefs.print_face_highlight": "Isticanje plohe ispisa",
|
||||
"prefs.print_face_highlight_tip": "Koliko se jako ploha ispisa tijela boji i obrubljuje u prikazu. 0 % ne prikazuje isticanje.",
|
||||
"prefs.profile_active": "Aktivni profil",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Osnovna boja · sRGB",
|
||||
"render.base_metallic": "Metalni udio",
|
||||
"render.base_opaque_only": "Ovaj uređivač čuva semantiku neprozirnih tijela. Za optičko propuštanje upotrijebite izričite kontrole stakla.",
|
||||
"render.batch_all_shots": "Svi kadrovi ({count})",
|
||||
"render.batch_empty": "Spremite kadar prije renderiranja svih kadrova.",
|
||||
"render.batch_limit": "Renderiranje svih kadrova ograničeno je na 32 kadra.",
|
||||
"render.batch_status": "U tijeku: {running} · Na čekanju: {waiting}",
|
||||
"render.bloom_enable": "Sjaj prikaza",
|
||||
"render.bloom_help": "Kreativni sjaj samo na izlazu iz pozitivnih linearnih svijetlih dijelova, raširen kroz tri normalizirana prolaza kutijastog filtra. Polumjer je u pikselima izlaza. Dodaje se u linearnoj svjetlosti prikaza prije sRGB-a; nije objektiv, volumetrijski efekt niti poboljšanje konvergencije. Sirovi EXR ostaje nepromijenjen.",
|
||||
"render.bloom_opaque": "Sjaj zahtijeva neprozirni izlaz. Prozirni PNG odbija se, a ne spljoštava tiho.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Najprije odaberite izgled u Izgledima",
|
||||
"render.chromatic_offset": "Kromatski pomak rubova",
|
||||
"render.clear_stage": "Ukloni spremljenu scenu",
|
||||
"render.compare_divider": "Razdjelnik usporedbe",
|
||||
"render.compare_live": "Uživo",
|
||||
"render.compare_live_preview": "Pregled uživo",
|
||||
"render.compare_render": "Renderiranje {number}",
|
||||
"render.compare_side": "Renderiranje {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · zabilježeno u reviziji {revision}",
|
||||
"render.compare_swap": "Zamijeni strane",
|
||||
"render.copy_request": "Kopiraj JSON",
|
||||
"render.cpu_aa_help": "Prostorni uzorci: više uzoraka poboljšava rubove i produljuje renderiranje.",
|
||||
"render.cpu_area_add": "Dodaj pravokutno CPU svjetlo",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Prikazuje se posljednja dovršena slika; novije renderiranje još je nije zamijenilo.",
|
||||
"render.result_sequence": "Kontrolna točka",
|
||||
"render.result_title": "Rezultat renderiranja",
|
||||
"render.results_choose": "Zadržani rezultati",
|
||||
"render.results_label": "Renderiranje {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [zastarjelo]",
|
||||
"render.results_unnamed": "Slika",
|
||||
"render.return_design": "Natrag na Dizajn",
|
||||
"render.review_stale": "Ovaj pregled je zastario. Riješite ga ili ponovno provjerite prije primjene.",
|
||||
"render.ribbon_aging": "Starenje",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "oba ishodišta nalaze se na istoj komponenti",
|
||||
"thread.automatic_size": "Automatska veličina",
|
||||
"thread.designation": "Oznaka",
|
||||
"thread.direction": "Smjer navoja",
|
||||
"thread.iso_metric": "ISO metrički",
|
||||
"thread.left_hand": "Lijevi",
|
||||
"thread.right_hand": "Desni",
|
||||
"thread.select_size": "Odaberite veličinu",
|
||||
"timeline.delete_after_marker_one": "Izbriši 1 značajku nakon oznake vraćanja",
|
||||
"timeline.delete_after_marker_other": "Izbriši značajke nakon oznake vraćanja: {count}",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Pokrenite ovo u terminalu, a zatim pritisnite Poveži ponovno:",
|
||||
"ui.agent_chat_auth_title": "Prijavi se kod agenta",
|
||||
"ui.agent_chat_cancel": "Odustani",
|
||||
"ui.agent_chat_cancel_timeout": "Agent nije odgovorio na Zaustavi pa je veza s njim prekinuta. Ponovno se povežite kako biste nastavili.",
|
||||
"ui.agent_chat_cannot_start": "Instalirano, ali se ne pokreće: {error}",
|
||||
"ui.agent_chat_changes": "Promjene: {n}, jedan korak poništavanja",
|
||||
"ui.agent_chat_changes_n": "Promjene: {n}",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Naredba agenta",
|
||||
"ui.agent_chat_connect": "Poveži",
|
||||
"ui.agent_chat_copy": "Kopiraj",
|
||||
"ui.agent_chat_copy_message": "Kopiraj poruku",
|
||||
"ui.agent_chat_ctx_bodies": "Tijela",
|
||||
"ui.agent_chat_ctx_elements": "Elementi",
|
||||
"ui.agent_chat_ctx_features": "Značajke",
|
||||
"ui.agent_chat_ctx_more": "+još {n}",
|
||||
"ui.agent_chat_ctx_other": "Ostalo",
|
||||
"ui.agent_chat_custom_heading": "Prilagođeni model",
|
||||
"ui.agent_chat_custom_hint": "Bilo koji program koji govori Agent Client Protocol na stdin i stdout. Ne pokreće ga nikakva ljuska.",
|
||||
"ui.agent_chat_denied": "Odbijeno",
|
||||
"ui.agent_chat_deny": "Odbij",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Sve",
|
||||
"ui.agent_chat_mode_ask": "Pitaj",
|
||||
"ui.agent_chat_mode_edits": "Izmjene",
|
||||
"ui.agent_chat_model_custom": "Koristi prilagođeni: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} ne može koristiti SolidCraft alate, pa može samo razgovarati. Odaberite model s podrškom za alate.",
|
||||
"ui.agent_chat_model_rejected": "Agent nije prihvatio model {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Pretraži modele…",
|
||||
"ui.agent_chat_model_tip": "Model koji agent koristi. Vaš odabir pamti se za ovog agenta.",
|
||||
"ui.agent_chat_models_failed": "Popis modela nije moguće učitati: {error}",
|
||||
"ui.agent_chat_models_heading": "Dostupni modeli",
|
||||
"ui.agent_chat_models_listing": "Učitavanje popisa modela…",
|
||||
"ui.agent_chat_models_none": "Krajnja točka ne navodi nijedan model",
|
||||
"ui.agent_chat_models_not_a_list": "Odgovor nije popis modela",
|
||||
"ui.agent_chat_new": "Novi razgovor",
|
||||
"ui.agent_chat_new_confirm": "Započeti novi razgovor? Trenutni će biti obrisan.",
|
||||
"ui.agent_chat_next_picture": "Sljedeća slika",
|
||||
@@ -4472,11 +4407,9 @@
|
||||
"ui.construction.contact_hint": "Tangenta: odaberite kružnicu/luk ili otvoreni spline/koniku, pa točku dodira (krivulje slobodnog oblika: krajnje točke)",
|
||||
"ui.construction.external_hint": "Tangenta: odaberite kružnicu ili luk, vanjsku točku, pa željeno rješenje",
|
||||
"ui.construction.free_hint": "Pomoćni pravac: kliknite dvije točke; desnim klikom počnite ispočetka",
|
||||
"ui.construction.normal_needs_length": "Beskonačna linija uvijek je pomoćna: upišite duljinu kako biste nacrtali običnu liniju",
|
||||
"ui.construction.point_hint": "Pomoćni pravac: kliknite točku kroz koju prolazi",
|
||||
"ui.construction.reference_hint": "Pomoćni pravac: odaberite ravnu liniju, pa točku; desnim klikom promijenite referencu",
|
||||
"ui.construction.ribbon_label": "Pomoćne",
|
||||
"ui.construction.side_hint": "Pomoćni pravac: kliknite stranu prema kojoj pravac ide; desnim klikom počnite ispočetka",
|
||||
"ui.context_menu": "Kontekstni izbornik",
|
||||
"ui.continuity_g0": "G0 pregib",
|
||||
"ui.continuity_g1": "G1 tangentno",
|
||||
@@ -4742,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Sakrij ishodište",
|
||||
"ui.hidePlanes": "Sakrij ravnine",
|
||||
"ui.hideProfile": "Sakrij profil",
|
||||
"ui.hide_component_history": "Sakrij njezinu vremensku traku",
|
||||
"ui.hide_origin_by_default": "Zadano sakrij ishodište",
|
||||
"ui.hide_origin_note": "Novi dizajni počinju sa skrivenom mapom Ishodište. Dizajn zadržava vlastitu postavku Ishodišta kad ga prikažete ili sakrijete.",
|
||||
"ui.history_back": "Idi korak natrag",
|
||||
@@ -4750,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "dvaput kliknite brid",
|
||||
"ui.history_first": "Idi na početak",
|
||||
"ui.history_forward": "Idi korak naprijed",
|
||||
"ui.history_hide_hidden": "Sakrij skrivene komponente",
|
||||
"ui.history_hide_names": "Sakrij nazive",
|
||||
"ui.history_last": "Idi na kraj",
|
||||
"ui.history_marker_tip": "Oznaka vraćanja: povucite kako biste vratili model unatrag; nove značajke dolaze ovdje",
|
||||
@@ -4760,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Prikaži tipku za reprodukciju",
|
||||
"ui.history_play_finish_action": "Završite trenutnu radnju prije reprodukcije vremenske trake",
|
||||
"ui.history_play_roll_back": "Pomaknite oznaku vraćanja unatrag prije reprodukcije",
|
||||
"ui.history_show_hidden": "Prikaži skrivene komponente",
|
||||
"ui.history_show_names": "Prikaži nazive",
|
||||
"ui.history_sketch_listed_under": "Navedeno ispod {feature}",
|
||||
"ui.history_title": "Vremenska traka",
|
||||
"ui.history_toggle_names": "Prikaži ili sakrij nazive značajki",
|
||||
"ui.history_waits_for_editor": "Poništi, Ponovi i vremenska traka čekaju dok se ne otvori uređivač značajke",
|
||||
"ui.hole": "Provrt",
|
||||
"ui.hole_tap_type": "Vrsta navoja provrta",
|
||||
"ui.hole_type": "Vrsta provrta",
|
||||
@@ -5046,7 +4975,6 @@
|
||||
"ui.showDimension": "Prikaži kotu",
|
||||
"ui.showOrigin": "Prikaži ishodište",
|
||||
"ui.showPlanes": "Prikaži ravnine",
|
||||
"ui.show_component_history": "Prikaži njezinu vremensku traku",
|
||||
"ui.show_constraints": "Ograničenja",
|
||||
"ui.show_construction_geometries": "Pomoćna geometrija",
|
||||
"ui.show_dimensions": "Kote",
|
||||
@@ -5273,7 +5201,6 @@
|
||||
"web.open_link": "Otvoriti povezani dizajn?",
|
||||
"web.open_link_confirm": "Postoje nespremljene promjene. Otvoriti povezani dizajn u novoj kartici?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ odaberi · Enter / Tab dovrši · Esc odbaci",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ odabir · Enter primjenjuje ovu vrijednost",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ odaberi · Enter primjenjuje ono što ste upisali · Esc odbaci",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter nastavlja na GitHubu."
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Téglalap alakú",
|
||||
"Rectangular Tube": "Téglalap alakú cső",
|
||||
"Reference face": "Referencialap",
|
||||
"Reference plane": "Referenciasík",
|
||||
"Relief Depth": "Tehermentesítés mélysége",
|
||||
"Relief Width": "Tehermentesítés szélessége",
|
||||
"Remove Tools": "Szerszámok eltávolítása",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Metszet",
|
||||
"Section (optional)": "Keresztmetszet (nem kötelező)",
|
||||
"Section position": "Szelvény helyzete",
|
||||
"Sections": "Keresztmetszetek",
|
||||
"Sections (one sketch each)": "Keresztmetszetek (egy-egy vázlat)",
|
||||
"Select Other": "Másik kijelölése",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Más kijelölése: tartsa nyomva a bal gombot egy helyen a modellen (vagy jobb kattintás › Más kijelölése…) a kurzor alatti elemek listázásához; kattintson újra ugyanarra a helyre a következő elem kijelöléséhez. A középső gomb dupla kattintása a kezdőnézetre vált (Beállítások › Navigáció).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Kivonás fájlba",
|
||||
"cmd.component_finish_edit": "Szerkesztés befejezése",
|
||||
"cmd.component_from_bodies": "Komponensek létrehozása testekből",
|
||||
"cmd.component_hide_history": "Komponens előzményeinek elrejtése",
|
||||
"cmd.component_insert_design": "Terv beszúrása",
|
||||
"cmd.component_links": "Kapcsolatok listázása",
|
||||
"cmd.component_list": "Komponensek listázása",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Komponens átnevezése",
|
||||
"cmd.component_replace_with_link": "Csere hivatkozásra",
|
||||
"cmd.component_revert_position": "Pozíció visszaállítása",
|
||||
"cmd.component_show_history": "Komponens előzményeinek megjelenítése",
|
||||
"cmd.component_unlock": "Tartalom feloldása",
|
||||
"cmd.component_update_links": "Kapcsolatok frissítése",
|
||||
"cmd.config_activate": "Konfiguráció aktiválása",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Konfigurációs szabály",
|
||||
"cmd.config_rules": "Konfigurációs szabályok",
|
||||
"cmd.config_table": "Konfigurációs táblázat megjelenítése",
|
||||
"cmd.construct.plane.axis_point": "Sík tengelyen és ponton át",
|
||||
"cmd.construct_axis_edge": "Tengely élen át",
|
||||
"cmd.construct_axis_normal_to_face": "Lapra merőleges tengely",
|
||||
"cmd.construct_axis_through_cylinder": "Tengely hengeren/kúpon/tóruszon át",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Robbantott nézet megjelenítése",
|
||||
"cmd.expr_evaluate": "Kifejezés kiértékelése",
|
||||
"cmd.face_delete": "Lap törlése",
|
||||
"cmd.file.export_3mf": "3MF exportálása",
|
||||
"cmd.file.export_obj": "OBJ exportálása",
|
||||
"cmd.file.export_step": "STEP exportálása",
|
||||
"cmd.file.export_stl": "STL exportálása",
|
||||
"cmd.file.export_urdf": "URDF exportálása",
|
||||
"cmd.file_export": "Exportálás",
|
||||
"cmd.file_insert_mesh": "Háló beszúrása",
|
||||
"cmd.file_insert_step": "STEP beszúrása",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Hozzáférhetőség-elemzés",
|
||||
"cmd.inspect_center_of_mass": "Tömegközéppont",
|
||||
"cmd.inspect_clearance": "Hézag",
|
||||
"cmd.inspect_colored_components": "Komponensszínek bekapcsolása",
|
||||
"cmd.inspect_colored_components_disable": "Komponensszínek kikapcsolása",
|
||||
"cmd.inspect_compare": "Testek összehasonlítása",
|
||||
"cmd.inspect_compare_picture": "Kép összehasonlítása",
|
||||
"cmd.inspect_continuity": "Folytonosságelemzés",
|
||||
"cmd.inspect_curvature_comb": "Görbületi fésű elemzése",
|
||||
"cmd.inspect_curvature_map": "Görbülettérkép-elemzés",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Pontfelhő szerkesztése",
|
||||
"cmd.pointcloud_insert": "Pontfelhő beszúrása",
|
||||
"cmd.pointcloud_list": "Pontfelhők",
|
||||
"cmd.render.compare.divider": "Renderösszehasonlító elválasztó mozgatása",
|
||||
"cmd.render.result.discard": "Renderelési eredmény elvetése",
|
||||
"cmd.render.result.select": "Renderelési eredmény kiválasztása",
|
||||
"cmd.render_aging_describe": "Rétegzett felületöregedés",
|
||||
"cmd.render_backends": "Renderelő motorok",
|
||||
"cmd.render_batch": "Összes felvétel renderelése",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Idővonal-marker mozgatása",
|
||||
"cmd.timeline_stop": "Előzmények leállítása",
|
||||
"cmd.timeline_suppress": "Elem elnyomása",
|
||||
"cmd.ui.assistant": "Asszisztens megjelenítése/elrejtése",
|
||||
"cmd.view.render_browser": "Renderböngésző",
|
||||
"cmd.view.render_dock": "Renderpanel áthelyezése",
|
||||
"cmd.view.render_focus": "Renderfókusz nézet",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Navigációs eszköz",
|
||||
"cmd.view_top": "Felülnézet",
|
||||
"cmd.view_visual_style": "Vizuális stílus",
|
||||
"color.blue": "Kék",
|
||||
"color.copy": "Kattintson a színértékek másolásához",
|
||||
"color.edit": "Kattintson a szín szerkesztéséhez",
|
||||
"color.gamma": "Színértékek megjelenítése 0–255 gamma térben",
|
||||
"color.green": "Zöld",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Adja meg: #rrggbb vagy #rrggbbaa; hat számjegy esetén az átlátszatlanság megmarad",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Színértékek megjelenítése 0–1 lineáris térben",
|
||||
"color.recent": "Legutóbbi színek",
|
||||
"color.recent_help": "A bármelyik színmezőben kiválasztott színek itt jelennek meg",
|
||||
"color.red": "Piros",
|
||||
"color.saturation": "Telítettség (százalék)",
|
||||
"color.saturation_value": "Telítettség és érték",
|
||||
"color.selected": "Kijelölt szín",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Lépés felfelé",
|
||||
"color.value": "Érték, a fényerő (százalék)",
|
||||
"config.column_filter_empty": "Nincs egyező oszlop",
|
||||
"config.rule_parameter_columns": "Csak azok a paraméterek szerepelnek, amelyeknek van oszlopa a táblázatban. Az Oszlop hozzáadása (+) paranccsal vehet fel egyet, hogy itt használhassa.",
|
||||
"config.rule_suppression_columns": "Csak azok az elemek szerepelnek, amelyeknek van Elnyomás oszlopa a táblázatban. Az Oszlop hozzáadása (+) paranccsal vehet fel egyet, hogy innen vezérelje.",
|
||||
"contact.components_count": "{count} komponens",
|
||||
"contact.enabled_label": "engedélyezve",
|
||||
"coupling.gear_1": "1. fogaskerék",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Válasszon testeket vagy elemeket: a vázlat egyik sem. Ismételje a görbéit a vázlat saját Minta vagy Tükrözés parancsával, vagy húzza ki előbb.",
|
||||
"dialog.select": "↖ Kijelölés",
|
||||
"dialog.selected_count": "↖ {count} kijelölve",
|
||||
"dialog.selection_constraint": "Kényszer {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · görbe {id}",
|
||||
"dialog.selection_guidance": "Válasszon ki egy bemenetet a módosításhoz. A kijelölt objektumokra kattintva eltávolíthatja őket; a Ctrl/Cmd-kattintás is váltja a kijelölést. Tekintse át az előnézetet, majd nyomja meg az OK gombot.",
|
||||
"dialog.selection_midpoint": "Felezőpont · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step}/{total}: {input} kijelölése",
|
||||
"dialog.step": "Lépés",
|
||||
"document.changed_on_disk": "A(z) {name} megváltozott a lemezen",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Szerkesztőtengelyt hoz létre két ponton, csúcson vagy vázlatponton át. Használja két ismert helyzet között húzódó tengelyhez.",
|
||||
"help.construct.plane.along_path": "Szerkesztősíkot hoz létre egy élre vagy görbére merőlegesen, annak egy adott helyén. Használja söpréshez való profil vagy egy útvonal keresztmetszetének megrajzolására.",
|
||||
"help.construct.plane.at_angle": "Szerkesztősíkot hoz létre, amely egy síktól vagy laptól egy tengely vagy él körül adott szöggel van elforgatva. Használja ferde vázlatokhoz, például szögben álló konzolhoz.",
|
||||
"help.construct.plane.axis_point": "Szerkesztősíkot hoz létre, amely tartalmaz egy tengelyt és egy, a tengelyen kívül eső pontot. Válasszon egy origótengelyt vagy szerkesztőtengelyt, egy egyenes élt vagy vázlatvonalat, majd egy csúcsot, vázlatpontot vagy szerkesztőpontot. A tengelyen lévő pont nem határoz meg egyértelmű síkot.",
|
||||
"help.construct.plane.midplane": "Szerkesztősíkot hoz létre két sík vagy sík lap között félúton, vagy, ha szögben találkoznak, felezve őket. Használja tükrözés szimmetriasíkjaként.",
|
||||
"help.construct.plane.offset": "Szerkesztősíkot hoz létre egy síkkal vagy sík lappal párhuzamosan, adott távolságra vagy egy ponton át. Használja, hogy egy lap fölött meghatározott magasságban rajzoljon vázlatot.",
|
||||
"help.construct.plane.perpendicular": "Szerkesztősíkot hoz létre, amely tartalmaz egy egyenest vagy élt, és merőleges egy síkra vagy lapra. Használja, hogy egy modellen egy él mentén függőlegesen álló vázlatot rajzoljon.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Ellenőrizze a csukló típusát (például Merev), a földhöz rögzítést, a csuklókorlátokat és a paraméterrel vezérelt értékeket.",
|
||||
"joint_move.blocked_title": "Ez az alkatrész nem mozgatható",
|
||||
"joint_move.coarse": "Forgatási lépés a tengely közelében (°)",
|
||||
"joint_move.conflict": "Ennek az alkatrésznek egy csuklója ütközik a többivel: {joint}.",
|
||||
"joint_move.conflict_hint": "Szerkessze, nyomja el vagy törölje az ütköző csuklót, hogy a többi csukló elhelyezhesse az alkatrészt.",
|
||||
"joint_move.distance": "Távolság a kezdőponttól",
|
||||
"joint_move.done": "Kész",
|
||||
"joint_move.fine": "Forgatási lépés a tengelytől távol (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "{number}. sáv",
|
||||
"nav.alias.help": "Shift+Alt+bal húzás: keringés, Shift+Alt+középső húzás: pásztázás, Shift+Alt+jobb húzás: nagyítás; a középső húzás is pásztáz, a jobb húzás is keringést végez",
|
||||
"nav.blender.help": "Középső húzás: keringés, Shift+középső húzás: pásztázás, Ctrl+középső húzás: nagyítás; a jobb húzás nem mozgatja a kamerát",
|
||||
"nav.creo.help": "Középső húzás: keringés, Shift+középső húzás: pásztázás, Ctrl+középső függőleges húzás: nagyítás (lefelé húzva közelít); az egérgörgő görgetése: nagyítás",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "Az egér és a 3D-egér mindkét póluson átforgat, és megőrzi az elfordulást. A Shiftre történő tengelyhez illesztés ki van kapcsolva.",
|
||||
"nav.free_orbit.label": "Szabad keringés",
|
||||
"nav.free_orbit.snap_help": "Az egér és a 3D-egér mindkét póluson átforgat, és megőrzi az elfordulást. Keringéskor tartsa lenyomva a Shiftet a nézetirány és az elfordulás illesztéséhez.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Belső kulcsnyílású lencsefejű csavar",
|
||||
"parts.family.dowel_pin": "Hengeres illesztőcsap",
|
||||
"parts.family.hex_bolt": "Hatlapfejű csavar",
|
||||
"parts.family.hex_cap_screw_inch": "Hatlapfejű csavar (hüvelykes)",
|
||||
"parts.family.hex_nut": "Hatlapú anya",
|
||||
"parts.family.hex_nut_inch": "Hatlapú anya (hüvelykes)",
|
||||
"parts.family.set_screw": "Belső kulcsnyílású szárnélküli csavar, lapos végű",
|
||||
"parts.family.socket_head_cap_screw": "Belső kulcsnyílású hengeresfejű csavar",
|
||||
"parts.family.washer": "Sima alátét",
|
||||
"parts.family.washer_sae": "Sima alátét, SAE (keskeny)",
|
||||
"parts.family.washer_uss": "Sima alátét, USS (széles)",
|
||||
"parts.holes": "Furatok (opcionális)",
|
||||
"parts.placement_hint": "jelöljön ki egy vagy több furatot, hogy mindegyikbe beillesszen egy alkatrészt, vagy az OK az origóba helyezi",
|
||||
"parts.system": "Mértékegység-rendszer",
|
||||
"parts.system.inch": "Hüvelyk",
|
||||
"parts.system.metric": "Metrikus",
|
||||
"pattern.alignment_2": "2. igazítás",
|
||||
"pattern.alignment_hint": "A darabszám tartalmazza az eredetit. A Közép kiegyensúlyozza a másolatokat; a Megfordítás páros összegnél kijelöli a többletoldalt.",
|
||||
"pattern.direction_1": "1. irány",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Húzás bal gombbal",
|
||||
"prefs.button_middle": "Húzás középső gombbal",
|
||||
"prefs.button_right": "Húzás jobb gombbal",
|
||||
"prefs.carousel_next": "Következő",
|
||||
"prefs.carousel_previous": "Előző",
|
||||
"prefs.choose_font": "Betűtípusfájl kiválasztása…",
|
||||
"prefs.click": "Kattintás",
|
||||
"prefs.contact_shading": "Lágy érintkezési árnyalás",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Görbült felületek",
|
||||
"prefs.curves": "Görbült felületek",
|
||||
"prefs.cut_preview_tint": "Az eltávolított anyag megjelenítése pirossal",
|
||||
"prefs.cut_preview_tint_help": "Az előnézet az elem által eltávolított anyagot és a lecserélt alakzatot piros, áttetsző szellemképpel tölti ki. Kikapcsolva ezek a kitöltések teljesen átlátszóak, és csak a körvonalaik láthatók.",
|
||||
"prefs.dark_parts_shading": "A sötét alkatrészek formájának megőrzése",
|
||||
"prefs.dark_parts_shading_help": "A nézet árnyalása lineáris fényben, lágy égbolt-kitöltéssel, hogy a sötét lapokon megmaradjon az átmenet, és ne váljanak feketévé",
|
||||
"prefs.dead_zone_note": "A sraffozott sáv a holtsáv: az azon belüli mozgást figyelmen kívül hagyja.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Felület nyelve",
|
||||
"prefs.interface_note": "A SolidCraft megjelenése és elemeinek elhelyezése.",
|
||||
"prefs.interval_minutes": "{n} perc",
|
||||
"prefs.invert_curved_roll_arrows": "Az íves gördülőnyilak megfordítása",
|
||||
"prefs.invert_curved_roll_arrows_note": "A nézetkocka sarkában lévő két íves nyíl a nézetet a képernyő síkjában gördíti. Be: az óramutató járásával megegyező nyíl ellentétes irányba gördít, az ellentétes nyíl pedig az óramutató járásával megegyező irányba. Ki: minden nyíl abba az irányba gördíti a nézetet, amerre ível.",
|
||||
"prefs.invert_navigator_arrows": "A nézetkocka forgatónyilainak megfordítása",
|
||||
"prefs.invert_navigator_arrows_note": "A kocka körüli nyilak úgy keringetik a kamerát, ahogy mutatnak, mint a Fusion és az Inventor esetében: a bal oldali nyíl jobbra mutat, és a kamerát jobbra lendíti. Ki: a nyíl a kockát afelé az oldal felé forgatja, amelyiken van, így a bal nyíl a kamerát balra lendíti. Az íves gördülőnyilakat ez nem érinti.",
|
||||
"prefs.keep_work_on_tab_switch": "A párbeszédablakok és eszközök nyitva tartása tervek közötti váltáskor",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Elhelyezés",
|
||||
"prefs.prefer_free_sketch_points": "Átfedő pontoknál a mozgatható vázlatpontok előnyben részesítése",
|
||||
"prefs.preview_choices": "A választások előnézete",
|
||||
"prefs.preview_tint_new_faces": "Az új felület színezése az előnézetben",
|
||||
"prefs.preview_tint_new_faces_help": "Egy testet módosító elem előnézete az általa hozzáadott felületet az előnézeti színnel rajzolhatja ki. Kikapcsolva a test önmagaként jelenik meg.",
|
||||
"prefs.print_face_highlight": "Nyomtatási lap kiemelése",
|
||||
"prefs.print_face_highlight_tip": "Milyen erősen színezi és körvonalazza a nézet a test nyomtatási lapját. 0 % esetén nincs kiemelés.",
|
||||
"prefs.profile_active": "Aktív profil",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Alapszín · sRGB",
|
||||
"render.base_metallic": "Fémes hányad",
|
||||
"render.base_opaque_only": "Ez a szerkesztő megőrzi az átlátszatlan test szemantikáját. Az optikai áteresztéshez használja a külön üvegvezérlőket.",
|
||||
"render.batch_all_shots": "Összes felvétel ({count})",
|
||||
"render.batch_empty": "Mentsen el egy felvételt, mielőtt az összes felvételt renderelné.",
|
||||
"render.batch_limit": "Az összes felvétel renderelése legfeljebb 32 felvételre korlátozott.",
|
||||
"render.batch_status": "{running} fut · {waiting} vár",
|
||||
"render.bloom_enable": "Bloom megjelenítése",
|
||||
"render.bloom_help": "Kreatív, csak a kimenetre vonatkozó fénykisugárzás a pozitív lineáris csúcsfényekből, három normalizált dobozszűrő-menettel szétterítve. A sugár kimeneti pixelben értendő. Lineáris megjelenítési fényhez adódik az sRGB előtt; nem lencsehatás, nem térfogati effektus és nem javítja a konvergenciát. A nyers EXR változatlan.",
|
||||
"render.bloom_opaque": "A bloomhoz átlátszatlan kimenet szükséges. Az átlátszó PNG-t a program elutasítja, nem lapítja csendben.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Először válasszon stílust a Megjelenések között",
|
||||
"render.chromatic_offset": "Kromatikus éleltolás",
|
||||
"render.clear_stage": "Mentett díszlet eltávolítása",
|
||||
"render.compare_divider": "Összehasonlító elválasztó",
|
||||
"render.compare_live": "Élő",
|
||||
"render.compare_live_preview": "Élő előnézet",
|
||||
"render.compare_render": "{number}. render",
|
||||
"render.compare_side": "{number}. render · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · rögzítve, revízió: {revision}",
|
||||
"render.compare_swap": "Csere",
|
||||
"render.copy_request": "JSON másolása",
|
||||
"render.cpu_aa_help": "Térbeli minták: a több minta javítja az éleket, és növeli a renderelési időt.",
|
||||
"render.cpu_area_add": "Téglalap alakú CPU-fényforrás hozzáadása",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Az utolsó kész kép látható; újabb renderelés még nem váltotta le.",
|
||||
"render.result_sequence": "Ellenőrzőpont",
|
||||
"render.result_title": "Renderelési eredmény",
|
||||
"render.results_choose": "Megtartott eredmények",
|
||||
"render.results_label": "{number}. render · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [elavult]",
|
||||
"render.results_unnamed": "Kép",
|
||||
"render.return_design": "Vissza a Tervezéshez",
|
||||
"render.review_stale": "Ez az áttekintés elavult. Az alkalmazás előtt oldja fel, vagy ellenőrizze újra.",
|
||||
"render.ribbon_aging": "Öregítés",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "a két origó ugyanazon a komponensen van",
|
||||
"thread.automatic_size": "Automatikus méret",
|
||||
"thread.designation": "Jelölés",
|
||||
"thread.direction": "Menet iránya",
|
||||
"thread.iso_metric": "ISO metrikus",
|
||||
"thread.left_hand": "Balmenetes",
|
||||
"thread.right_hand": "Jobbmenetes",
|
||||
"thread.select_size": "Méret kiválasztása",
|
||||
"timeline.delete_after_marker_one": "1 elem törlése az előzményjelölő után",
|
||||
"timeline.delete_after_marker_other": "{count} elem törlése az előzményjelölő után",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Futtassa ezt egy terminálban, majd nyomja meg újra a Csatlakozás gombot:",
|
||||
"ui.agent_chat_auth_title": "Bejelentkezés az ügynökbe",
|
||||
"ui.agent_chat_cancel": "Mégse",
|
||||
"ui.agent_chat_cancel_timeout": "Az ügynök nem válaszolt a Leállításra, ezért le lett választva. A folytatáshoz csatlakozzon újra.",
|
||||
"ui.agent_chat_cannot_start": "Telepítve van, de nem indul el: {error}",
|
||||
"ui.agent_chat_changes": "Módosítások: {n}, egy visszavonási lépés",
|
||||
"ui.agent_chat_changes_n": "{n} módosítás",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Ügynök parancsa",
|
||||
"ui.agent_chat_connect": "Csatlakozás",
|
||||
"ui.agent_chat_copy": "Másolás",
|
||||
"ui.agent_chat_copy_message": "Üzenet másolása",
|
||||
"ui.agent_chat_ctx_bodies": "Testek",
|
||||
"ui.agent_chat_ctx_elements": "Geometriai elemek",
|
||||
"ui.agent_chat_ctx_features": "Elemek",
|
||||
"ui.agent_chat_ctx_more": "+{n} további",
|
||||
"ui.agent_chat_ctx_other": "Egyéb",
|
||||
"ui.agent_chat_custom_heading": "Egyéni modell",
|
||||
"ui.agent_chat_custom_hint": "Bármely program, amely az Agent Client Protocolt használja stdin-en és stdout-on. Shell nem futtatja.",
|
||||
"ui.agent_chat_denied": "Elutasítva",
|
||||
"ui.agent_chat_deny": "Elutasítás",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Minden",
|
||||
"ui.agent_chat_mode_ask": "Kérdez",
|
||||
"ui.agent_chat_mode_edits": "Szerkesztés",
|
||||
"ui.agent_chat_model_custom": "Egyéni használata: {model}",
|
||||
"ui.agent_chat_model_no_tools": "A(z) {model} nem tudja használni a SolidCraft eszközeit, ezért csak csevegni lehet vele. Válasszon eszköztámogatással rendelkező modellt.",
|
||||
"ui.agent_chat_model_rejected": "Az ügynök nem fogadta el a(z) {model} modellt: {error}",
|
||||
"ui.agent_chat_model_search": "Modellek keresése…",
|
||||
"ui.agent_chat_model_tip": "Az ügynök által használt modell. A választás megmarad ennél az ügynöknél.",
|
||||
"ui.agent_chat_models_failed": "A modellek nem listázhatók: {error}",
|
||||
"ui.agent_chat_models_heading": "Elérhető modellek",
|
||||
"ui.agent_chat_models_listing": "Modellek listázása…",
|
||||
"ui.agent_chat_models_none": "A végpont nem listáz modelleket",
|
||||
"ui.agent_chat_models_not_a_list": "A válasz nem modellek listája volt",
|
||||
"ui.agent_chat_new": "Új csevegés",
|
||||
"ui.agent_chat_new_confirm": "Új csevegést indít? A jelenlegi törlődik.",
|
||||
"ui.agent_chat_next_picture": "Következő kép",
|
||||
@@ -4472,21 +4407,14 @@
|
||||
"ui.construction.contact_hint": "Érintő: válasszon egy kört/ívet vagy nyitott spline-t/kúpgörbét, majd az érintési pontot (szabadformájú görbék: végpontok)",
|
||||
"ui.construction.external_hint": "Érintő: válasszon egy kört vagy ívet, egy külső pontot, majd a kívánt megoldást",
|
||||
"ui.construction.free_hint": "Szerkesztőegyenes: kattintson két pontra; jobb kattintással újrakezdheti",
|
||||
"ui.construction.normal_needs_length": "A végtelen egyenes mindig szerkesztőegyenes: normál vonal rajzolásához adjon meg hosszt",
|
||||
"ui.construction.point_hint": "Szerkesztőegyenes: kattintson arra a pontra, amelyen át kell haladnia",
|
||||
"ui.construction.reference_hint": "Szerkesztőegyenes: válasszon egy egyenest, majd egy pontot; jobb kattintással módosíthatja a referenciát",
|
||||
"ui.construction.ribbon_label": "Segédvonalak",
|
||||
"ui.construction.side_hint": "Szerkesztőegyenes: kattintson arra az oldalra, amerre a vonal halad; jobb kattintással újrakezdheti",
|
||||
"ui.context_menu": "Helyi menü",
|
||||
"ui.continuity_crease": "Törés",
|
||||
"ui.continuity_curvature": "Görbület",
|
||||
"ui.continuity_curvature_change": "Görbületváltozás",
|
||||
"ui.continuity_g0": "G0 törés",
|
||||
"ui.continuity_g1": "G1 érintő",
|
||||
"ui.continuity_g2": "G2 görbület",
|
||||
"ui.continuity_gap": "Rés",
|
||||
"ui.continuity_position": "Pozíció",
|
||||
"ui.continuity_tangent": "Érintő",
|
||||
"ui.copy": "Másolás",
|
||||
"ui.corner": "Sarok",
|
||||
"ui.corners": "Sarkok",
|
||||
@@ -4747,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Origó elrejtése",
|
||||
"ui.hidePlanes": "Síkok elrejtése",
|
||||
"ui.hideProfile": "Profil elrejtése",
|
||||
"ui.hide_component_history": "Előzményeinek elrejtése",
|
||||
"ui.hide_origin_by_default": "Origó elrejtése alapértelmezetten",
|
||||
"ui.hide_origin_note": "Az új tervek az Origó mappa elrejtésével indulnak. A terv megtartja a saját Origó-beállítását, amint megjeleníti vagy elrejti.",
|
||||
"ui.history_back": "Egy lépéssel vissza",
|
||||
@@ -4755,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "kattintson duplán az élre",
|
||||
"ui.history_first": "Ugrás az elejére",
|
||||
"ui.history_forward": "Egy lépéssel előre",
|
||||
"ui.history_hide_hidden": "Rejtett komponensek elrejtése",
|
||||
"ui.history_hide_names": "Nevek elrejtése",
|
||||
"ui.history_last": "Ugrás a végére",
|
||||
"ui.history_marker_tip": "Előzményjelölő: húzással visszagörgetheti a modellt; az új elemek ide kerülnek",
|
||||
@@ -4765,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Lejátszás gomb megjelenítése",
|
||||
"ui.history_play_finish_action": "Az Előzmények lejátszása előtt fejezze be az aktuális műveletet",
|
||||
"ui.history_play_roll_back": "Lejátszás előtt mozgassa vissza az előzménymarkert",
|
||||
"ui.history_show_hidden": "Rejtett komponensek megjelenítése",
|
||||
"ui.history_show_names": "Nevek megjelenítése",
|
||||
"ui.history_sketch_listed_under": "Itt szerepel: {feature}",
|
||||
"ui.history_title": "Előzmények",
|
||||
"ui.history_toggle_names": "Elemnevek megjelenítése vagy elrejtése",
|
||||
"ui.history_waits_for_editor": "A Visszavonás, a Mégis és az Előzmények addig várnak, amíg az elemszerkesztő meg nem nyílik",
|
||||
"ui.hole": "Furat",
|
||||
"ui.hole_tap_type": "Furat menettípusa",
|
||||
"ui.hole_type": "Furat típusa",
|
||||
@@ -4786,7 +4710,6 @@
|
||||
"ui.insert_design.saved_file": "A(z) {name} nem mentett módosításokat tartalmaz, ezért a mentett fájlja lett beszúrva. Mentse el, majd a Kapcsolatok frissítése paranccsal hozza át a módosításokat.",
|
||||
"ui.insert_design.unsaved": "Nem mentett módosítások",
|
||||
"ui.inspect": "Vizsgálat",
|
||||
"ui.instances": "Példányok",
|
||||
"ui.intersect": "Metszet",
|
||||
"ui.isolate": "Elkülönítés",
|
||||
"ui.join": "Egyesítés",
|
||||
@@ -5051,7 +4974,6 @@
|
||||
"ui.showDimension": "Méret megjelenítése",
|
||||
"ui.showOrigin": "Origó megjelenítése",
|
||||
"ui.showPlanes": "Síkok megjelenítése",
|
||||
"ui.show_component_history": "Előzményeinek megjelenítése",
|
||||
"ui.show_constraints": "Kényszerek",
|
||||
"ui.show_construction_geometries": "Szerkesztő geometriák",
|
||||
"ui.show_dimensions": "Méretek",
|
||||
@@ -5234,7 +5156,7 @@
|
||||
"update.check_unavailable": "Ehhez a telepítéshez nem érhető el a frissítések keresése.",
|
||||
"update.checking": "Keresés…",
|
||||
"update.copy": "Másolás",
|
||||
"update.dmg_opened": "A lemezkép meg van nyitva és ellenőrizve: lépjen ki a SolidCraftból, húzza az új SolidCraftot az Alkalmazások mappába, lecserélve a régi példányt, majd nyissa meg újra a SolidCraftot.",
|
||||
"update.dmg_opened": "A lemezkép meg van nyitva és ellenőrizve: húzza a SolidCraftot az Alkalmazások mappába, lecserélve a régi példányt, majd indítsa újra a SolidCraftot.",
|
||||
"update.download": "Letöltés",
|
||||
"update.download_open": "Letöltés és megnyitás",
|
||||
"update.downloading": "Letöltés és ellenőrzés… {done}/{total} MB",
|
||||
@@ -5278,7 +5200,6 @@
|
||||
"web.open_link": "Megnyitja a csatolt tervet?",
|
||||
"web.open_link_confirm": "Mentetlen módosítások vannak. Megnyitja a csatolt tervet új fülön?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ választás · Enter / Tab kiegészítés · Esc elvetés",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ választás · Enter alkalmazza ezt az értéket",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ választás · Enter alkalmazza a beírtakat · Esc elvetés",
|
||||
"⌘Enter continues on GitHub.": "A ⌘Enter a GitHubon folytatja."
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Persegi panjang",
|
||||
"Rectangular Tube": "Pipa Persegi Panjang",
|
||||
"Reference face": "Muka referensi",
|
||||
"Reference plane": "Bidang referensi",
|
||||
"Relief Depth": "Kedalaman Relief",
|
||||
"Relief Width": "Lebar Relief",
|
||||
"Remove Tools": "Hapus Alat",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Penampang",
|
||||
"Section (optional)": "Penampang (opsional)",
|
||||
"Section position": "Posisi penampang",
|
||||
"Sections": "Penampang",
|
||||
"Sections (one sketch each)": "Penampang (satu sketsa per penampang)",
|
||||
"Select Other": "Pilih Lainnya",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Select Other: tahan tombol kiri diam di model (atau klik kanan › Select Other…) untuk mendaftar semua yang ada di bawah kursor; klik lagi di titik yang sama untuk memilih benda berikutnya. Klik dua kali tombol tengah untuk ke Home (Preferensi › Navigasi).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Ekstrak ke File",
|
||||
"cmd.component_finish_edit": "Selesai Edit",
|
||||
"cmd.component_from_bodies": "Buat Komponen dari Bodi",
|
||||
"cmd.component_hide_history": "Sembunyikan Riwayat Komponen",
|
||||
"cmd.component_insert_design": "Sisipkan Desain",
|
||||
"cmd.component_links": "Daftar Tautan",
|
||||
"cmd.component_list": "Daftar Komponen",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Ganti Nama Komponen",
|
||||
"cmd.component_replace_with_link": "Ganti dengan Tautan",
|
||||
"cmd.component_revert_position": "Kembalikan Posisi",
|
||||
"cmd.component_show_history": "Tampilkan Riwayat Komponen",
|
||||
"cmd.component_unlock": "Buka Kunci Isi",
|
||||
"cmd.component_update_links": "Perbarui Tautan",
|
||||
"cmd.config_activate": "Aktifkan Konfigurasi",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Aturan Konfigurasi",
|
||||
"cmd.config_rules": "Aturan Konfigurasi",
|
||||
"cmd.config_table": "Tampilkan Tabel Konfigurasi",
|
||||
"cmd.construct.plane.axis_point": "Bidang Melalui Sumbu dan Titik",
|
||||
"cmd.construct_axis_edge": "Sumbu Melalui Tepi",
|
||||
"cmd.construct_axis_normal_to_face": "Sumbu Tegak Lurus Muka",
|
||||
"cmd.construct_axis_through_cylinder": "Sumbu Melalui Silinder/Kerucut/Torus",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Tampilkan Tampilan Urai",
|
||||
"cmd.expr_evaluate": "Evaluasi Ekspresi",
|
||||
"cmd.face_delete": "Hapus Muka",
|
||||
"cmd.file.export_3mf": "Ekspor 3MF",
|
||||
"cmd.file.export_obj": "Ekspor OBJ",
|
||||
"cmd.file.export_step": "Ekspor STEP",
|
||||
"cmd.file.export_stl": "Ekspor STL",
|
||||
"cmd.file.export_urdf": "Ekspor URDF",
|
||||
"cmd.file_export": "Ekspor",
|
||||
"cmd.file_insert_mesh": "Sisipkan Mesh",
|
||||
"cmd.file_insert_step": "Sisipkan STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Analisis Aksesibilitas",
|
||||
"cmd.inspect_center_of_mass": "Pusat Massa",
|
||||
"cmd.inspect_clearance": "Kelonggaran",
|
||||
"cmd.inspect_colored_components": "Aktifkan Warna Komponen",
|
||||
"cmd.inspect_colored_components_disable": "Nonaktifkan Warna Komponen",
|
||||
"cmd.inspect_compare": "Bandingkan Bodi",
|
||||
"cmd.inspect_compare_picture": "Bandingkan dengan Gambar",
|
||||
"cmd.inspect_continuity": "Analisis Kontinuitas",
|
||||
"cmd.inspect_curvature_comb": "Analisis Sisir Kelengkungan",
|
||||
"cmd.inspect_curvature_map": "Analisis Peta Kelengkungan",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Edit Awan Titik",
|
||||
"cmd.pointcloud_insert": "Sisipkan Awan Titik",
|
||||
"cmd.pointcloud_list": "Awan Titik",
|
||||
"cmd.render.compare.divider": "Pindahkan Pembagi Perbandingan Render",
|
||||
"cmd.render.result.discard": "Buang Hasil Render",
|
||||
"cmd.render.result.select": "Pilih Hasil Render",
|
||||
"cmd.render_aging_describe": "Penuaan Permukaan Berlapis",
|
||||
"cmd.render_backends": "Backend Render",
|
||||
"cmd.render_batch": "Render Semua Bidikan",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Pindahkan Penanda Linimasa",
|
||||
"cmd.timeline_stop": "Hentikan Riwayat",
|
||||
"cmd.timeline_suppress": "Tekan Fitur",
|
||||
"cmd.ui.assistant": "Alihkan Asisten",
|
||||
"cmd.view.render_browser": "Penjelajah Render",
|
||||
"cmd.view.render_dock": "Pindah Dok Render",
|
||||
"cmd.view.render_focus": "Tampilan Fokus Render",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Alat navigasi",
|
||||
"cmd.view_top": "Tampilan Atas",
|
||||
"cmd.view_visual_style": "Gaya visual",
|
||||
"color.blue": "Biru",
|
||||
"color.copy": "Klik untuk menyalin nilai warna",
|
||||
"color.edit": "Klik untuk mengedit warna",
|
||||
"color.gamma": "Menampilkan nilai warna dalam ruang gamma 0-255",
|
||||
"color.green": "Hijau",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Masukkan #rrggbb atau #rrggbbaa; enam digit mempertahankan opasitas",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Menampilkan nilai warna dalam ruang linear 0-1",
|
||||
"color.recent": "Warna terbaru",
|
||||
"color.recent_help": "Warna yang Anda pilih di kolom warna mana pun muncul di sini",
|
||||
"color.red": "Merah",
|
||||
"color.saturation": "Saturasi (persen)",
|
||||
"color.saturation_value": "Saturasi dan nilai",
|
||||
"color.selected": "Warna terpilih",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Naikkan",
|
||||
"color.value": "Nilai, yaitu kecerahan (persen)",
|
||||
"config.column_filter_empty": "Tidak ada kolom yang cocok",
|
||||
"config.rule_parameter_columns": "Hanya parameter yang memiliki kolom di tabel yang dicantumkan. Tambahkan satu dengan Tambah Kolom (+) untuk memakainya di sini.",
|
||||
"config.rule_suppression_columns": "Hanya fitur yang memiliki kolom Tekan di tabel yang dicantumkan. Tambahkan satu dengan Tambah Kolom (+) untuk mengendalikannya di sini.",
|
||||
"contact.components_count": "{count} komponen",
|
||||
"contact.enabled_label": "aktif",
|
||||
"coupling.gear_1": "Roda Gigi 1",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Pilih bodi atau fitur: sketsa bukan keduanya. Ulangi kurvanya dengan Pola atau Cermin milik sketsa itu sendiri, atau ekstrusi dulu.",
|
||||
"dialog.select": "↖ Pilih",
|
||||
"dialog.selected_count": "↖ {count} terpilih",
|
||||
"dialog.selection_constraint": "Batasan {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · kurva {id}",
|
||||
"dialog.selection_guidance": "Pilih input untuk mengubahnya. Klik objek terpilih untuk menghapusnya; Ctrl/Cmd-klik juga mengalihkannya. Tinjau pratinjau, lalu tekan OK.",
|
||||
"dialog.selection_midpoint": "Titik tengah · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step} dari {total}: Pilih {input}",
|
||||
"dialog.step": "Langkah",
|
||||
"document.changed_on_disk": "{name} berubah di disk",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Membuat sumbu konstruksi melalui dua titik, titik sudut, atau titik sketsa. Gunakan untuk sumbu yang membentang di antara dua posisi yang diketahui.",
|
||||
"help.construct.plane.along_path": "Membuat bidang konstruksi tegak lurus terhadap sebuah tepi atau kurva, pada suatu posisi di sepanjangnya. Gunakan untuk menggambar sketsa profil bagi sweep atau penampang sebuah jalur.",
|
||||
"help.construct.plane.at_angle": "Membuat bidang konstruksi yang diputar dengan suatu sudut dari sebuah bidang atau muka, terhadap sebuah sumbu atau tepi. Gunakan untuk sketsa miring, seperti braket bersudut.",
|
||||
"help.construct.plane.axis_point": "Membuat bidang konstruksi yang memuat sebuah sumbu dan sebuah titik di luar sumbu itu. Pilih sebuah origin atau sumbu konstruksi, sebuah tepi lurus atau garis sketsa, lalu sebuah titik sudut, titik sketsa, atau titik konstruksi. Titik yang berada pada sumbu tidak dapat menentukan satu bidang yang unik.",
|
||||
"help.construct.plane.midplane": "Membuat bidang konstruksi tepat di tengah antara dua bidang atau muka datar, atau membagi dua keduanya di tempat mereka bertemu pada suatu sudut. Gunakan sebagai bidang simetri untuk pencerminan.",
|
||||
"help.construct.plane.offset": "Membuat bidang konstruksi yang sejajar dengan sebuah bidang atau muka datar, pada suatu jarak atau melalui sebuah titik. Gunakan untuk menggambar sketsa pada ketinggian tertentu di atas sebuah muka.",
|
||||
"help.construct.plane.perpendicular": "Membuat bidang konstruksi yang memuat sebuah garis atau tepi dan berdiri tegak lurus terhadap sebuah bidang atau muka. Gunakan untuk menggambar sketsa tegak pada model sepanjang sebuah tepi.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Periksa tipe sambungan (misalnya Kaku), status ground, batas sambungan, dan nilai yang digerakkan parameter.",
|
||||
"joint_move.blocked_title": "Tidak dapat memindahkan part ini",
|
||||
"joint_move.coarse": "Langkah rotasi dekat sumbu (°)",
|
||||
"joint_move.conflict": "Sebuah sambungan part ini berkonflik dengan yang lain: {joint}.",
|
||||
"joint_move.conflict_hint": "Edit, tekan, atau hapus sambungan yang berkonflik agar sambungan lain dapat menempatkan part.",
|
||||
"joint_move.distance": "Jarak dari awal",
|
||||
"joint_move.done": "Selesai",
|
||||
"joint_move.fine": "Langkah rotasi jauh dari sumbu (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "Track {number}",
|
||||
"nav.alias.help": "Shift+Alt+seret kiri memutar orbit, Shift+Alt+tombol tengah menggeser, Shift+Alt+klik kanan memperbesar/memperkecil; seret tombol tengah juga menggeser dan seret kanan memutar orbit",
|
||||
"nav.blender.help": "Seret tombol tengah memutar orbit, Shift+tombol tengah menggeser, Ctrl+tombol tengah memperbesar/memperkecil; seret kanan tidak menggerakkan kamera",
|
||||
"nav.creo.help": "Seret tombol tengah memutar orbit, Shift+tombol tengah menggeser, Ctrl+seret tombol tengah secara vertikal memperbesar/memperkecil (ke bawah untuk memperbesar); gulir roda untuk zoom",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "Mouse dan mouse 3D berputar melewati kedua kutub dan mempertahankan roll. Penjepretan sumbu dengan Shift dinonaktifkan.",
|
||||
"nav.free_orbit.label": "Orbit bebas",
|
||||
"nav.free_orbit.snap_help": "Mouse dan mouse 3D berputar melewati kedua kutub dan mempertahankan roll. Tahan Shift saat menyeret orbit untuk menjepret arah tampilan dan roll.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Sekrup kepala bulat (button head)",
|
||||
"parts.family.dowel_pin": "Pin paralel (dowel)",
|
||||
"parts.family.hex_bolt": "Baut / sekrup kepala segi enam",
|
||||
"parts.family.hex_cap_screw_inch": "Sekrup kepala segi enam (cap screw, inci)",
|
||||
"parts.family.hex_nut": "Mur segi enam",
|
||||
"parts.family.hex_nut_inch": "Mur segi enam (inci)",
|
||||
"parts.family.set_screw": "Sekrup set soket segi enam, ujung datar",
|
||||
"parts.family.socket_head_cap_screw": "Sekrup kepala soket (cap screw)",
|
||||
"parts.family.washer": "Ring datar (washer)",
|
||||
"parts.family.washer_sae": "Ring datar (washer), SAE (sempit)",
|
||||
"parts.family.washer_uss": "Ring datar (washer), USS (lebar)",
|
||||
"parts.holes": "Lubang (opsional)",
|
||||
"parts.placement_hint": "pilih satu lubang atau lebih untuk menempatkan part di masing-masing, atau OK menempatkannya di origin",
|
||||
"parts.system": "Sistem satuan",
|
||||
"parts.system.inch": "Inci",
|
||||
"parts.system.metric": "Metrik",
|
||||
"pattern.alignment_2": "Perataan 2",
|
||||
"pattern.alignment_hint": "Jumlah termasuk yang asli. Center menyeimbangkan salinan; Flip memilih sisi tambahan untuk total genap.",
|
||||
"pattern.direction_1": "Arah 1",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Seret kiri",
|
||||
"prefs.button_middle": "Seret tengah",
|
||||
"prefs.button_right": "Seret kanan",
|
||||
"prefs.carousel_next": "Berikutnya",
|
||||
"prefs.carousel_previous": "Sebelumnya",
|
||||
"prefs.choose_font": "Pilih file font…",
|
||||
"prefs.click": "Klik",
|
||||
"prefs.contact_shading": "Bayangan kontak lembut",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Permukaan melengkung",
|
||||
"prefs.curves": "Permukaan melengkung",
|
||||
"prefs.cut_preview_tint": "Tampilkan material yang dihilangkan dengan warna merah",
|
||||
"prefs.cut_preview_tint_help": "Pratinjau mengisi material yang dihilangkan sebuah fitur, dan bentuk yang digantikannya, dengan bayangan merah tembus pandang. Jika mati, isian itu sepenuhnya transparan dan hanya garis luarnya yang digambar.",
|
||||
"prefs.dark_parts_shading": "Pertahankan bentuk part yang gelap",
|
||||
"prefs.dark_parts_shading_help": "Melakukan pewarnaan tampilan dalam cahaya linear dengan cahaya pengisi langit yang lembut, sehingga muka yang gelap tetap memiliki gradasi dan tidak menjadi hitam",
|
||||
"prefs.dead_zone_note": "Pita berarsir adalah zona mati: gerakan di dalamnya diabaikan.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Bahasa antarmuka",
|
||||
"prefs.interface_note": "Tampilan SolidCraft dan letak bagian-bagiannya.",
|
||||
"prefs.interval_minutes": "{n} mnt",
|
||||
"prefs.invert_curved_roll_arrows": "Balikkan panah roll melengkung",
|
||||
"prefs.invert_curved_roll_arrows_note": "Dua panah melengkung di sudut kubus memutar (roll) tampilan di dalam bidang layar. Aktif: panah searah jarum jam memutar berlawanan arah jarum jam dan panah berlawanan arah jarum jam memutar searah jarum jam. Mati: tiap panah memutar tampilan sesuai arah lengkungannya.",
|
||||
"prefs.invert_navigator_arrows": "Balikkan panah rotasi kubus tampilan",
|
||||
"prefs.invert_navigator_arrows_note": "Panah di sekeliling kubus memutar orbit kamera ke arah yang ditunjuknya, seperti di Fusion dan Inventor: panah di kiri menunjuk ke kanan dan mengayunkan kamera ke kanan. Mati: panah memutar kubus ke sisi tempatnya berada, sehingga panah kiri mengayunkan kamera ke kiri. Panah roll yang melengkung tidak terpengaruh.",
|
||||
"prefs.keep_work_on_tab_switch": "Biarkan dialog dan alat tetap terbuka saat berpindah desain",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Penempatan",
|
||||
"prefs.prefer_free_sketch_points": "Utamakan titik sketsa yang dapat digerakkan saat titik saling tumpang tindih",
|
||||
"prefs.preview_choices": "Pratinjau pilihan Anda",
|
||||
"prefs.preview_tint_new_faces": "Warnai permukaan baru di pratinjau",
|
||||
"prefs.preview_tint_new_faces_help": "Pratinjau fitur yang mengubah sebuah bodi dapat menggambar permukaan yang ditambahkannya dengan warna pratinjau. Jika mati, bodi digambar apa adanya.",
|
||||
"prefs.print_face_highlight": "Sorotan permukaan cetak",
|
||||
"prefs.print_face_highlight_tip": "Seberapa kuat permukaan cetak sebuah bodi diwarnai dan diberi garis tepi di tampilan. 0 % tidak menampilkan sorotan.",
|
||||
"prefs.profile_active": "Profil aktif",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Warna dasar · sRGB",
|
||||
"render.base_metallic": "Fraksi metalik",
|
||||
"render.base_opaque_only": "Editor ini mempertahankan semantik bodi opak. Gunakan kontrol kaca eksplisit untuk transmisi optik.",
|
||||
"render.batch_all_shots": "Semua bidikan ({count})",
|
||||
"render.batch_empty": "Simpan sebuah bidikan sebelum merender semua bidikan.",
|
||||
"render.batch_limit": "Render Semua Bidikan dibatasi hingga 32 bidikan.",
|
||||
"render.batch_status": "{running} berjalan · {waiting} menunggu",
|
||||
"render.bloom_enable": "Tampilkan bloom",
|
||||
"render.bloom_help": "Cahaya kreatif khusus keluaran dari sorotan linear positif, disebar dengan tiga lintasan kotak ternormalisasi. Radius dalam piksel keluaran. Ditambahkan dalam cahaya tampilan linear sebelum sRGB; bukan lensa, efek volumetrik, atau peningkatan konvergensi. EXR mentah tidak berubah.",
|
||||
"render.bloom_opaque": "Bloom memerlukan keluaran opak. PNG transparan ditolak, tidak diratakan secara diam-diam.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Pilih look di Tampilan terlebih dahulu",
|
||||
"render.chromatic_offset": "Offset tepi kromatik",
|
||||
"render.clear_stage": "Hapus panggung tersimpan",
|
||||
"render.compare_divider": "Pembagi perbandingan",
|
||||
"render.compare_live": "Langsung",
|
||||
"render.compare_live_preview": "Pratinjau langsung",
|
||||
"render.compare_render": "Render {number}",
|
||||
"render.compare_side": "Render {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} d · {renderer} · ditangkap pada revisi {revision}",
|
||||
"render.compare_swap": "Tukar",
|
||||
"render.copy_request": "Salin JSON",
|
||||
"render.cpu_aa_help": "Sampel spasial: lebih banyak sampel memperbaiki tepi dan menambah waktu render.",
|
||||
"render.cpu_area_add": "Tambah lampu persegi panjang CPU",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Menampilkan gambar selesai terakhir; render yang lebih baru belum menggantikannya.",
|
||||
"render.result_sequence": "Checkpoint",
|
||||
"render.result_title": "Hasil Render",
|
||||
"render.results_choose": "Hasil yang dipertahankan",
|
||||
"render.results_label": "Render {number} · {shot} · {seconds} d",
|
||||
"render.results_stale_label": "{result} [usang]",
|
||||
"render.results_unnamed": "Gambar",
|
||||
"render.return_design": "Kembali ke Desain",
|
||||
"render.review_stale": "Tinjauan ini sudah usang. Selesaikan atau periksa lagi sebelum menerapkan.",
|
||||
"render.ribbon_aging": "Penuaan",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "kedua origin berada pada komponen yang sama",
|
||||
"thread.automatic_size": "Ukuran otomatis",
|
||||
"thread.designation": "Penunjukan",
|
||||
"thread.direction": "Arah ulir",
|
||||
"thread.iso_metric": "ISO metrik",
|
||||
"thread.left_hand": "Ulir kiri",
|
||||
"thread.right_hand": "Ulir kanan",
|
||||
"thread.select_size": "Pilih ukuran",
|
||||
"timeline.delete_after_marker_one": "Hapus 1 Fitur Setelah Penanda Riwayat",
|
||||
"timeline.delete_after_marker_other": "Hapus {count} Fitur Setelah Penanda Riwayat",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Jalankan ini di terminal, lalu tekan Hubungkan lagi:",
|
||||
"ui.agent_chat_auth_title": "Masuk ke agen",
|
||||
"ui.agent_chat_cancel": "Batal",
|
||||
"ui.agent_chat_cancel_timeout": "Agen tidak merespons Berhenti dan terputus. Hubungkan lagi untuk melanjutkan.",
|
||||
"ui.agent_chat_cannot_start": "Terpasang, tetapi tidak dapat dimulai: {error}",
|
||||
"ui.agent_chat_changes": "Perubahan: {n}, satu langkah urung",
|
||||
"ui.agent_chat_changes_n": "{n} perubahan",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Perintah agen",
|
||||
"ui.agent_chat_connect": "Hubungkan",
|
||||
"ui.agent_chat_copy": "Salin",
|
||||
"ui.agent_chat_copy_message": "Salin pesan",
|
||||
"ui.agent_chat_ctx_bodies": "Bodi",
|
||||
"ui.agent_chat_ctx_elements": "Elemen",
|
||||
"ui.agent_chat_ctx_features": "Fitur",
|
||||
"ui.agent_chat_ctx_more": "+{n} lagi",
|
||||
"ui.agent_chat_ctx_other": "Lainnya",
|
||||
"ui.agent_chat_custom_heading": "Model kustom",
|
||||
"ui.agent_chat_custom_hint": "Program apa pun yang berbicara Agent Client Protocol lewat stdin dan stdout. Tidak dijalankan oleh shell.",
|
||||
"ui.agent_chat_denied": "Ditolak",
|
||||
"ui.agent_chat_deny": "Tolak",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Semua",
|
||||
"ui.agent_chat_mode_ask": "Tanya",
|
||||
"ui.agent_chat_mode_edits": "Edit",
|
||||
"ui.agent_chat_model_custom": "Gunakan kustom: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} tidak dapat memakai alat SolidCraft, jadi hanya bisa mengobrol. Pilih model yang mendukung alat.",
|
||||
"ui.agent_chat_model_rejected": "Agen tidak menerima {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Cari model…",
|
||||
"ui.agent_chat_model_tip": "Model yang dipakai agen. Pilihan Anda diingat untuk agen ini.",
|
||||
"ui.agent_chat_models_failed": "Tidak dapat mencantumkan model: {error}",
|
||||
"ui.agent_chat_models_heading": "Model yang tersedia",
|
||||
"ui.agent_chat_models_listing": "Mencantumkan model…",
|
||||
"ui.agent_chat_models_none": "Endpoint tidak mencantumkan model",
|
||||
"ui.agent_chat_models_not_a_list": "Jawabannya bukan daftar model",
|
||||
"ui.agent_chat_new": "Chat baru",
|
||||
"ui.agent_chat_new_confirm": "Mulai chat baru? Chat saat ini akan dibersihkan.",
|
||||
"ui.agent_chat_next_picture": "Gambar berikutnya",
|
||||
@@ -4472,21 +4407,14 @@
|
||||
"ui.construction.contact_hint": "Garis singgung: pilih lingkaran/busur atau spline/kurva konik terbuka, lalu titik singgung (kurva bentuk bebas: titik ujung)",
|
||||
"ui.construction.external_hint": "Garis singgung: pilih lingkaran atau busur, sebuah titik luar, lalu solusi yang diinginkan",
|
||||
"ui.construction.free_hint": "Garis bantu: klik dua titik; klik kanan untuk mengulang",
|
||||
"ui.construction.normal_needs_length": "Garis tak hingga selalu berupa garis bantu: ketik panjang untuk menggambar garis normal",
|
||||
"ui.construction.point_hint": "Garis bantu: klik titik yang akan dilalui",
|
||||
"ui.construction.reference_hint": "Garis bantu: pilih garis lurus, lalu sebuah titik; klik kanan untuk mengganti referensi",
|
||||
"ui.construction.ribbon_label": "Garis bantu",
|
||||
"ui.construction.side_hint": "Garis bantu: klik sisi tempat garis mengarah; klik kanan untuk mengulang",
|
||||
"ui.context_menu": "Menu konteks",
|
||||
"ui.continuity_crease": "Lipatan",
|
||||
"ui.continuity_curvature": "Kelengkungan",
|
||||
"ui.continuity_curvature_change": "Perubahan kelengkungan",
|
||||
"ui.continuity_g0": "Lipatan G0",
|
||||
"ui.continuity_g1": "Tangen G1",
|
||||
"ui.continuity_g2": "Kelengkungan G2",
|
||||
"ui.continuity_gap": "Celah",
|
||||
"ui.continuity_position": "Posisi",
|
||||
"ui.continuity_tangent": "Tangen",
|
||||
"ui.copy": "Salin",
|
||||
"ui.corner": "Sudut",
|
||||
"ui.corners": "Sudut",
|
||||
@@ -4747,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Sembunyikan Origin",
|
||||
"ui.hidePlanes": "Sembunyikan Bidang",
|
||||
"ui.hideProfile": "Sembunyikan Profil",
|
||||
"ui.hide_component_history": "Sembunyikan Riwayatnya",
|
||||
"ui.hide_origin_by_default": "Sembunyikan origin secara default",
|
||||
"ui.hide_origin_note": "Desain baru dimulai dengan folder Origin tersembunyi. Desain mempertahankan pengaturan Origin-nya sendiri setelah Anda menampilkan atau menyembunyikannya.",
|
||||
"ui.history_back": "Mundur satu langkah",
|
||||
@@ -4755,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "klik dua kali tepinya",
|
||||
"ui.history_first": "Ke awal",
|
||||
"ui.history_forward": "Maju satu langkah",
|
||||
"ui.history_hide_hidden": "Sembunyikan Komponen Tersembunyi",
|
||||
"ui.history_hide_names": "Sembunyikan nama",
|
||||
"ui.history_last": "Ke akhir",
|
||||
"ui.history_marker_tip": "Penanda riwayat: seret untuk memundurkan model; fitur baru ditaruh di sini",
|
||||
@@ -4765,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Tampilkan Tombol Putar",
|
||||
"ui.history_play_finish_action": "Selesaikan aksi saat ini sebelum memutar Riwayat",
|
||||
"ui.history_play_roll_back": "Pindahkan penanda Riwayat ke belakang sebelum memutar",
|
||||
"ui.history_show_hidden": "Tampilkan Komponen Tersembunyi",
|
||||
"ui.history_show_names": "Tampilkan nama",
|
||||
"ui.history_sketch_listed_under": "Terdaftar di bawah {feature}",
|
||||
"ui.history_title": "Riwayat",
|
||||
"ui.history_toggle_names": "Tampilkan atau sembunyikan nama fitur",
|
||||
"ui.history_waits_for_editor": "Urungkan, Ulangi, dan Riwayat menunggu hingga editor fitur terbuka",
|
||||
"ui.hole": "Lubang",
|
||||
"ui.hole_tap_type": "Tipe Tap Lubang",
|
||||
"ui.hole_type": "Tipe Lubang",
|
||||
@@ -4786,7 +4710,6 @@
|
||||
"ui.insert_design.saved_file": "{name} memiliki perubahan yang belum disimpan, sehingga file tersimpannya yang disisipkan. Simpan desain itu, lalu jalankan Perbarui Tautan untuk memasukkan perubahannya.",
|
||||
"ui.insert_design.unsaved": "Perubahan belum disimpan",
|
||||
"ui.inspect": "Inspeksi",
|
||||
"ui.instances": "Instans",
|
||||
"ui.intersect": "Irisan",
|
||||
"ui.isolate": "Isolasi",
|
||||
"ui.join": "Gabung",
|
||||
@@ -5051,7 +4974,6 @@
|
||||
"ui.showDimension": "Tampilkan Dimensi",
|
||||
"ui.showOrigin": "Tampilkan Origin",
|
||||
"ui.showPlanes": "Tampilkan Bidang",
|
||||
"ui.show_component_history": "Tampilkan Riwayatnya",
|
||||
"ui.show_constraints": "Batasan",
|
||||
"ui.show_construction_geometries": "Geometri Konstruksi",
|
||||
"ui.show_dimensions": "Dimensi",
|
||||
@@ -5234,7 +5156,7 @@
|
||||
"update.check_unavailable": "Pemeriksaan pembaruan tidak tersedia untuk instalasi ini.",
|
||||
"update.checking": "Memeriksa…",
|
||||
"update.copy": "Salin",
|
||||
"update.dmg_opened": "Disk image terbuka dan terverifikasi: keluar dari SolidCraft, seret SolidCraft yang baru ke Applications, ganti salinan lama, lalu buka SolidCraft lagi.",
|
||||
"update.dmg_opened": "Disk image terbuka dan terverifikasi: seret SolidCraft ke Applications, ganti salinan lama, lalu mulai ulang SolidCraft.",
|
||||
"update.download": "Unduh",
|
||||
"update.download_open": "Unduh dan buka",
|
||||
"update.downloading": "Mengunduh dan memverifikasi… {done} dari {total} MB",
|
||||
@@ -5278,7 +5200,6 @@
|
||||
"web.open_link": "Buka desain tertaut?",
|
||||
"web.open_link_confirm": "Ada perubahan yang belum disimpan. Buka desain tertaut di tab baru?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ pilih · Enter / Tab lengkapi · Esc tutup",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ pilih · Enter menerapkan nilai ini",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ pilih · Enter menerapkan yang Anda ketik · Esc tutup",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter melanjutkan di GitHub."
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Rettangolare",
|
||||
"Rectangular Tube": "Tubo rettangolare",
|
||||
"Reference face": "Faccia di riferimento",
|
||||
"Reference plane": "Piano di riferimento",
|
||||
"Relief Depth": "Profondità di scarico",
|
||||
"Relief Width": "Larghezza di scarico",
|
||||
"Remove Tools": "Rimuovi corpi utensile",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Sezione",
|
||||
"Section (optional)": "Sezione (facoltativa)",
|
||||
"Section position": "Posizione della sezione",
|
||||
"Sections": "Sezioni",
|
||||
"Sections (one sketch each)": "Sezioni (uno schizzo ciascuna)",
|
||||
"Select Other": "Seleziona altro",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Seleziona altro: tieni premuto il pulsante sinistro senza muovere il puntatore sul modello (o fai clic destro › Seleziona altro…) per elencare gli oggetti sotto il cursore; fai di nuovo clic nello stesso punto per selezionare l'oggetto successivo. Fai doppio clic con il pulsante centrale per tornare alla vista iniziale (Preferenze › Navigazione).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Estrai in file",
|
||||
"cmd.component_finish_edit": "Termina modifica",
|
||||
"cmd.component_from_bodies": "Crea componenti da corpi",
|
||||
"cmd.component_hide_history": "Nascondi cronologia del componente",
|
||||
"cmd.component_insert_design": "Inserisci progetto",
|
||||
"cmd.component_links": "Elenca collegamenti",
|
||||
"cmd.component_list": "Elenca componenti",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Rinomina componente",
|
||||
"cmd.component_replace_with_link": "Sostituisci con collegamento",
|
||||
"cmd.component_revert_position": "Ripristina posizione",
|
||||
"cmd.component_show_history": "Mostra cronologia del componente",
|
||||
"cmd.component_unlock": "Sblocca contenuto",
|
||||
"cmd.component_update_links": "Aggiorna collegamenti",
|
||||
"cmd.config_activate": "Attiva configurazione",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Regola di configurazione",
|
||||
"cmd.config_rules": "Regole di configurazione",
|
||||
"cmd.config_table": "Mostra tabella delle configurazioni",
|
||||
"cmd.construct.plane.axis_point": "Piano per un asse e un punto",
|
||||
"cmd.construct_axis_edge": "Asse lungo uno spigolo",
|
||||
"cmd.construct_axis_normal_to_face": "Asse perpendicolare a una faccia",
|
||||
"cmd.construct_axis_through_cylinder": "Asse di cilindro/cono/toro",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Mostra vista esplosa",
|
||||
"cmd.expr_evaluate": "Valuta espressione",
|
||||
"cmd.face_delete": "Elimina faccia",
|
||||
"cmd.file.export_3mf": "Esporta 3MF",
|
||||
"cmd.file.export_obj": "Esporta OBJ",
|
||||
"cmd.file.export_step": "Esporta STEP",
|
||||
"cmd.file.export_stl": "Esporta STL",
|
||||
"cmd.file.export_urdf": "Esporta URDF",
|
||||
"cmd.file_export": "Esporta",
|
||||
"cmd.file_insert_mesh": "Inserisci mesh",
|
||||
"cmd.file_insert_step": "Inserisci STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Analisi di accessibilità",
|
||||
"cmd.inspect_center_of_mass": "Centro di massa",
|
||||
"cmd.inspect_clearance": "Distanza libera",
|
||||
"cmd.inspect_colored_components": "Attiva colori dei componenti",
|
||||
"cmd.inspect_colored_components_disable": "Disattiva colori dei componenti",
|
||||
"cmd.inspect_compare": "Confronta corpi",
|
||||
"cmd.inspect_compare_picture": "Confronta immagine",
|
||||
"cmd.inspect_continuity": "Analisi di continuità",
|
||||
"cmd.inspect_curvature_comb": "Analisi del pettine di curvatura",
|
||||
"cmd.inspect_curvature_map": "Analisi della mappa di curvatura",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Modifica nuvola di punti",
|
||||
"cmd.pointcloud_insert": "Inserisci nuvola di punti",
|
||||
"cmd.pointcloud_list": "Nuvole di punti",
|
||||
"cmd.render.compare.divider": "Sposta divisore del confronto di rendering",
|
||||
"cmd.render.result.discard": "Scarta risultato di rendering",
|
||||
"cmd.render.result.select": "Seleziona risultato di rendering",
|
||||
"cmd.render_aging_describe": "Invecchiamento a strati delle superfici",
|
||||
"cmd.render_backends": "Motori di rendering",
|
||||
"cmd.render_batch": "Renderizza tutte le inquadrature",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Sposta marcatore della sequenza temporale",
|
||||
"cmd.timeline_stop": "Arresta riproduzione cronologia",
|
||||
"cmd.timeline_suppress": "Sospendi lavorazione",
|
||||
"cmd.ui.assistant": "Mostra/Nascondi assistente",
|
||||
"cmd.view.render_browser": "Struttura del rendering",
|
||||
"cmd.view.render_dock": "Sposta pannello di rendering",
|
||||
"cmd.view.render_focus": "Vista focalizzata sul rendering",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Strumento di navigazione",
|
||||
"cmd.view_top": "Vista dall'alto",
|
||||
"cmd.view_visual_style": "Stile di visualizzazione",
|
||||
"color.blue": "Blu",
|
||||
"color.copy": "Fai clic per copiare i valori del colore",
|
||||
"color.edit": "Fai clic per modificare il colore",
|
||||
"color.gamma": "Valori del colore mostrati nello spazio gamma 0-255",
|
||||
"color.green": "Verde",
|
||||
"color.hex": "Hex",
|
||||
"color.hex_help": "Inserisci #rrggbb o #rrggbbaa; con sei cifre l'opacità resta invariata",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Valori del colore mostrati nello spazio lineare 0-1",
|
||||
"color.recent": "Colori recenti",
|
||||
"color.recent_help": "I colori scelti in qualsiasi campo colore compaiono qui",
|
||||
"color.red": "Rosso",
|
||||
"color.saturation": "Saturazione (percentuale)",
|
||||
"color.saturation_value": "Saturazione e valore",
|
||||
"color.selected": "Colore selezionato",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Aumenta di un passo",
|
||||
"color.value": "Valore, la luminosità (percentuale)",
|
||||
"config.column_filter_empty": "Nessuna colonna corrispondente",
|
||||
"config.rule_parameter_columns": "Sono elencati solo i parametri che hanno una colonna nella tabella. Aggiungine una con Aggiungi colonna (+) per usarla qui.",
|
||||
"config.rule_suppression_columns": "Sono elencate solo le lavorazioni che hanno una colonna Sospendi nella tabella. Aggiungine una con Aggiungi colonna (+) per comandarle da qui.",
|
||||
"contact.components_count": "{count} componenti",
|
||||
"contact.enabled_label": "abilitato",
|
||||
"coupling.gear_1": "Ruota dentata 1",
|
||||
@@ -1364,21 +1346,14 @@
|
||||
"dialog.computing": "Calcolo in corso… {seconds} s",
|
||||
"dialog.could_not_be_checked": "impossibile verificare",
|
||||
"dialog.custom_axis": "↖ Asse personalizzato",
|
||||
"dialog.cylinder_face_tip": "La faccia o il vertice del modello su cui poggia il cilindro: fai clic qui, poi su di esso nella vista. Se resta vuoto, lo posizionano X, Y e Z.",
|
||||
"dialog.dimension_insert_hint": "Fai doppio clic su un’altra quota di controllo per inserirne il nome",
|
||||
"dialog.dimension_name": "Quota: {name}",
|
||||
"dialog.edit_title": "MODIFICA {title}",
|
||||
"dialog.no_shown_body": "Nessun corpo visibile da modificare: mostrane uno o scegli Nuovo corpo",
|
||||
"dialog.pattern_sketch_not_object": "Seleziona corpi o lavorazioni: uno schizzo non è nessuno dei due. Ripeti le sue curve con Ripetizione o Specchia dello schizzo stesso, oppure estrudilo prima.",
|
||||
"dialog.primitive_point_tip": "Un punto che compila X, Y e Z: fai clic qui, poi su un vertice, uno spigolo o una faccia del modello, su un punto di uno schizzo oppure sull'origine.",
|
||||
"dialog.reference_edge_tip": "Uno spigolo rettilineo della faccia scelta da cui si misura la distanza sotto: fai clic qui, poi sullo spigolo.",
|
||||
"dialog.select": "↖ Seleziona",
|
||||
"dialog.selected_count": "↖ Selezionati: {count}",
|
||||
"dialog.selection_constraint": "Vincolo {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · curva {id}",
|
||||
"dialog.selection_guidance": "Seleziona un dato di ingresso per modificarlo. Fai clic sugli oggetti selezionati per rimuoverli; anche Ctrl/Cmd-clic ne alterna la selezione. Controlla l'anteprima, poi premi OK.",
|
||||
"dialog.selection_midpoint": "Punto medio · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profilo {number}",
|
||||
"dialog.selection_step": "{step} di {total}: seleziona {input}",
|
||||
"dialog.step": "Passo",
|
||||
"document.changed_on_disk": "{name} è stato modificato sul disco",
|
||||
@@ -1393,7 +1368,6 @@
|
||||
"document.read_only_save": "Questa progettazione è stata aperta in sola lettura: salvala come nuovo file",
|
||||
"document.save_changes": "Salvare le modifiche a {name}?",
|
||||
"document.save_copy": "Salva una copia…",
|
||||
"document.tab_unsaved_changes": "Modifiche non salvate",
|
||||
"document.unsaved_changes": "{name} contiene modifiche non salvate. Se non salvi, andranno perse.",
|
||||
"drive.rigid": "{joint} è un giunto rigido: tiene ferme le sue parti, quindi non c'è nulla da azionare. Per farle muovere, cambia il suo tipo in Modifica giunto: Rotante per ruotare, Scorrevole per scorrere, Cilindrico per entrambi.",
|
||||
"drive.stopped_by_contact": "Arrestato a {value}: {a} attraverserebbe {b}",
|
||||
@@ -1462,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Crea un asse di costruzione passante per due punti, vertici o punti dello schizzo. Usalo per un asse che collega due posizioni note.",
|
||||
"help.construct.plane.along_path": "Crea un piano di costruzione perpendicolare a uno spigolo o a una curva, in una posizione lungo di essa. Usalo per disegnare il profilo di uno sweep o una sezione di un percorso.",
|
||||
"help.construct.plane.at_angle": "Crea un piano di costruzione ruotato di un angolo rispetto a un piano o a una faccia, attorno a un asse o a uno spigolo. Usalo per schizzi inclinati, ad esempio una staffa angolata.",
|
||||
"help.construct.plane.axis_point": "Crea un piano di costruzione che contiene un asse e un punto esterno all'asse. Seleziona un'origine o un asse di costruzione, uno spigolo rettilineo o una linea dello schizzo, poi un vertice, un punto dello schizzo o un punto di costruzione. Un punto sull'asse non può definire un piano univoco.",
|
||||
"help.construct.plane.midplane": "Crea un piano di costruzione a metà tra due piani o facce piane, oppure che li biseca dove si incontrano ad angolo. Usalo come piano di simmetria per specchiare.",
|
||||
"help.construct.plane.offset": "Crea un piano di costruzione parallelo a un piano o a una faccia piana, a una certa distanza o passante per un punto. Usalo per disegnare a un'altezza stabilita sopra una faccia.",
|
||||
"help.construct.plane.perpendicular": "Crea un piano di costruzione che contiene una linea o uno spigolo ed è perpendicolare a un piano o a una faccia. Usalo per disegnare in verticale sul modello lungo uno spigolo.",
|
||||
@@ -1839,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Controlla il tipo di giunto (ad esempio Rigido), il fissaggio a terra, i limiti del giunto e i valori comandati da parametri.",
|
||||
"joint_move.blocked_title": "Impossibile spostare questa parte",
|
||||
"joint_move.coarse": "Passo di rotazione vicino all'asse (°)",
|
||||
"joint_move.conflict": "Un giunto di questa parte è in conflitto con gli altri: {joint}.",
|
||||
"joint_move.conflict_hint": "Modifica, sospendi o elimina il giunto in conflitto, così gli altri giunti potranno posizionare la parte.",
|
||||
"joint_move.distance": "Distanza dall'inizio",
|
||||
"joint_move.done": "Fatto",
|
||||
"joint_move.fine": "Passo di rotazione lontano dall'asse (°)",
|
||||
@@ -1940,8 +1911,6 @@
|
||||
"motion.track_name": "Traccia {number}",
|
||||
"nav.alias.help": "Shift+Alt+trascinamento sinistro orbita, Shift+Alt+centrale fa la panoramica, Shift+Alt+destro fa lo zoom; anche il trascinamento centrale fa la panoramica e il destro orbita",
|
||||
"nav.blender.help": "Il trascinamento centrale orbita, Shift+centrale fa la panoramica, Ctrl+centrale fa lo zoom; il trascinamento destro lascia ferma la vista",
|
||||
"nav.creo.help": "Il trascinamento centrale orbita, Shift+centrale fa la panoramica, il trascinamento verticale con Ctrl+centrale fa lo zoom (verso il basso per avvicinare); scorri la rotellina per fare lo zoom",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "Mouse e mouse 3D ruotano oltre entrambi i poli e conservano il rollio. L'aggancio degli assi con Shift è disattivato.",
|
||||
"nav.free_orbit.label": "Orbita libera",
|
||||
"nav.free_orbit.snap_help": "Mouse e mouse 3D ruotano oltre entrambi i poli e conservano il rollio. Tieni premuto Shift durante il trascinamento dell'orbita per agganciare la direzione della vista e il rollio.",
|
||||
@@ -2094,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Vite a testa bombata con esagono incassato",
|
||||
"parts.family.dowel_pin": "Spina cilindrica",
|
||||
"parts.family.hex_bolt": "Vite a testa esagonale",
|
||||
"parts.family.hex_cap_screw_inch": "Vite a testa esagonale (pollici)",
|
||||
"parts.family.hex_nut": "Dado esagonale",
|
||||
"parts.family.hex_nut_inch": "Dado esagonale (pollici)",
|
||||
"parts.family.set_screw": "Grano a esagono incassato, punta piana",
|
||||
"parts.family.socket_head_cap_screw": "Vite a testa cilindrica con esagono incassato",
|
||||
"parts.family.washer": "Rondella piana",
|
||||
"parts.family.washer_sae": "Rondella piana, SAE (stretta)",
|
||||
"parts.family.washer_uss": "Rondella piana, USS (larga)",
|
||||
"parts.holes": "Fori (facoltativo)",
|
||||
"parts.placement_hint": "scegli uno o più fori per alloggiare una parte in ciascuno, oppure OK la colloca nell'origine",
|
||||
"parts.system": "Sistema di unità",
|
||||
"parts.system.inch": "Pollici",
|
||||
"parts.system.metric": "Metrico",
|
||||
"pattern.alignment_2": "Allineamento 2",
|
||||
"pattern.alignment_hint": "Il conteggio include l'originale. Centro distribuisce le copie sui due lati; Inverti sceglie il lato con la copia in più per totali pari.",
|
||||
"pattern.direction_1": "Direzione 1",
|
||||
@@ -2159,8 +2121,6 @@
|
||||
"prefs.bottom_centre": "In basso al centro",
|
||||
"prefs.bottom_right": "In basso a destra",
|
||||
"prefs.browser": "Struttura",
|
||||
"prefs.browser_components_open": "Componenti aperti all'inizio",
|
||||
"prefs.browser_components_open_note": "Ogni componente nel browser inizia aperto, come prima, invece che chiuso finché non lo apri.",
|
||||
"prefs.browser_eye_left": "Occhio accanto alla freccia di espansione",
|
||||
"prefs.browser_eye_left_note": "Le righe della Struttura mostrano l'occhio mostra/nascondi accanto alla freccia di espansione, come prima, invece che in una colonna all'estremità destra delle righe.",
|
||||
"prefs.browser_reveal_on_hover": "Mostra al passaggio del puntatore",
|
||||
@@ -2170,8 +2130,6 @@
|
||||
"prefs.button_left": "Trascinamento sinistro",
|
||||
"prefs.button_middle": "Trascinamento centrale",
|
||||
"prefs.button_right": "Trascinamento destro",
|
||||
"prefs.carousel_next": "Successivo",
|
||||
"prefs.carousel_previous": "Precedente",
|
||||
"prefs.choose_font": "Scegli il file del carattere…",
|
||||
"prefs.click": "Clic",
|
||||
"prefs.contact_shading": "Ombreggiatura di contatto morbida",
|
||||
@@ -2184,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Superfici curve",
|
||||
"prefs.curves": "Superfici curve",
|
||||
"prefs.cut_preview_tint": "Mostra in rosso il materiale rimosso",
|
||||
"prefs.cut_preview_tint_help": "Un'anteprima riempie il materiale che una lavorazione rimuove, e la forma che sostituisce, con un fantasma rosso semitrasparente. Se disattivato, questi riempimenti sono completamente trasparenti e vengono disegnati solo i loro contorni.",
|
||||
"prefs.dark_parts_shading": "Mantieni la forma delle parti scure",
|
||||
"prefs.dark_parts_shading_help": "Ombreggia la vista in luce lineare con un morbido riempimento di luce dal cielo, così le facce scure mantengono una sfumatura invece di diventare nere",
|
||||
"prefs.dead_zone_note": "La fascia tratteggiata è la zona morta: il movimento al suo interno viene ignorato.",
|
||||
@@ -2244,8 +2200,6 @@
|
||||
"prefs.interface_language": "Lingua dell'interfaccia",
|
||||
"prefs.interface_note": "Aspetto di SolidCraft e disposizione dei suoi elementi.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Inverti le frecce curve di rollio",
|
||||
"prefs.invert_curved_roll_arrows_note": "Le due frecce curve all'angolo del cubo fanno ruotare la vista nel piano dello schermo. Attivato: la freccia oraria ruota in senso antiorario e la freccia antioraria ruota in senso orario. Disattivato: ogni freccia ruota la vista nel verso in cui si curva.",
|
||||
"prefs.invert_navigator_arrows": "Inverti le frecce di rotazione del cubo della vista",
|
||||
"prefs.invert_navigator_arrows_note": "Le frecce attorno al cubo fanno orbitare la camera nella direzione in cui puntano, come in Fusion e Inventor: la freccia a sinistra punta a destra e sposta la camera a destra. Disattivato: una freccia ruota il cubo verso il lato in cui si trova, quindi la freccia sinistra sposta la camera a sinistra. Le frecce curve di rollio non sono interessate.",
|
||||
"prefs.keep_work_on_tab_switch": "Mantieni aperti finestre di dialogo e strumenti quando cambi progetto",
|
||||
@@ -2305,8 +2259,6 @@
|
||||
"prefs.placement": "Posizione",
|
||||
"prefs.prefer_free_sketch_points": "Preferisci i punti dello schizzo mobili quando i punti si sovrappongono",
|
||||
"prefs.preview_choices": "Anteprima delle tue scelte",
|
||||
"prefs.preview_tint_new_faces": "Colora la nuova superficie in un'anteprima",
|
||||
"prefs.preview_tint_new_faces_help": "L'anteprima di una lavorazione che modifica un corpo può disegnare la superficie aggiunta con il colore dell'anteprima. Se disattivato, il corpo viene disegnato com'è.",
|
||||
"prefs.print_face_highlight": "Evidenziazione della faccia di stampa",
|
||||
"prefs.print_face_highlight_tip": "Intensità della tinta e del contorno della faccia di stampa di un corpo nella vista. 0 % non mostra evidenziazione.",
|
||||
"prefs.profile_active": "Profilo attivo",
|
||||
@@ -2604,10 +2556,6 @@
|
||||
"render.base_color": "Colore di base · sRGB",
|
||||
"render.base_metallic": "Frazione metallica",
|
||||
"render.base_opaque_only": "Questo editor mantiene la semantica dei corpi opachi. Per la trasmissione ottica usa i controlli espliciti del vetro.",
|
||||
"render.batch_all_shots": "Tutte le inquadrature ({count})",
|
||||
"render.batch_empty": "Salva un'inquadratura prima di renderizzare tutte le inquadrature.",
|
||||
"render.batch_limit": "Il rendering di tutte le inquadrature è limitato a 32 inquadrature.",
|
||||
"render.batch_status": "{running} in corso · {waiting} in attesa",
|
||||
"render.bloom_enable": "Bloom di visualizzazione",
|
||||
"render.bloom_help": "Alone creativo solo in uscita, ricavato dalle alte luci lineari positive e diffuso con tre passate box normalizzate. Il raggio è in pixel di uscita. Viene aggiunto nella luce di visualizzazione lineare prima della conversione sRGB; non è un effetto di lente o volumetrico né un miglioramento della convergenza. L'EXR grezzo resta invariato.",
|
||||
"render.bloom_opaque": "Il bloom richiede un'uscita opaca. Il PNG trasparente viene rifiutato, non appiattito in silenzio.",
|
||||
@@ -2718,13 +2666,6 @@
|
||||
"render.choose_look": "Scegli prima un aspetto in Aspetti",
|
||||
"render.chromatic_offset": "Scostamento cromatico dei bordi",
|
||||
"render.clear_stage": "Rimuovi la scenografia salvata",
|
||||
"render.compare_divider": "Divisore del confronto",
|
||||
"render.compare_live": "Live",
|
||||
"render.compare_live_preview": "Anteprima dal vivo",
|
||||
"render.compare_render": "Rendering {number}",
|
||||
"render.compare_side": "Rendering {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · acquisito alla revisione {revision}",
|
||||
"render.compare_swap": "Scambia",
|
||||
"render.copy_request": "Copia JSON",
|
||||
"render.cpu_aa_help": "Campioni spaziali: più campioni migliorano i bordi e aumentano il tempo di rendering.",
|
||||
"render.cpu_area_add": "Aggiungi luce rettangolare CPU",
|
||||
@@ -3474,10 +3415,6 @@
|
||||
"render.result_retained": "Viene mostrata l'ultima immagine completata; un rendering più recente non l'ha ancora sostituita.",
|
||||
"render.result_sequence": "Punto di ripristino",
|
||||
"render.result_title": "Risultato del rendering",
|
||||
"render.results_choose": "Risultati conservati",
|
||||
"render.results_label": "Rendering {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [obsoleto]",
|
||||
"render.results_unnamed": "Immagine",
|
||||
"render.return_design": "Torna a Progettazione",
|
||||
"render.review_stale": "Questa revisione è obsoleta. Risolvi o verifica di nuovo prima di applicare.",
|
||||
"render.ribbon_aging": "Invecchiamento",
|
||||
@@ -3961,7 +3898,10 @@
|
||||
"the two origins are on the same component": "le due origini sono sullo stesso componente",
|
||||
"thread.automatic_size": "Dimensione automatica",
|
||||
"thread.designation": "Designazione",
|
||||
"thread.direction": "Verso del filetto",
|
||||
"thread.iso_metric": "Metrica ISO",
|
||||
"thread.left_hand": "Sinistro",
|
||||
"thread.right_hand": "Destro",
|
||||
"thread.select_size": "Seleziona dimensione",
|
||||
"timeline.delete_after_marker_one": "Elimina 1 lavorazione dopo il marcatore della cronologia",
|
||||
"timeline.delete_after_marker_other": "Elimina {count} lavorazioni dopo il marcatore della cronologia",
|
||||
@@ -4135,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Esegui questo comando in un terminale, poi premi di nuovo Connetti:",
|
||||
"ui.agent_chat_auth_title": "Accedi all'agente",
|
||||
"ui.agent_chat_cancel": "Annulla",
|
||||
"ui.agent_chat_cancel_timeout": "L'agente non ha risposto ad Arresta ed è stato disconnesso. Riconnetti per continuare.",
|
||||
"ui.agent_chat_cannot_start": "Installato, ma non si avvia: {error}",
|
||||
"ui.agent_chat_changes": "Modifiche: {n}, un solo passo di annullamento",
|
||||
"ui.agent_chat_changes_n": "{n} modifiche",
|
||||
@@ -4145,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Comando dell'agente",
|
||||
"ui.agent_chat_connect": "Connetti",
|
||||
"ui.agent_chat_copy": "Copia",
|
||||
"ui.agent_chat_copy_message": "Copia messaggio",
|
||||
"ui.agent_chat_ctx_bodies": "Corpi",
|
||||
"ui.agent_chat_ctx_elements": "Elementi",
|
||||
"ui.agent_chat_ctx_features": "Lavorazioni",
|
||||
"ui.agent_chat_ctx_more": "+{n} altri",
|
||||
"ui.agent_chat_ctx_other": "Altro",
|
||||
"ui.agent_chat_custom_heading": "Modello personalizzato",
|
||||
"ui.agent_chat_custom_hint": "Qualsiasi programma che comunica tramite Agent Client Protocol su stdin e stdout. Non viene eseguito da una shell.",
|
||||
"ui.agent_chat_denied": "Negato",
|
||||
"ui.agent_chat_deny": "Nega",
|
||||
@@ -4195,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Tutto",
|
||||
"ui.agent_chat_mode_ask": "Chiedi",
|
||||
"ui.agent_chat_mode_edits": "Modifiche",
|
||||
"ui.agent_chat_model_custom": "Usa personalizzato: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} non può usare gli strumenti di SolidCraft, quindi può solo chattare. Scegli un modello con supporto per gli strumenti.",
|
||||
"ui.agent_chat_model_rejected": "L'agente non ha accettato {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Cerca modelli…",
|
||||
"ui.agent_chat_model_tip": "Il modello usato dall'agente. La scelta viene memorizzata per questo agente.",
|
||||
"ui.agent_chat_models_failed": "Impossibile elencare i modelli: {error}",
|
||||
"ui.agent_chat_models_heading": "Modelli disponibili",
|
||||
"ui.agent_chat_models_listing": "Elenco dei modelli in corso…",
|
||||
"ui.agent_chat_models_none": "L'endpoint non elenca alcun modello",
|
||||
"ui.agent_chat_models_not_a_list": "La risposta non era un elenco di modelli",
|
||||
"ui.agent_chat_new": "Nuova chat",
|
||||
"ui.agent_chat_new_confirm": "Avviare una nuova chat? Quella corrente verrà cancellata.",
|
||||
"ui.agent_chat_next_picture": "Immagine successiva",
|
||||
@@ -4478,21 +4407,14 @@
|
||||
"ui.construction.contact_hint": "Tangente: seleziona un cerchio/arco o una spline/conica aperta, poi il punto di contatto (curve a forma libera: estremi)",
|
||||
"ui.construction.external_hint": "Tangente: seleziona un cerchio o un arco, un punto esterno, poi la soluzione desiderata",
|
||||
"ui.construction.free_hint": "Retta di costruzione: fai clic su due punti; clic destro per ricominciare",
|
||||
"ui.construction.normal_needs_length": "Una retta infinita è sempre di costruzione: digita una lunghezza per disegnare una linea normale",
|
||||
"ui.construction.point_hint": "Retta di costruzione: fai clic sul punto di passaggio",
|
||||
"ui.construction.reference_hint": "Retta di costruzione: seleziona una linea retta, poi un punto; clic destro per cambiare il riferimento",
|
||||
"ui.construction.ribbon_label": "Linee aus.",
|
||||
"ui.construction.side_hint": "Retta di costruzione: fai clic sul lato verso cui prosegue la retta; clic destro per ricominciare",
|
||||
"ui.context_menu": "Menu contestuale",
|
||||
"ui.continuity_crease": "Spigolo vivo",
|
||||
"ui.continuity_curvature": "Curvatura",
|
||||
"ui.continuity_curvature_change": "Variazione di curvatura",
|
||||
"ui.continuity_g0": "G0 spigolo vivo",
|
||||
"ui.continuity_g1": "G1 tangenza",
|
||||
"ui.continuity_g2": "G2 curvatura",
|
||||
"ui.continuity_gap": "Gioco",
|
||||
"ui.continuity_position": "Posizione",
|
||||
"ui.continuity_tangent": "Tangente",
|
||||
"ui.copy": "Copia",
|
||||
"ui.corner": "Angolo",
|
||||
"ui.corners": "Angoli",
|
||||
@@ -4753,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Nascondi origine",
|
||||
"ui.hidePlanes": "Nascondi piani",
|
||||
"ui.hideProfile": "Nascondi profilo",
|
||||
"ui.hide_component_history": "Nascondi la sua cronologia",
|
||||
"ui.hide_origin_by_default": "Nascondi l'origine per impostazione predefinita",
|
||||
"ui.hide_origin_note": "I nuovi progetti partono con la cartella Origine nascosta. Un progetto conserva la propria impostazione dell'origine dopo che l'hai mostrata o nascosta.",
|
||||
"ui.history_back": "Indietro di un passaggio",
|
||||
@@ -4761,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "fai doppio clic sullo spigolo",
|
||||
"ui.history_first": "Vai all’inizio",
|
||||
"ui.history_forward": "Avanti di un passaggio",
|
||||
"ui.history_hide_hidden": "Nascondi componenti nascosti",
|
||||
"ui.history_hide_names": "Nascondi i nomi",
|
||||
"ui.history_last": "Vai alla fine",
|
||||
"ui.history_marker_tip": "Marcatore della cronologia: trascinalo per far tornare indietro il modello; le nuove lavorazioni vanno qui",
|
||||
@@ -4771,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Mostra pulsante Riproduci",
|
||||
"ui.history_play_finish_action": "Termina l'azione corrente prima di riprodurre la cronologia",
|
||||
"ui.history_play_roll_back": "Sposta indietro il marcatore della cronologia prima di riprodurre",
|
||||
"ui.history_show_hidden": "Mostra componenti nascosti",
|
||||
"ui.history_show_names": "Mostra nomi",
|
||||
"ui.history_sketch_listed_under": "Elencato sotto {feature}",
|
||||
"ui.history_title": "Cronologia",
|
||||
"ui.history_toggle_names": "Mostra o nascondi i nomi delle lavorazioni",
|
||||
"ui.history_waits_for_editor": "Annulla, Ripeti e Cronologia restano in attesa finché l'editor della lavorazione non si è aperto",
|
||||
"ui.hole": "Foro",
|
||||
"ui.hole_tap_type": "Tipo di maschiatura del foro",
|
||||
"ui.hole_type": "Tipo di foro",
|
||||
@@ -5057,7 +4975,6 @@
|
||||
"ui.showDimension": "Mostra quota",
|
||||
"ui.showOrigin": "Mostra origine",
|
||||
"ui.showPlanes": "Mostra piani",
|
||||
"ui.show_component_history": "Mostra la sua cronologia",
|
||||
"ui.show_constraints": "Vincoli",
|
||||
"ui.show_construction_geometries": "Geometrie di costruzione",
|
||||
"ui.show_dimensions": "Quote",
|
||||
@@ -5240,7 +5157,7 @@
|
||||
"update.check_unavailable": "La verifica degli aggiornamenti non è disponibile per questa installazione.",
|
||||
"update.checking": "Controllo…",
|
||||
"update.copy": "Copia",
|
||||
"update.dmg_opened": "L'immagine disco è aperta e verificata: esci da SolidCraft, trascina il nuovo SolidCraft in Applicazioni sostituendo la copia precedente, poi riapri SolidCraft.",
|
||||
"update.dmg_opened": "L'immagine disco è aperta e verificata: trascina SolidCraft in Applicazioni sostituendo la copia precedente, poi riavvia SolidCraft.",
|
||||
"update.download": "Scarica",
|
||||
"update.download_open": "Scarica e apri",
|
||||
"update.downloading": "Download e verifica… {done} di {total} MB",
|
||||
@@ -5284,7 +5201,6 @@
|
||||
"web.open_link": "Aprire il progetto collegato?",
|
||||
"web.open_link_confirm": "Ci sono modifiche non salvate. Aprire il progetto collegato in una nuova scheda?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ scegli · Invio / Tab completa · Esc chiudi",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ scegli · Enter applica questo valore",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ scegli · Enter applica quanto digitato · Esc chiudi",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter continua su GitHub."
|
||||
}
|
||||
|
||||
@@ -325,7 +325,6 @@
|
||||
"Move Type": "이동 유형",
|
||||
"Move down": "아래로 이동",
|
||||
"Move earlier": "앞으로 이동",
|
||||
"Move holes by clicking or dragging": "클릭 또는 드래그로 이동",
|
||||
"Move later": "뒤로 이동",
|
||||
"Move up": "위로 이동",
|
||||
"Name": "Name",
|
||||
@@ -425,7 +424,6 @@
|
||||
"Rename Folder": "폴더 이름 바꾸기",
|
||||
"Render Stage": "렌더링 단계",
|
||||
"Reopen the designs that were open when SolidCraft was last closed": "지난번에 열려 있던 디자인 다시 열기",
|
||||
"Repair Part": "부품 복구",
|
||||
"Replace existing library file": "기존 라이브러리 파일 바꾸기",
|
||||
"Report a bug — review a screenshot and open a GitHub draft": "버그 신고 — 스크린샷을 검토하고 GitHub 초안 열기",
|
||||
"Report text copied. Paste it into GitHub and submit your report there.": "보고서 텍스트를 복사했습니다. GitHub에 붙여넣고 그곳에서 보고서를 제출하세요.",
|
||||
@@ -506,7 +504,7 @@
|
||||
"Show Agent Cursor": "에이전트 커서 표시",
|
||||
"Show in folder": "폴더에서 보기",
|
||||
"Show the pointer an AI agent moves (#35)": "AI 에이전트가 움직이는 포인터 표시 (#35)",
|
||||
"Show tool at cursor": "커서에 도구 표시",
|
||||
"Show tool at cursor": "커서 옆에 도구 표시",
|
||||
"Signed axes": "부호 있는 축",
|
||||
"Size": "크기",
|
||||
"Sketch constraints": "스케치 구속 조건",
|
||||
@@ -517,7 +515,6 @@
|
||||
"Sketch points": "스케치 점",
|
||||
"Slider": "슬라이더",
|
||||
"Slot width": "슬롯 폭",
|
||||
"Snap to face and edge centres": "중심에 스냅",
|
||||
"SolidCraft closed before these designs were saved. Their last autosave can be opened.": "이 디자인들이 저장되기 전에 SolidCraft가 종료되었습니다. 마지막 자동 저장본을 열 수 있습니다.",
|
||||
"Spacing 2 must be a non-zero number": "간격 2는 0이 아닌 숫자여야 합니다",
|
||||
"Spacing must be a non-zero number": "간격은 0이 아닌 숫자여야 합니다",
|
||||
@@ -643,32 +640,26 @@
|
||||
"a picked edge is gone": "선택한 모서리가 더 이상 없습니다",
|
||||
"a picked edge's body is gone": "선택한 모서리의 바디가 더 이상 없습니다",
|
||||
"add a key for a joint first": "먼저 관절의 키를 추가하세요",
|
||||
"ai.prefs.allow_control": "AI 에이전트 연결 허용",
|
||||
"ai.prefs.allow_control_note": "로컬 채널(MCP)입니다. 이 컴퓨터 밖으로 아무것도 나가지 않습니다.\n\n이 컴퓨터에서만 제어 채널을 엽니다(127.0.0.1, 포트 27878 또는 다음 빈 포트). 연결하는 프로그램은 시작할 때마다 새로 생성되며 현재 사용자 계정만 읽을 수 있는 비밀 토큰을 제시해야 합니다. SolidCraft를 다음에 시작할 때 적용됩니다. 끄면 --control로 실행하지 않는 한 아무것도 수신하지 않습니다.",
|
||||
"ai.prefs.availability": "사용 설정",
|
||||
"ai.prefs.background_commands": "느린 명령을 백그라운드에서 실행",
|
||||
"ai.prefs.connect_apps": "AI 앱에 SolidCraft 추가",
|
||||
"ai.prefs.connect_apps_note": "끄면 추가한 항목이 다시 제거됩니다.\n\nSolidCraft 시작 시 이 컴퓨터에서 찾은 AI 앱(OpenCode, Claude Desktop, Claude Code, Cursor, Codex, Gemini CLI, Copilot CLI, Windsurf, Zed, VS Code)에 MCP 서버로 추가합니다. solidcraft 항목만 추가하거나 경로를 갱신하며, 사용자가 변경한 항목은 그대로 두고 파일을 먼저 백업합니다. 끄면 추가했던 항목을 제거합니다. 도움말 > AI 어시스턴트 연결…에서 수동으로 실행할 수도 있습니다.",
|
||||
"ai.prefs.connect_at_start": "시작할 때 마지막 에이전트 연결",
|
||||
"ai.prefs.follow_camera": "카메라 따라가기",
|
||||
"ai.prefs.availability": "어시스턴트 제어",
|
||||
"ai.prefs.background_commands": "에이전트 명령을 백그라운드에서 실행",
|
||||
"ai.prefs.follow_camera": "카메라를 스케치로 돌리기",
|
||||
"ai.prefs.follow_camera_off": "카메라 자동 따라가기가 꺼져 있습니다.",
|
||||
"ai.prefs.follow_camera_on": "에이전트가 만드는 스케치를 향해 카메라가 회전합니다.",
|
||||
"ai.prefs.intro": "SolidCraft의 AI 에이전트.",
|
||||
"ai.prefs.make_default": "기본값으로 설정",
|
||||
"ai.prefs.intro": "에이전트, 권한, 그리고 작업이 표시되는 방식.",
|
||||
"ai.prefs.make_default": "기본값으로 사용",
|
||||
"ai.prefs.no_agents": "아직 에이전트가 없습니다. 에이전트를 추가하거나 새로 고쳐서 설치된 에이전트를 찾으세요.",
|
||||
"ai.prefs.no_saved_approvals": "저장된 승인이 없습니다.",
|
||||
"ai.prefs.open_at_start": "시작할 때 채팅 열기",
|
||||
"ai.prefs.override_all_warning": "이 에이전트는 묻지 않고 설계를 저장, 덮어쓰기, 내보내기 또는 교체할 수 있습니다.",
|
||||
"ai.prefs.permission_all": "모두 실행",
|
||||
"ai.prefs.permission_all_summary": "묻지 않음",
|
||||
"ai.prefs.permission_ask": "먼저 묻기",
|
||||
"ai.prefs.permission_ask_summary": "모든 변경 전에 확인",
|
||||
"ai.prefs.permission_edits": "편집 허용",
|
||||
"ai.prefs.permission_edits_summary": "자유롭게 편집하고 나머지는 확인",
|
||||
"ai.prefs.permission_all": "모두 허용",
|
||||
"ai.prefs.permission_all_summary": "사용 가능한 SolidCraft 도구가 묻지 않고 실행됩니다.",
|
||||
"ai.prefs.permission_ask": "변경 전에 묻기",
|
||||
"ai.prefs.permission_ask_summary": "모든 설계 변경과 파일 작업을 승인합니다.",
|
||||
"ai.prefs.permission_edits": "모델 편집 허용",
|
||||
"ai.prefs.permission_edits_summary": "모델 편집은 실행되고, 파일 및 파괴적인 작업은 계속 묻습니다.",
|
||||
"ai.prefs.permission_scope": "SolidCraft 도구에만 적용됩니다. 에이전트의 셸과 파일 편집은 자체 권한을 사용합니다.",
|
||||
"ai.prefs.pointer_preview_hidden": "포인터 숨김",
|
||||
"ai.prefs.pointer_preview_speed": "에이전트 포인터 · {speed}",
|
||||
"ai.prefs.pointer_speed": "속도",
|
||||
"ai.prefs.pointer_speed": "동작 속도",
|
||||
"ai.prefs.privacy_note": "선택한 에이전트 또는 엔드포인트만 대화를 받습니다. 엔드포인트 키는 시스템 키체인에 저장됩니다.",
|
||||
"ai.prefs.show_pointer": "에이전트 포인터 표시",
|
||||
"ai.prefs.turn_limit_hint": "메시지 하나에 어시스턴트가 수행할 수 있는 설계 변경 도구 호출의 최대 횟수입니다.",
|
||||
@@ -691,7 +682,6 @@
|
||||
"browser.edit_canvas_title": "캔버스 편집: {name}",
|
||||
"browser.edit_point_cloud_title": "포인트 클라우드 편집: {name}",
|
||||
"browser.move_title": "이동: {name}",
|
||||
"browser.needs_repair": "{name}: 복구 필요",
|
||||
"browser.parameters": "매개변수 ({count})",
|
||||
"browser.precision": "정밀도: {precision}",
|
||||
"browser.sketch_attachment_lost": "{name} (부착 해제됨)",
|
||||
@@ -727,7 +717,6 @@
|
||||
"cmd.assembly_mass_properties": "질량 특성",
|
||||
"cmd.body.print_face": "출력 바닥면",
|
||||
"cmd.body_rename": "바디 이름 변경",
|
||||
"cmd.body_repair": "부품 복구",
|
||||
"cmd.browser_group": "새 그룹",
|
||||
"cmd.browser_move": "브라우저 항목 이동",
|
||||
"cmd.browser_order": "브라우저 항목 정렬",
|
||||
@@ -801,8 +790,6 @@
|
||||
"cmd.diagnostics_crash_reports": "충돌 보고서",
|
||||
"cmd.diagnostics_crash_reports_clear": "충돌 보고서 삭제",
|
||||
"cmd.diagnostics_crash_test": "충돌 테스트",
|
||||
"cmd.diagnostics_profile": "프로파일",
|
||||
"cmd.diagnostics_validation": "검증",
|
||||
"cmd.doc_recover": "설계 복구",
|
||||
"cmd.doc_recovery_discard": "복구된 설계 폐기",
|
||||
"cmd.doc_recovery_list": "복구 가능한 설계",
|
||||
@@ -864,11 +851,6 @@
|
||||
"cmd.joint_screw": "나사",
|
||||
"cmd.joint_solve": "관절 해석",
|
||||
"cmd.joint_sweep": "관절 스윕",
|
||||
"cmd.layout.apply": "레이아웃 적용",
|
||||
"cmd.layout.get": "레이아웃 확인",
|
||||
"cmd.layout.reset": "레이아웃 초기화",
|
||||
"cmd.layout.save": "레이아웃 저장",
|
||||
"cmd.layout.workspace.select": "레이아웃 작업 공간 선택",
|
||||
"cmd.library_apply": "로컬 라이브러리 항목 적용",
|
||||
"cmd.library_export": "설계 라이브러리 내보내기",
|
||||
"cmd.library_inspect": "로컬 라이브러리 확인",
|
||||
@@ -898,21 +880,6 @@
|
||||
"cmd.occurrence_ground": "고정",
|
||||
"cmd.occurrence_move": "인스턴스 이동",
|
||||
"cmd.occurrence_properties": "인스턴스 물리 속성",
|
||||
"cmd.panel.activate": "패널 탭 활성화",
|
||||
"cmd.panel.close": "패널 닫기",
|
||||
"cmd.panel.collapse": "패널 구역 접기",
|
||||
"cmd.panel.dismiss": "패널 임시 보기 닫기",
|
||||
"cmd.panel.float": "패널 띄우기",
|
||||
"cmd.panel.focus": "패널에 초점 맞추기",
|
||||
"cmd.panel.list": "패널 목록",
|
||||
"cmd.panel.move": "패널 이동",
|
||||
"cmd.panel.native_close": "별도 창의 패널 구역 다시 도킹",
|
||||
"cmd.panel.open": "패널 열기",
|
||||
"cmd.panel.peek": "접힌 패널 임시 보기",
|
||||
"cmd.panel.popout": "패널을 별도 창으로 이동",
|
||||
"cmd.panel.reset_pair": "패널 구분선 초기화",
|
||||
"cmd.panel.resize": "패널 구역 크기 조정",
|
||||
"cmd.panel.resize_pair": "패널 구분선 이동",
|
||||
"cmd.parameters_add": "사용자 매개 변수 추가",
|
||||
"cmd.parameters_change": "매개 변수 변경",
|
||||
"cmd.parameters_comment": "매개 변수에 설명 추가",
|
||||
@@ -1133,7 +1100,6 @@
|
||||
"cmd.timeline_stop": "기록 재생 중지",
|
||||
"cmd.timeline_suppress": "특성 비활성화",
|
||||
"cmd.ui.assistant": "도우미 표시 전환",
|
||||
"cmd.view.navigator": "사용자 지정 뷰 탐색기",
|
||||
"cmd.view.render_browser": "렌더 브라우저",
|
||||
"cmd.view.render_dock": "렌더 도크 이동",
|
||||
"cmd.view.render_focus": "렌더 집중 보기",
|
||||
@@ -1198,7 +1164,6 @@
|
||||
"crash.report": "보고…",
|
||||
"crash.unclean_explanation": "이후 실행 시 감지되었으며 정확한 종료 시각은 기록되지 않았습니다. 시스템 종료나 강제 중지로도 비정상 종료가 발생할 수 있습니다.",
|
||||
"delete.and_more": "… 외 {count}개",
|
||||
"diagnostic.reselect_reference": "{feature}: 참조 `{reference}`이(가) 선택했을 때의 모습으로 더 이상 존재하지 않습니다. 다시 선택하세요",
|
||||
"dialog.bodies_count": "바디 {count}개",
|
||||
"dialog.clearance_invalid": "간격은 0 이상의 길이여야 합니다",
|
||||
"dialog.compute": "계산",
|
||||
@@ -1206,16 +1171,10 @@
|
||||
"dialog.computing": "계산 중… {seconds}초",
|
||||
"dialog.could_not_be_checked": "확인할 수 없음",
|
||||
"dialog.custom_axis": "↖ 사용자 정의 축",
|
||||
"dialog.cylinder_face_tip": "원기둥이 놓일 모델의 면 또는 꼭짓점: 여기를 클릭한 다음 뷰에서 그것을 클릭. 비워 두면 X, Y, Z로 배치합니다.",
|
||||
"dialog.dimension_insert_hint": "다른 구동 치수를 두 번 클릭하여 이름 삽입",
|
||||
"dialog.dimension_name": "치수: {name}",
|
||||
"dialog.dimension_step_down": "{step} 줄이기",
|
||||
"dialog.dimension_step_expression": "숫자만 증감할 수 있으며 수식은 입력한 그대로 유지됩니다",
|
||||
"dialog.dimension_step_up": "{step} 늘리기",
|
||||
"dialog.edit_title": "{title} 편집",
|
||||
"dialog.pattern_sketch_not_object": "바디나 피처를 선택하세요. 스케치는 둘 다 아닙니다. 곡선은 스케치 안의 패턴이나 대칭 복사로 반복하거나, 먼저 돌출하세요.",
|
||||
"dialog.primitive_point_tip": "X, Y, Z를 채우는 점: 여기를 클릭한 다음 모델의 꼭짓점, 모서리, 면, 스케치의 점 또는 원점을 클릭.",
|
||||
"dialog.reference_edge_tip": "아래 거리를 재는 기준이 되는, 선택한 면의 직선 모서리: 여기를 클릭한 다음 모서리를 클릭.",
|
||||
"dialog.select": "↖ 선택",
|
||||
"dialog.selected_count": "↖ {count}개 선택됨",
|
||||
"dialog.selection_constraint": "구속 조건 {id}",
|
||||
@@ -1244,9 +1203,6 @@
|
||||
"dxf.tolerance_help": "표시된 바디와 스케치만 내보내며 곡선은 직선 구간으로 근사합니다. 허용 오차가 작을수록 곡선이 매끄러워지지만 처리 시간과 메모리 사용량이 늘어납니다.",
|
||||
"each rail's curves must be in one sketch": "각 레일의 곡선은 하나의 스케치 안에 있어야 합니다",
|
||||
"feature computation ended without a result": "특성 계산이 결과 없이 끝났습니다",
|
||||
"file.open.portal_fallback.no_portal_service": "열린 파일 선택은 데스크톱의 것이 아닙니다: 데스크톱의 파일 선택 서비스를 사용할 수 없습니다.",
|
||||
"file.open.portal_fallback.no_session_bus": "열린 파일 선택은 데스크톱의 것이 아닙니다: D-Bus 세션 버스가 없습니다.",
|
||||
"file.open.portal_fallback.xwayland": "열린 파일 선택은 데스크톱의 것이 아닙니다: XWayland에서는 포털이 부모 창을 거부합니다.",
|
||||
"file_load.built": "지금까지 빌드됨: 피처 {total}개 중 {built}개.",
|
||||
"file_load.converting": "솔리드 변환 중 {done}/{total}",
|
||||
"file_load.inserting": "기하 삽입 중",
|
||||
@@ -1258,14 +1214,6 @@
|
||||
"file_load.reading_percent": "파일 읽는 중... {percent}%",
|
||||
"file_load.rebuilding": "피처 다시 빌드 중 {done}/{total}",
|
||||
"file_load.recovering": "디자인 복구 중",
|
||||
"file_load.step_build": "모델 만드는 중",
|
||||
"file_load.step_feature": "피처 {done}/{total}",
|
||||
"file_load.step_part": "부품 {done}/{total}",
|
||||
"file_load.step_percent": "{percent}%",
|
||||
"file_load.step_read": "파일 읽는 중",
|
||||
"file_load.step_rebuild": "피처 다시 빌드 중",
|
||||
"file_load.step_repair": "부품 확인 및 복구 중",
|
||||
"file_load.step_show": "뷰 준비 중",
|
||||
"file_load.stopping": "이전 작업이 중지되기를 기다리는 중...",
|
||||
"file_load.waiting": "파일 작업 대기 중",
|
||||
"fits the face, e.g. M8": "면에 맞음, 예: M8",
|
||||
@@ -1277,29 +1225,9 @@
|
||||
"help.construct.plane.axis_point": "축과 축 위에 있지 않은 점을 포함하는 구성 평면을 만듭니다. 원점 축, 구성 축, 직선 모서리 또는 스케치 직선을 선택한 다음 꼭짓점, 스케치 점 또는 구성점을 선택합니다. 축 위의 점으로는 평면을 하나로 결정할 수 없습니다.",
|
||||
"help.font_details": "인터페이스는 egui의 내장 글꼴을 사용합니다(Ubuntu Font Licence 및 MIT/Bitstream Vera 라이선스의 Ubuntu Light와 Hack, OFL 및 MIT의 Noto Emoji와 이모지 아이콘 글꼴). 스케치 텍스트는 Liberation Sans(SIL OFL 1.1)를 사용합니다. The Crafting Apps의 선택적 글꼴 팩 craft-fonts(BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans CJK SC, Noto Sans Arabic; SIL OFL 1.1)는 SolidCraft 빌드에 포함되지 않습니다.",
|
||||
"help.fonts": "글꼴",
|
||||
"help.layout.apply": "저장된 레이아웃을 현재 작업 공간에 적용합니다. 유지 중인 편집기의 초안은 보존됩니다.",
|
||||
"help.layout.get": "확정된 레이아웃과 등록된 작업 공간을 확인합니다.",
|
||||
"help.layout.reset": "현재 작업 공간에 등록된 기본 레이아웃을 복원합니다. 사용자 사전 설정은 유지됩니다.",
|
||||
"help.layout.save": "전체 레이아웃에 이름을 지정해 저장합니다. 사용자 사전 설정을 바꾸려면 명시적인 덮어쓰기 허용이 필요합니다.",
|
||||
"help.layout.workspace.select": "등록된 레이아웃 작업 공간을 선택하고 이전 레이아웃을 기억합니다.",
|
||||
"help.licences": "라이선스",
|
||||
"help.mit": "MIT 라이선스",
|
||||
"help.notice": "NOTICE",
|
||||
"help.panel.activate": "키보드 초점을 요청하지 않고 열린 패널 탭을 선택합니다.",
|
||||
"help.panel.close": "콘텐츠 수명 주기 검사를 거쳐 패널을 닫습니다. 배치는 기억됩니다.",
|
||||
"help.panel.collapse": "요청한 치수를 유지하면서 패널 구역을 접거나 펼칩니다.",
|
||||
"help.panel.dismiss": "접힌 패널의 임시 보기를 닫습니다.",
|
||||
"help.panel.float": "패널을 위치가 명시된 부동 구역으로 이동합니다.",
|
||||
"help.panel.focus": "패널에 초점을 맞춥니다. 필요하면 유지된 상태를 다시 열고 구역을 펼칩니다.",
|
||||
"help.panel.list": "등록된 패널 종류, 확정된 인스턴스, 활성 탭, 사용 가능 여부와 호스트 기능을 확인합니다.",
|
||||
"help.panel.move": "패널을 새 구역으로 이동하거나 기존 탭 그룹에 추가합니다.",
|
||||
"help.panel.native_close": "콘텐츠를 취소하지 않고 별도 창의 패널 구역을 기억된 도킹 위치로 되돌립니다.",
|
||||
"help.panel.open": "패널 종류와 명시적인 인스턴스 식별자로 패널을 엽니다. 새 인스턴스에는 배치가 필요합니다.",
|
||||
"help.panel.peek": "저장된 크기나 접힘 상태를 바꾸지 않고 접힌 패널을 임시로 표시합니다.",
|
||||
"help.panel.popout": "지원되는 경우 패널을 별도 창으로 이동합니다. 브라우저에서는 부동 구역을 사용합니다.",
|
||||
"help.panel.reset_pair": "인접한 도킹 구역 쌍을 공통 제약 안에서 기본 크기 비율로 복원합니다.",
|
||||
"help.panel.resize": "모든 탭의 제약 안에서 패널 구역의 바깥 너비와 높이를 설정합니다.",
|
||||
"help.panel.resize_pair": "공유하는 전체 길이를 유지하면서 인접한 도킹 구역의 크기를 함께 조정합니다.",
|
||||
"help.pointcloud.insert": "PLY, XYZ 또는 PTS 스캔을 모델 옆에 포인트 클라우드로 삽입합니다. 측정 데이터를 참고하여 모델링할 때 사용합니다.",
|
||||
"help.sketch.construction_line": "두 점을 지나는 무한 보조 직선을 만듭니다. 스케치 프로파일을 나누지 않고 작도할 수 있습니다.",
|
||||
"help.sketch.construction_line.angle": "스케치 X축을 기준으로 지정한 각도의 무한 보조 직선을 만듭니다. 점을 선택한 뒤 각도를 입력하거나 방향을 선택합니다.",
|
||||
@@ -1373,27 +1301,6 @@
|
||||
"home.start_hint": "설계를 만들거나, 최근 파일을 열거나, 예제를 살펴보세요.",
|
||||
"home.whats_new": "새로운 기능",
|
||||
"home.yesterday": "어제",
|
||||
"import_report.details": "세부 정보:",
|
||||
"import_report.file_one": "파일에 다른 문제가 1개 있었습니다.",
|
||||
"import_report.file_other": "파일에 다른 문제가 {count}개 있었습니다.",
|
||||
"import_report.hint": "이 부품을 만든 프로그램에서 다시 내보내면 해결될 수 있습니다. 세부 정보는 경고 메시지 복사에 있습니다.",
|
||||
"import_report.left_out_one": "면 1개를 복구하지 못해 빠져 있습니다.",
|
||||
"import_report.left_out_other": "면 {count}개를 복구하지 못해 빠져 있습니다.",
|
||||
"import_report.needs_repair_one": "부품 1개는 모델링하기 전에 복구해야 합니다.",
|
||||
"import_report.needs_repair_other": "부품 {count}개는 모델링하기 전에 복구해야 합니다.",
|
||||
"import_report.not_imported_one": "부품 1개를 가져오지 못했습니다.",
|
||||
"import_report.not_imported_other": "부품 {count}개를 가져오지 못했습니다.",
|
||||
"import_report.not_shown_one": "면 1개를 그리지 못해 숨겨져 있습니다.",
|
||||
"import_report.not_shown_other": "면 {count}개를 그리지 못해 숨겨져 있습니다.",
|
||||
"import_report.open_one": "부품 1개는 면 사이에 틈이 있어 열린 바디로 들어왔습니다.",
|
||||
"import_report.open_other": "부품 {count}개는 면 사이에 틈이 있어 열린 바디로 들어왔습니다.",
|
||||
"import_report.refused_one": "부품 1개를 솔리드로 만들지 못해 가져오지 않았습니다.",
|
||||
"import_report.refused_other": "부품 {count}개를 솔리드로 만들지 못해 가져오지 않았습니다.",
|
||||
"import_report.repaired_one": "부품 1개의 작은 결함을 복구했습니다.",
|
||||
"import_report.repaired_other": "부품 {count}개의 작은 결함을 복구했습니다.",
|
||||
"import_report.solids": "부품 {parts}개 중 {solids}개를 솔리드로 가져왔습니다.",
|
||||
"import_report.surfaces_one": "부품 1개를 솔리드로 만들지 못해 서피스로 들어왔습니다.",
|
||||
"import_report.surfaces_other": "부품 {count}개를 솔리드로 만들지 못해 서피스로 들어왔습니다.",
|
||||
"install.button_tip": "SolidCraft를 앱으로 설치합니다. 별도 창에서 열리며 오프라인에서도 작동합니다",
|
||||
"install.dock_1": "파일 › Dock에 추가를 선택합니다.",
|
||||
"install.dock_2": "추가를 클릭합니다.",
|
||||
@@ -1418,13 +1325,8 @@
|
||||
"joint.from_bodies_hint": "선택한 각 바디가 제자리에 그대로 있으면서 자체의 새 구성 요소로 옮겨집니다.",
|
||||
"joint.one_body": "두 선택이 모두 {body}에 있습니다. 두 번째는 다른 부품에서 선택하세요.",
|
||||
"joint.one_component": "이 부품들은 한 구성 요소의 바디입니다: {bodies}. 관절은 한 구성 요소를 다른 구성 요소에 대해 움직이므로 부품마다 자체 구성 요소가 필요합니다.",
|
||||
"joint_move.coarse": "축 근처 단계",
|
||||
"joint_move.conflict": "이 부품의 조인트가 다른 조인트와 충돌합니다: {joint}.",
|
||||
"joint_move.conflict_hint": "충돌하는 조인트를 편집, 비활성화 또는 삭제하여 다른 조인트가 부품을 배치할 수 있게 하세요.",
|
||||
"joint_move.fine": "축에서 먼 단계",
|
||||
"joint_move.legacy_hint": "끄면 이전 이동 패널을 사용합니다.",
|
||||
"joint_move.precision": "미세 단계를 사용하려면 점선 가이드 바깥으로 당기세요.",
|
||||
"joint_move.use_handles": "이동에 조인트 모션 핸들 사용",
|
||||
"keymap.command_id": "명령 ID",
|
||||
"keymap.empty": "일치하는 명령이 없습니다",
|
||||
"keymap.use_anyway": "그래도 여기서 {key} 사용",
|
||||
@@ -1441,7 +1343,6 @@
|
||||
"material.selection.mixed": "서로 다른 재질",
|
||||
"measure.at": "위치",
|
||||
"measure.item": "항목 {number}",
|
||||
"measure.line_to_centre": "선에서 중심까지",
|
||||
"messages.ago_h": "{n}시간 전",
|
||||
"messages.ago_min": "{n}분 전",
|
||||
"messages.ago_s": "{n}초 전",
|
||||
@@ -1468,8 +1369,6 @@
|
||||
"nav.blender.help": "가운데 드래그는 회전, Shift+가운데 드래그는 이동, Ctrl+가운데 드래그는 확대/축소입니다. 오른쪽 드래그는 카메라에 영향을 주지 않습니다",
|
||||
"nav.creo.help": "가운데 버튼을 끌면 회전, Shift+가운데 버튼은 이동, Ctrl+가운데 버튼을 위아래로 끌면 확대/축소합니다(아래로 끌면 확대). 휠을 굴려도 확대/축소합니다",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.drag_keeps_mode.help": "가운데 또는 오른쪽 버튼 드래그는 도중에 키를 누르거나 떼어도 버튼을 놓을 때까지 시작할 때의 회전, 이동 또는 확대/축소를 계속합니다. 끄면 드래그하는 동안 누르고 있는 키가 매 순간 결정합니다.",
|
||||
"nav.drag_keeps_mode.label": "드래그는 시작할 때의 탐색 방식을 유지",
|
||||
"nav.free_orbit.help": "마우스와 3D 마우스로 롤을 유지하며 양 극점을 넘어 회전합니다. Shift 축 스냅은 비활성화됩니다.",
|
||||
"nav.free_orbit.label": "자유 회전",
|
||||
"nav.free_orbit.snap_help": "마우스와 3D 마우스로 양쪽 극점을 지나 회전할 수 있으며 롤 각도가 유지됩니다. 궤도 회전 드래그 중 Shift를 누르면 보기 방향과 롤 각도가 스냅됩니다.",
|
||||
@@ -1497,12 +1396,6 @@
|
||||
"navbar.assistant": "어시스턴트",
|
||||
"navbar.custom.add_slot": "{slot} 추가",
|
||||
"navbar.custom.always": "항상 표시",
|
||||
"navbar.custom.anchor.bottom_centre": "하단 가운데",
|
||||
"navbar.custom.anchor.bottom_left": "왼쪽 아래",
|
||||
"navbar.custom.anchor.bottom_right": "오른쪽 아래",
|
||||
"navbar.custom.anchor.top_centre": "상단 가운데",
|
||||
"navbar.custom.anchor.top_left": "왼쪽 위",
|
||||
"navbar.custom.anchor.top_right": "오른쪽 위",
|
||||
"navbar.custom.available": "{available} · 막대로 끌어 놓거나 추가를 누르세요",
|
||||
"navbar.custom.buttons": "버튼",
|
||||
"navbar.custom.compact": "컴팩트",
|
||||
@@ -1536,7 +1429,6 @@
|
||||
"navbar.custom.reset_default": "기본값으로 재설정",
|
||||
"navbar.custom.right": "오른쪽으로 이동",
|
||||
"navbar.custom.select": "선택한 칸이 없습니다. 막대에서 칸을 선택하면 이동하거나 제거하거나 모양을 바꿀 수 있습니다.",
|
||||
"navbar.custom.separator_hint": "버튼 그룹 사이의 구분선입니다. 막대로 끌거나 추가를 누르세요. 원하는 만큼 사용할 수 있습니다.",
|
||||
"navbar.custom.separators_full": "막대에 구분선이 이미 네 개 있습니다.",
|
||||
"navbar.custom.show": "탐색 막대 표시",
|
||||
"navbar.custom.shows": "표시 내용",
|
||||
@@ -1645,53 +1537,20 @@
|
||||
"prefs.add_item": "항목 추가",
|
||||
"prefs.alt_frees_manipulators": "Alt / Option 키로 조작 화살표도 자유롭게 움직입니다",
|
||||
"prefs.anti_aliasing_note": "울퉁불퉁한 모서리를 부드럽게 합니다.",
|
||||
"prefs.appearance.colour_vision_help": "의미를 전달하는 색상을 변경합니다: 스케치 상태, 축, 굽힘, 분석 및 아이콘.",
|
||||
"prefs.appearance.desktop_only": "데스크톱 전용",
|
||||
"prefs.appearance.follow_help": "다른 도구가 파일을 다시 쓰면 다시 불러옵니다.",
|
||||
"prefs.appearance.interface_scale": "인터페이스 배율",
|
||||
"prefs.appearance.next_themes": "다음 테마",
|
||||
"prefs.appearance.option_count": "옵션 {count}개",
|
||||
"prefs.appearance.option_count_one": "옵션 {count}개",
|
||||
"prefs.appearance.previous_themes": "이전 테마",
|
||||
"prefs.appearance.theme_count": "테마 {count}개",
|
||||
"prefs.appearance.theme_own": "테마 자체",
|
||||
"prefs.appearance.viewport_colours": "3D 보기 색상",
|
||||
"prefs.appearance.viewport_logo": "뷰포트 및 로고",
|
||||
"prefs.applies_on_restart": "다시 시작하면 적용됩니다",
|
||||
"prefs.assistant": "어시스턴트",
|
||||
"prefs.assistant_connecting": "연결",
|
||||
"prefs.assistant_follow_help": "스케치의 Look At 동작이 포인터와 함께 보기를 이동합니다.",
|
||||
"prefs.assistant_limit": "제한",
|
||||
"prefs.assistant_limited": "제한됨",
|
||||
"prefs.assistant_pointer_speed": "포인터 속도",
|
||||
"prefs.assistant_seconds": "초",
|
||||
"prefs.assistant_seconds_aria": "초",
|
||||
"prefs.assistant_use_care": "주의해서 사용",
|
||||
"prefs.auto_operation_note": "재료가 닿는 대상에 따라 결합, 새 바디 또는 자르기를 선택합니다.",
|
||||
"prefs.axes": "축",
|
||||
"prefs.axis": "축",
|
||||
"prefs.blur_cost": "상대적인 렌더링 비용입니다. 막대가 많을수록 일반적으로 더 많은 작업이 필요합니다.",
|
||||
"prefs.blur_device": "이 그래픽 장치 또는 렌더링 경로에서는 사용할 수 없습니다",
|
||||
"prefs.blur_guard": "화면의 원활한 응답을 위해 흐림 품질을 낮췄습니다. 반경은 변경되지 않습니다.",
|
||||
"prefs.blur_off": "끄기",
|
||||
"prefs.blur_reduce_transparency": "시스템의 투명도 줄이기 설정에 의해 비활성화됨",
|
||||
"prefs.blur_software": "소프트웨어 그래픽에서는 사용할 수 없습니다 (gpu-software)",
|
||||
"prefs.blur_source": "이 작업 공간 보기에서는 사용할 수 없습니다",
|
||||
"prefs.blur_web": "이 웹 렌더링 경로에서는 사용할 수 없습니다",
|
||||
"prefs.bodies_folders": "바디 폴더",
|
||||
"prefs.bodies_folders_always": "항상 표시",
|
||||
"prefs.bodies_folders_never": "표시 안 함",
|
||||
"prefs.bodies_folders_several": "여러 개",
|
||||
"prefs.bodies_folders_several": "바디가 여러 개일 때만",
|
||||
"prefs.bottom": "아래쪽",
|
||||
"prefs.bottom_centre": "하단 가운데",
|
||||
"prefs.bottom_right": "오른쪽 아래",
|
||||
"prefs.browser": "브라우저",
|
||||
"prefs.browser_components_open": "구성요소를 펼친 상태로 시작",
|
||||
"prefs.browser_components_open_note": "브라우저의 모든 구성요소가 이전처럼 펼쳐진 상태로 시작합니다. 끄면 직접 펼칠 때까지 접혀 있습니다.",
|
||||
"prefs.browser_eye_left": "접기 화살표 옆의 눈 아이콘",
|
||||
"prefs.browser_eye_left": "표시 아이콘을 접기 화살표 옆에 표시",
|
||||
"prefs.browser_eye_left_note": "브라우저 행의 표시 아이콘을 행 오른쪽 끝의 열이 아니라 이전처럼 접기 화살표 옆에 표시합니다.",
|
||||
"prefs.browser_rows_start_closed": "브라우저 행을 접힌 상태로 시작",
|
||||
"prefs.browser_rows_start_closed_note": "브라우저의 배치와 폴더가 접힌 상태로 시작하므로, 컴포넌트가 많은 디자인은 최상위 행만 보여 줍니다. 각 행은 화살표, '모두 펼치기' 또는 '브라우저에서 찾기'로 엽니다. '브라우저에서 찾기'는 바디까지의 행만 열고, 지난번 찾기로 연 행은 다시 접습니다. 3D 뷰에서 바디를 골라도 브라우저는 그대로입니다. 끄면 폴더가 펼친 상태로 시작하고, 배치는 '구성요소를 펼친 상태로 시작' 설정을 따르며, 3D 뷰에서 고르면 브라우저가 그 바디까지 열립니다(이전과 같음).",
|
||||
"prefs.built_in": "기본 제공",
|
||||
"prefs.button_left": "왼쪽 드래그",
|
||||
"prefs.button_middle": "가운데 드래그",
|
||||
@@ -1716,32 +1575,14 @@
|
||||
"prefs.dark_parts_shading_help": "선형 조명과 부드러운 하늘광으로 뷰를 음영 처리하여 어두운 면이 검게 뭉개지지 않고 그라데이션을 유지합니다",
|
||||
"prefs.dead_zone_note": "빗금 친 영역이 데드존입니다. 그 안의 움직임은 무시됩니다.",
|
||||
"prefs.default": "기본값",
|
||||
"prefs.design": "디자인",
|
||||
"prefs.design_note": "새 디자인의 시작 설정입니다.",
|
||||
"prefs.design_tools.ask": "묻기",
|
||||
"prefs.design_tools.degrees": "도",
|
||||
"prefs.design_tools.double_click_help": "연결된 구성요소를 제자리에서 편집할 수도 있습니다.",
|
||||
"prefs.design_tools.ghost": "이동한 부품의 이전 위치를 고스트로 표시",
|
||||
"prefs.design_tools.hole_moves_help": "면을 다시 선택하면 구멍을 제거하지 않고 이동합니다.",
|
||||
"prefs.design_tools.hole_snaps_help": "면의 가운데와 원형 모서리의 중심입니다.",
|
||||
"prefs.design_tools.imported_files": "가져온 파일",
|
||||
"prefs.design_tools.note": "알림",
|
||||
"prefs.design_tools.off": "끄기",
|
||||
"prefs.design_tools.placement_group": "레스트, 보스, 스냅 핏",
|
||||
"prefs.design_tools.rows": "행",
|
||||
"prefs.design": "설계 기본값",
|
||||
"prefs.design_note": "새 설계가 시작할 때의 설정입니다. 열려 있는 설계는 자체 설정을 유지합니다.",
|
||||
"prefs.devices": "3D 마우스",
|
||||
"prefs.dialog_buttons": "대화 상자 버튼",
|
||||
"prefs.dialogs": "대화 상자",
|
||||
"prefs.dialogs_edge": "뷰 가장자리",
|
||||
"prefs.dialogs_left_column": "왼쪽 열",
|
||||
"prefs.dialogs_note": "명령 대화 상자가 열리는 위치: 뷰 가장자리에 고정하거나, 왼쪽 열에서 브라우저와 이력 대신 표시합니다. 대화 상자를 닫으면 브라우저와 이력이 그대로 돌아옵니다.",
|
||||
"prefs.dimension_step_angle": "창의 화살표, 위/아래 키와 휠이 각도에 더하는 값",
|
||||
"prefs.dimension_step_length": "창의 화살표, 위/아래 키와 휠이 길이에 더하는 값",
|
||||
"prefs.dimension_steps": "창의 증감 단위",
|
||||
"prefs.dimension_values": "치수 값",
|
||||
"prefs.dimension_values_note": "치수 값을 입력하는 위치입니다. 창에는 값을 증감하는 화살표가 있으며, 치수 도구는 텍스트를 먼저 배치합니다.",
|
||||
"prefs.dimension_values_view": "뷰에서",
|
||||
"prefs.dimension_values_window": "창에서",
|
||||
"prefs.display": "표시",
|
||||
"prefs.edges": "모서리 및 음영",
|
||||
"prefs.extend": "확장",
|
||||
@@ -1770,23 +1611,6 @@
|
||||
"prefs.gesture_zoom": "확대/축소",
|
||||
"prefs.graphics": "그래픽",
|
||||
"prefs.graphics_note": "속도와 부드러움 중에서 선택합니다. 각 선택의 효과가 샘플에 표시됩니다.",
|
||||
"prefs.graphics_page.current_view": "현재 뷰",
|
||||
"prefs.graphics_page.fastest": "가장 빠르게",
|
||||
"prefs.graphics_page.fold_items": "항목 접기",
|
||||
"prefs.graphics_page.in_view": "뷰 내부",
|
||||
"prefs.graphics_page.live_details": "실시간 세부 정보",
|
||||
"prefs.graphics_page.own_mix": "사용자 조합",
|
||||
"prefs.graphics_page.quick_measure": "빠른 측정",
|
||||
"prefs.graphics_page.reference_geometry_through_bodies": "바디를 통한 참조 형상",
|
||||
"prefs.graphics_page.restart": "다시 시작 필요",
|
||||
"prefs.graphics_page.sharpest": "가장 선명하게",
|
||||
"prefs.graphics_page.single_surface_analysis": "단일 표면 분석",
|
||||
"prefs.graphics_page.status_chips": "상태 칩",
|
||||
"prefs.graphics_page.subtitle": "속도와 부드러움의 균형을 조절합니다.",
|
||||
"prefs.graphics_page.supersampling": "슈퍼샘플링",
|
||||
"prefs.graphics_page.supersampling_adaptive": "뷰가 멈춰 있는 동안만",
|
||||
"prefs.graphics_page.surfaces_edges": "곡면과 모서리",
|
||||
"prefs.graphics_page.view_bar": "뷰 표시줄에서도 사용",
|
||||
"prefs.graphics_presets_note": "프리셋은 아래의 선택 항목을 한 번에 설정합니다.",
|
||||
"prefs.hide_constraint_glyphs_zoomed_out": "축소 시 구속 조건 기호 숨기기",
|
||||
"prefs.hide_constraint_glyphs_zoomed_out_note": "스케치를 충분히 축소하면 구속 조건 기호가 가려지지 않도록 숨깁니다. 끄기: 확대/축소와 관계없이 고정된 화면 크기를 유지합니다.",
|
||||
@@ -1795,7 +1619,6 @@
|
||||
"prefs.history_origin_bodies_note": "왼쪽 또는 브라우저 아래의 히스토리가 활성 구성 요소의 바디 폴더와 원점으로 시작하며, 브라우저처럼 표시 아이콘과 메뉴가 있습니다. 아래쪽 히스토리는 바뀌지 않습니다.",
|
||||
"prefs.history_play_button": "기록 컨트롤에 기록 재생 버튼 표시",
|
||||
"prefs.history_play_button_note": "기록 컨트롤의 한 단계 뒤로와 한 단계 앞으로 사이에 기록 재생을 추가합니다. 컨트롤을 마우스 오른쪽 버튼으로 클릭해 표시하거나 숨길 수도 있습니다.",
|
||||
"prefs.history_sketches": "히스토리의 스케치",
|
||||
"prefs.hold_stop_snapping": "누르고 있으면 스냅 중지",
|
||||
"prefs.home_fit_size": "홈·맞춤 크기",
|
||||
"prefs.home_fit_size_note": "홈, 맞춤, 연 디자인 또는 뷰 탐색기의 뷰 변경으로 모델을 화면에 맞출 때 모델이 뷰에서 차지하는 비율. 아래 설정과 관계없이 적용됩니다. 100 %: 패널, 나침반, 내비게이션 바 사이의 뷰를 가득 채웁니다.",
|
||||
@@ -1810,22 +1633,6 @@
|
||||
"prefs.language_system": "시스템",
|
||||
"prefs.layout": "레이아웃",
|
||||
"prefs.layout_note": "컴팩트는 패널을 창 가장자리에 고정합니다. 브라우저 반투명은 플로팅에서 사용할 수 있습니다. 어시스턴트 위치는 어시스턴트 페이지에서 바꿀 수 있습니다.",
|
||||
"prefs.layout_page.active_commands": "활성 명령 강조",
|
||||
"prefs.layout_page.always": "항상",
|
||||
"prefs.layout_page.card": "카드",
|
||||
"prefs.layout_page.column": "열",
|
||||
"prefs.layout_page.hover": "마우스를 올릴 때",
|
||||
"prefs.layout_page.icons_labels": "아이콘 + 레이블",
|
||||
"prefs.layout_page.ok_cancel": "확인과 취소",
|
||||
"prefs.layout_page.on_hover": "마우스를 올릴 때",
|
||||
"prefs.layout_page.open": "열기",
|
||||
"prefs.layout_page.origin_bodies": "원점과 바디를 위에 표시",
|
||||
"prefs.layout_page.panels": "패널",
|
||||
"prefs.layout_page.pinned_menus": "고정 명령을 메뉴에서 제외",
|
||||
"prefs.layout_page.play_history": "히스토리 재생 버튼",
|
||||
"prefs.layout_page.slot_count": "슬롯 {count} / {max}개",
|
||||
"prefs.layout_page.view_corner": "뷰 모서리에도 표시",
|
||||
"prefs.layout_page.window": "창",
|
||||
"prefs.left": "왼쪽",
|
||||
"prefs.left_click": "왼쪽 클릭",
|
||||
"prefs.line_drag_draws_one": "누른 채 드래그하면 선 하나 그리기",
|
||||
@@ -1847,13 +1654,6 @@
|
||||
"prefs.mouse_heading": "마우스",
|
||||
"prefs.mouse_mapping": "마우스 매핑",
|
||||
"prefs.navigation": "탐색",
|
||||
"prefs.navigation.axes_signed": "축",
|
||||
"prefs.navigation.cube_compact": "큐브",
|
||||
"prefs.navigation.device": "장치",
|
||||
"prefs.navigation.free": "자유",
|
||||
"prefs.navigation.orbit_style": "회전 방식",
|
||||
"prefs.navigation.radial_help": "오른쪽 버튼을 길게 누르거나 튕겨 도구 링을 표시합니다.",
|
||||
"prefs.navigation.turntable": "턴테이블",
|
||||
"prefs.navigation_note": "마우스, 트랙패드, 3D 마우스로 뷰를 움직이는 방식입니다.",
|
||||
"prefs.navigator_fit": "뷰 탐색기로 뷰를 바꾼 뒤 모델 맞춤",
|
||||
"prefs.navigator_fit_note": "큐브의 면, 모서리, 화살표나 기즈모의 축을 클릭하면 어느 방향에서든 전체가 들어가는 한 거리(경계 구)에서 모델의 중심을 바라봅니다. 켜져 있는 동안 홈 뷰도 같은 확대/축소와 중심을 쓰므로 그 뒤에 고른 뷰는 방향만 바뀝니다. 끄면 뷰가 제자리에서 회전하며 대상과 확대/축소를 유지합니다.",
|
||||
@@ -1866,14 +1666,9 @@
|
||||
"prefs.origin_plane_names": "원점 평면 이름",
|
||||
"prefs.origin_plane_names_note": "브라우저, 대화 상자, 목록에 표시되는 이름입니다. {top}은 XY, {front}는 XZ, {right}는 YZ이며 Z가 위쪽입니다. 명령, 스크립트, 저장된 디자인은 XY, XZ, YZ를 유지합니다.",
|
||||
"prefs.overall_speed": "전체 속도",
|
||||
"prefs.parameter_list_rows_tip": "빈 수식 상자에서 아래쪽 화살표로 표시할 매개변수의 최대 개수입니다.",
|
||||
"prefs.persistent_ribbon_pressed": "활성 리본 명령 강조 표시",
|
||||
"prefs.persistent_ribbon_pressed_help": "효과가 켜져 있는 동안 리본 명령을 강조 표시합니다. 끄면 이전의 중립적인 모양과 명령 도움말을 유지합니다.",
|
||||
"prefs.perspective_fov": "시야각",
|
||||
"prefs.plugins_enabled": "사용",
|
||||
"prefs.plugins_installed": "설치됨",
|
||||
"prefs.plugins_intro": "샌드박스 추가 기능.",
|
||||
"prefs.plugins_show_folder": "플러그인 폴더 표시",
|
||||
"prefs.preview_choices": "선택 항목 미리보기",
|
||||
"prefs.preview_tint_new_faces": "미리 보기에서 새 면에 색 입히기",
|
||||
"prefs.preview_tint_new_faces_help": "바디를 변경하는 피처의 미리 보기에서 추가되는 면을 미리 보기 색으로 그릴 수 있습니다. 끄면 바디가 원래 색으로 그려집니다.",
|
||||
@@ -1881,8 +1676,6 @@
|
||||
"prefs.print_face_highlight_tip": "뷰에서 바디의 출력 바닥면을 색칠하고 윤곽을 그리는 강도입니다. 0 %이면 강조하지 않습니다.",
|
||||
"prefs.profile_export": "내보내기",
|
||||
"prefs.profile_imported": "프로필을 가져왔습니다.",
|
||||
"prefs.profile_operations": "작업 프로파일링",
|
||||
"prefs.profile_operations_note": "SMLib 호출, 커널 작업, 피처, 명령, 가져오기에 걸리는 시간을 기록합니다. 에이전트는 diagnostics.profile로 읽습니다.",
|
||||
"prefs.project_options_dialog": "Project 옵션 대화상자",
|
||||
"prefs.project_options_dialog_note": "켜기: 투영 전에 지오메트리 또는 바디와 투영 링크를 선택합니다. 끄기: 기존 직접 선택 작업 흐름을 유지합니다.",
|
||||
"prefs.quality_default": "기본값",
|
||||
@@ -1900,8 +1693,6 @@
|
||||
"prefs.search": "설정 검색",
|
||||
"prefs.search_empty": "일치하는 설정이 없습니다",
|
||||
"prefs.select": "선택",
|
||||
"prefs.select_hidden_edges": "숨은 모서리가 표시된 음영 뷰에서 숨은 모서리 선택",
|
||||
"prefs.select_hidden_edges_help": "숨은 모서리가 표시된 음영 뷰에서 면 뒤의 모서리를 선택하여 필렛에 사용할 수 있습니다. 끄면 v0.30.1의 선택 동작을 유지합니다. 숨긴 바디는 선택되지 않습니다.",
|
||||
"prefs.select_one_item": "항목 하나 선택",
|
||||
"prefs.select_other_hidden": "다른 항목 선택으로 숨겨진 형상 선택",
|
||||
"prefs.select_other_hidden_help": "켜짐: 포인터 근처의 숨겨진 바디, 스케치 및 가려진 모서리를 포함합니다. 꺼짐: 포인터 바로 아래에 보이는 형상만 포함하는 기존 목록을 사용합니다.",
|
||||
@@ -1910,45 +1701,8 @@
|
||||
"prefs.shift_add_ctrl_toggle": "Shift는 추가만, Ctrl / Cmd는 토글",
|
||||
"prefs.shift_add_note": "켬: Shift는 추가, Ctrl / Cmd는 토글. 끔(기본값): 둘 다 토글.",
|
||||
"prefs.shortcuts": "단축키",
|
||||
"prefs.shortcuts_import": "가져오기",
|
||||
"prefs.shortcuts_intro": "각 명령의 키.",
|
||||
"prefs.shortcuts_not_assigned": "할당되지 않음",
|
||||
"prefs.shortcuts_press": "키를 누르세요...",
|
||||
"prefs.single_surface_analysis": "표면 분석 하나만 사용",
|
||||
"prefs.single_surface_analysis_help": "끄면 표면 음영 분석과 연속성 분석을 함께 유지할 수 있습니다. 켜면 마지막으로 시작하거나 변경한 분석만 유지됩니다.",
|
||||
"prefs.sketch.align_points": "다른 스케치 점에 정렬",
|
||||
"prefs.sketch.align_points_note": "그리는 선을 두 축의 점에 동시에 정렬합니다.",
|
||||
"prefs.sketch.auto_dimensions": "새 도형에 모든 치수 지정",
|
||||
"prefs.sketch.auto_dimensions_note": "입력한 값과 도형을 고정하는 모든 치수.",
|
||||
"prefs.sketch.auto_locate": "스케치 원점에서 배치",
|
||||
"prefs.sketch.auto_locate_note": "모든 치수 지정 시 적용.",
|
||||
"prefs.sketch.center_arc": "중심점 호가 커서를 따라감",
|
||||
"prefs.sketch.click_selects": "클릭으로 선택",
|
||||
"prefs.sketch.conflict_colour": "충돌 색상",
|
||||
"prefs.sketch.drawing": "그리기",
|
||||
"prefs.sketch.ellipse_axes": "타원을 축과 함께 그리기",
|
||||
"prefs.sketch.ellipse_axes_note": "장축과 단축을 구성선으로 그려 반축과 축에 치수를 지정할 수 있습니다. 끄면: 타원만 그리며, D는 값을 어디로 끌든 단반경을 주며, 이전처럼 R로 표시되어 그려집니다.",
|
||||
"prefs.sketch.exit_keep": "유지",
|
||||
"prefs.sketch.exit_previous": "이전 뷰로 돌아가기",
|
||||
"prefs.sketch.exit_unless_rotated": "회전하지 않았다면",
|
||||
"prefs.sketch.faces_edges": "면과 모서리",
|
||||
"prefs.sketch.hide_glyphs": "축소 시 기호 숨기기",
|
||||
"prefs.sketch.marks": "스케치의 표시",
|
||||
"prefs.sketch.note": "그리기, 스냅 및 선택.",
|
||||
"prefs.sketch.opening_closing": "열기 및 닫기",
|
||||
"prefs.sketch.prefer_free_points": "겹치는 곳에서는 이동 가능한 점 우선",
|
||||
"prefs.sketch.project_options": "투영 옵션을 먼저 묻기",
|
||||
"prefs.sketch.recent_snap": "두 안내선의 교차점에 스냅",
|
||||
"prefs.sketch.select_other_hidden": "다른 항목 선택에 숨겨진 형상 포함",
|
||||
"prefs.sketch.shortcut_cycles_modes": "단축키를 다시 누르면 모드 전환",
|
||||
"prefs.sketch.shortcut_cycles_modes_tip": "끄면 도구를 다시 시작합니다.",
|
||||
"prefs.sketch.snapping_guides": "스냅 및 안내선",
|
||||
"prefs.sketch.state_icons": "상태 아이콘",
|
||||
"prefs.sketch.tracking_guides": "중심 및 중점 안내선",
|
||||
"prefs.sketch.tracking_guides_note": "마우스를 올린 중심과 모서리 중점에서 임시 가로 및 세로 안내선을 표시합니다.",
|
||||
"prefs.sketch.tracking_points": "스케치 점에서도 안내선 표시",
|
||||
"prefs.sketch.unless_turned_note": "회전하지 않았으면 복귀",
|
||||
"prefs.sketch.whole_bodies": "전체 바디",
|
||||
"prefs.sketch_align_points": "그리는 동안 다른 스케치 점에 맞추기",
|
||||
"prefs.sketch_align_points_note": "그리는 동안 점이 한 스케치 점과 수평으로, 다른 점과 수직으로 동시에 맞춰지고 점선 안내선이 표시됩니다. 클릭하면 두 정렬이 모두 유지됩니다. 끄기: 이전 점에만, 한 번에 한 축씩 맞춥니다.",
|
||||
"prefs.sketch_conflict_colour": "충돌하는 기하를 충돌 색상으로 표시",
|
||||
@@ -1974,8 +1728,6 @@
|
||||
"prefs.source": "소스",
|
||||
"prefs.speed": "속도",
|
||||
"prefs.stand_up_note": "STEP 또는 IGES 파일이 Z가 위쪽인 상태로 열린 후 X축 기준 90도 회전을 제안합니다.",
|
||||
"prefs.stand_up_unknown": "알 수 없는 프로그램의 파일",
|
||||
"prefs.stand_up_unknown_note": "파일에 알려진 프로그램 이름이 없을 때 제안하는 방법입니다.",
|
||||
"prefs.status_bar": "상태 표시줄",
|
||||
"prefs.status_bar_note": "창 아래쪽 가장자리의 얇은 표시줄에 편집 중인 항목, 열려 있는 스케치의 상태(해당 색상으로), 설계 단위가 표시됩니다. 단위를 클릭하면 변경할 수 있습니다. 스케치 상태는 더 이상 뷰 모서리에 표시되지 않습니다.",
|
||||
"prefs.status_done": "완료",
|
||||
@@ -2009,10 +1761,6 @@
|
||||
"prefs.theme_follow_note": "다른 도구가 파일을 다시 쓸 때마다 다시 불러옵니다. 테마를 선택하면 따르기를 중지합니다.",
|
||||
"prefs.theme_follow_off": "끄기",
|
||||
"prefs.theme_follow_stop": "따르기 중지",
|
||||
"prefs.theme_notice_dismiss": "닫기",
|
||||
"prefs.theme_option_blur": "패널 뒤 흐림",
|
||||
"prefs.theme_option_blur_help": "반투명 패널 뒤의 흐림을 물리적 픽셀 단위로 설정합니다. 0이면 꺼집니다.",
|
||||
"prefs.theme_option_invalid": "{option}의 저장된 값이 유효하지 않아 테마 기본값을 사용합니다. 저장된 값은 유지됩니다.",
|
||||
"prefs.theme_option_opacity": "불투명도",
|
||||
"prefs.theme_option_opacity_help": "플로팅 패널의 불투명도를 조정합니다. 뷰포트 색상 위에서도 텍스트를 읽을 수 있도록 최솟값이 적용됩니다.",
|
||||
"prefs.theme_option_tab_colours": "탭 색상",
|
||||
@@ -2021,7 +1769,6 @@
|
||||
"prefs.theme_option_tab_fade_ms_help": "탭을 전환할 때 패널 및 뷰포트 색상을 서서히 변경합니다. 0이면 즉시 전환합니다.",
|
||||
"prefs.theme_options_badge": "옵션",
|
||||
"prefs.theme_options_more": "더 보기 · {count}",
|
||||
"prefs.theme_options_newer": "이 테마에는 더 최신 버전의 SolidCraft가 필요합니다. 업그레이드 후 원본 테마를 다시 가져와 옵션 정의를 복구하세요.",
|
||||
"prefs.theme_options_reset": "초기화",
|
||||
"prefs.theme_options_unavailable": "이 기기에서는 사용할 수 없음",
|
||||
"prefs.timeline_half_hour": "−30분",
|
||||
@@ -2043,14 +1790,8 @@
|
||||
"prefs.under_cursor": "커서 아래",
|
||||
"prefs.units_precision": "단위 및 정밀도",
|
||||
"prefs.updates": "업데이트",
|
||||
"prefs.validation": "바디 검사",
|
||||
"prefs.validation.each_feature": "피처마다 한 번",
|
||||
"prefs.validation.each_step": "모든 단계 후",
|
||||
"prefs.validation.label": "커널 전체 검사",
|
||||
"prefs.validation.off": "끄기 (실험적)",
|
||||
"prefs.validation_note": "모든 단계 후: 스윕, 불리언, 블렌드마다 검사합니다.\n피처마다 한 번: 각 피처의 결과를 한 번 검사합니다.\n끄기 (실험적): 검사하지 않습니다.",
|
||||
"prefs.value_boxes_in_view": "핸들 옆에 값 표시",
|
||||
"prefs.value_boxes_in_view_tip": "돌출의 거리, 필렛의 반지름 등을 뷰 안의 상자로 표시합니다.",
|
||||
"prefs.value_boxes_in_view": "뷰에서 핸들 옆에 값 표시",
|
||||
"prefs.value_boxes_in_view_tip": "명령의 값(거리, 반경 등)을 모델 위 핸들 옆 상자에 표시합니다. 끄면 값은 명령 창에 입력하며, 선택 후 키보드 입력이 그 창으로 가고 모델을 가리는 것이 없습니다.",
|
||||
"prefs.view": "뷰 동작",
|
||||
"prefs.view_confirm_corner": "뷰 모서리에 확인 및 취소",
|
||||
"prefs.view_confirm_corner_note": "대화 상자나 스케치가 열려 있는 동안 뷰 오른쪽 위의 체크와 X 표시가 대화 상자의 확인과 취소와 같은 동작을 하거나 스케치를 마치고 취소합니다. 뷰 탐색기는 그 아래로 내려갑니다.",
|
||||
@@ -2080,17 +1821,6 @@
|
||||
"recovery.just_now": "방금",
|
||||
"recovery.min_ago": "{count}분 전",
|
||||
"recovery.never_saved": "저장된 적 없음",
|
||||
"refused.change": "{body}을(를) 변경하지 못했습니다.",
|
||||
"refused.cut": "{body}을(를) 자르지 못했습니다.",
|
||||
"refused.face_curved": "곡면 중 하나를 모델링에 사용할 수 없습니다.",
|
||||
"refused.face_flat": "평면 중 하나를 모델링에 사용할 수 없습니다.",
|
||||
"refused.face_freeform": "자유 곡면 중 하나를 모델링에 사용할 수 없습니다.",
|
||||
"refused.face_rounded": "둥근 면 중 하나를 모델링에 사용할 수 없습니다.",
|
||||
"refused.faces_apart": "면들이 충분히 맞물리지 않아 모델링에 사용할 수 없습니다.",
|
||||
"refused.intersect": "{body}과(와) 교차하지 못했습니다.",
|
||||
"refused.join": "{body}에 결합하지 못했습니다.",
|
||||
"refused.repair": "부품 복구",
|
||||
"refused.show_face": "면 보기",
|
||||
"render.add_ground": "Add fitted ground to setup",
|
||||
"render.all_shots_label": "모든 샷",
|
||||
"render.appearance_editor.absorption": "흡수",
|
||||
@@ -2120,7 +1850,7 @@
|
||||
"render.appearance_editor.replace": "이 바디의 저장된 광학 설정 교체",
|
||||
"render.appearance_editor.revalidate": "이 바디 다시 검증",
|
||||
"render.appearance_editor.save": "이 광학 설정 저장",
|
||||
"render.appearance_editor.save_reason": "유리를 저장하려면 현재 CPU 또는 GPU 바디에서 활성화하세요. 클리어 코트는 샷에 코트 저장을 사용하세요. 로컬 이미지 파일은 임시 설정으로 유지됩니다.",
|
||||
"render.appearance_editor.save_reason": "저장하려면 현재 GPU 바디에 물리 유리가 필요합니다. 코트와 로컬 이미지 맵은 임시로 유지됩니다.",
|
||||
"render.appearance_editor.saved": "이 샷에 저장됨",
|
||||
"render.appearance_editor.saved_state": "이 광학 설정은 이 샷과 함께 저장되어 있습니다.",
|
||||
"render.appearance_editor.scatter_reason": "내부 산란에는 닫힌 솔리드 경계가 필요합니다.",
|
||||
@@ -2168,7 +1898,7 @@
|
||||
"render.children.copy_request": "요청 JSON 복사",
|
||||
"render.children.coverage_error": "커버리지 오차",
|
||||
"render.children.cpu": "CPU 레이 트레이싱",
|
||||
"render.children.denoise": "이미지 노이즈 줄이기",
|
||||
"render.children.denoise": "완성 이미지의 노이즈 줄이기",
|
||||
"render.children.depth": "경로 깊이",
|
||||
"render.children.distortion": "왜곡",
|
||||
"render.children.edge_samples": "모서리 샘플",
|
||||
@@ -2389,7 +2119,7 @@
|
||||
"render.error_visible_solid": "여기서 미리 보려면 디자인에서 보이는 솔리드를 만드세요.",
|
||||
"render.exit_focus": "집중 보기 종료",
|
||||
"render.export_image_label": "이미지 내보내기",
|
||||
"render.exr_pending": "CPU 레이 트레이싱과 GPU 패스 트레이싱은 원본 선형 EXR을 보존할 수 있습니다. 압축, 다중 레이어 이미지 및 보조 렌더 패스는 아직 사용할 수 없습니다.",
|
||||
"render.exr_pending": "GPU는 원본 선형 EXR을 보존할 수 있습니다. 압축, 다중 레이어 이미지 및 보조 렌더 패스는 아직 사용할 수 없습니다.",
|
||||
"render.fit": "제품에 맞추기",
|
||||
"render.fixed_camera": "카메라",
|
||||
"render.fixed_world": "월드",
|
||||
@@ -2639,7 +2369,7 @@
|
||||
"render.output_exr_unavailable": "여기서는 EXR 출력을 설정할 수 없습니다. 선형 EXR 내보내기에는 선형 원본을 보관한 완료된 GPU 이미지가 필요합니다.",
|
||||
"render.output_fit": "맞춤",
|
||||
"render.output_format": "형식",
|
||||
"render.output_format_status": "PNG는 완료된 표시 이미지 그대로입니다. 선택적 CPU/GPU EXR은 표시 필터 전 선형 FLOAT32 방사 휘도를 보존합니다. 미리보기 캐시나 다른 작업으로 바꾸어 내보내지 않습니다.",
|
||||
"render.output_format_status": "PNG는 완료된 표시 이미지 그대로입니다. 선택적 GPU EXR은 표시 필터 전 선형 FLOAT32 방사 휘도를 보존합니다. 미리보기 캐시나 다른 작업으로 바꾸어 내보내지 않습니다.",
|
||||
"render.output_grading": "색 보정",
|
||||
"render.output_image": "이미지",
|
||||
"render.output_largest": "{width} × {height} 최대",
|
||||
@@ -2689,7 +2419,6 @@
|
||||
"render.quality_custom": "사용자 지정",
|
||||
"render.quality_draft": "초안",
|
||||
"render.quality_high": "높음",
|
||||
"render.quality_projection_help": "이 검토에서는 해상도, 샘플 예산, 경로 깊이, 시드 및 노이즈 제거만 변경합니다. 다른 설정은 유지되며 수렴은 보장되지 않습니다.",
|
||||
"render.radial_assign": "외관 지정",
|
||||
"render.radial_compare": "비교",
|
||||
"render.radial_fit": "제품 맞추기",
|
||||
@@ -2832,7 +2561,7 @@
|
||||
"render.stage_panels.prepare_plan": "생성 계획 준비",
|
||||
"render.stage_panels.projects": "투영",
|
||||
"render.stage_panels.quality_ack": "고정 예산과 생략 사항을 확인했습니다",
|
||||
"render.stage_panels.quality_omissions": "이 예산은 적응형 오차 목표, 독립적인 투과 한도, 러시안 룰렛 규칙 또는 선형 마스터 보존을 설정하지 않으며 수렴을 보장하지 않습니다.",
|
||||
"render.stage_panels.quality_omissions": "적응형 중지 및 오차 목표, 별도 투과 깊이, 러시안 룰렛 규칙, 원시 선형 출력, EXR 마스터 및 수렴 인증.",
|
||||
"render.stage_panels.reference": "참조",
|
||||
"render.stage_panels.references": "참조",
|
||||
"render.stage_panels.remove_optics": "광학 설정 제거",
|
||||
@@ -2876,7 +2605,7 @@
|
||||
"render.stage_panels.subtitle_environment": "원본 HDR 환경 · 전체 장면",
|
||||
"render.stage_panels.subtitle_maps": "제작된 공간 맵 · 임시 미리 보기",
|
||||
"render.stage_panels.subtitle_physical": "읽기 전용 계획 · 재질 생성 또는 할당 없음",
|
||||
"render.stage_panels.subtitle_quality": "GPU 샘플링 예산 · 표시된 설정만 변경",
|
||||
"render.stage_panels.subtitle_quality": "고정 GPU 예산 · 향후 요구 사항 생략",
|
||||
"render.stage_panels.subtitle_rig": "제작된 조명 리그 · 광원 및 수동 카드",
|
||||
"render.stage_panels.subtitle_saved_maps": "제작된 맵 · 이 샷에 저장",
|
||||
"render.stage_panels.subtitle_stage": "물리적 바닥 및 스테이지 · 제품에 맞춤",
|
||||
@@ -2893,7 +2622,6 @@
|
||||
"render.state_in_progress": "이미지 생성 중",
|
||||
"render.state_model": "모델 미리보기",
|
||||
"render.strip_label": "스트립",
|
||||
"render.studio_denoise_help": "프로그레시브 이미지와 완성 이미지에 경계를 보존하는 필터를 적용합니다. 미세한 디테일이 부드러워질 수 있습니다. 유리와 날카로운 정반사 표면은 필터를 거치지 않습니다.",
|
||||
"render.studio_tiles_require_gpu": "스튜디오에는 GPU 경로 추적이 필요합니다. 출력 편집기에서 GPU를 선택하세요.",
|
||||
"render.studios_environments": "스튜디오 및 환경",
|
||||
"render.tone_aces_fitted": "ACES 근사",
|
||||
@@ -2995,10 +2723,10 @@
|
||||
"select the sheet edge first": "판금 모서리를 먼저 선택하세요",
|
||||
"select two components": "구성요소 두 개를 선택하세요",
|
||||
"settings.display_quality_note": "세밀할수록 곡면과 모서리가 더 매끄럽게 표시되지만 메시 생성 시간이 늘어납니다. 뷰포트만 바뀌며 설계나 내보내기는 바뀌지 않습니다.",
|
||||
"settings.double_click_components": "구성요소를 두 번 클릭하여 활성화",
|
||||
"settings.double_click_components": "구성 요소를 두 번 클릭하여 활성화하거나 연결된 구성 요소를 제자리에서 편집",
|
||||
"settings.frame_rate_note": "뷰의 오른쪽 아래에 초당 프레임 수를 표시합니다.",
|
||||
"settings.insert_lists_open_designs": "디자인 삽입에서 열린 디자인을 먼저 표시",
|
||||
"settings.measure_axis_distances": "측정에서 축 거리 표시",
|
||||
"settings.insert_lists_open_designs": "설계 삽입에서 다른 탭에 열린 설계를 먼저 표시",
|
||||
"settings.measure_axis_distances": "X/Y/Z 거리 표시",
|
||||
"settings.measure_axis_distances_help": "측정 대화상자가 열려 있는 동안 전체 거리와 함께 월드 축 방향 치수와 거리를 표시합니다.",
|
||||
"settings.msaa_applies_on_restart": "안티앨리어싱은 다음에 SolidCraft를 시작할 때 적용됩니다",
|
||||
"settings.msaa_reset": "{setting}: {requested}×는 이 그래픽 장치에서 지원되지 않아 {reset}×로 변경했습니다(지원: {supported}).",
|
||||
@@ -3008,7 +2736,6 @@
|
||||
"settings.reset_intro": "다음 설정은 이 컴퓨터에서 작동하지 않아 SolidCraft를 시작할 수 없게 하므로 변경되었습니다:",
|
||||
"settings.reset_title": "설정이 변경되었습니다",
|
||||
"settings.supersampling_note": "뷰를 더 높은 해상도로 그린 뒤 축소합니다. 4K 미만 화면에서 모서리, 선, 그리드가 더 매끄러워집니다. GPU 시간이 듭니다: 1.5×에서 약 2.25배, 2×에서 4배의 픽셀.",
|
||||
"settings.update_changed_links": "탭으로 돌아올 때 변경된 링크 업데이트",
|
||||
"sheet.flange_type_base": "베이스",
|
||||
"sheet.flange_type_contour": "윤곽",
|
||||
"sheet.flange_type_edge": "모서리",
|
||||
@@ -3091,18 +2818,7 @@
|
||||
"the section's profiles must be in one sketch": "단면의 프로파일은 하나의 스케치 안에 있어야 합니다",
|
||||
"the two origins are on the same component": "두 원점이 같은 구성요소에 있습니다",
|
||||
"thread.automatic_size": "자동 크기",
|
||||
"thread.chamfer_depth_needed": "이 형상에 필요",
|
||||
"thread.designation": "호칭",
|
||||
"thread.drill_range": "탭 제조사의 드릴은 {drill} mm입니다. 암나사의 규격 골지름은 {minor} mm입니다. {low}~{high} mm 드릴을 사용할 수 있습니다.",
|
||||
"thread.drill_range_custom": "드릴은 바깥지름에서 피치를 뺀 {drill} mm입니다. 암나사의 규격 골지름은 {minor} mm입니다. {low}~{high} mm 드릴을 사용할 수 있습니다.",
|
||||
"thread.drill_range_taper": "탭 제조사의 드릴은 {drill} mm입니다. 나사 끝에서의 규격 골지름은 {minor} mm입니다. {low}~{high} mm 드릴을 사용할 수 있습니다.",
|
||||
"thread.end_square": "직각",
|
||||
"thread.fade_short": "1.25 P (ISO 3508)",
|
||||
"thread.fade_standard": "2.5 P (ISO 3508)",
|
||||
"thread.form_bspp": "관용 평행나사 G (ISO 228-1)",
|
||||
"thread.form_bspt": "관용 테이퍼나사 R, Rc (ISO 7-1)",
|
||||
"thread.form_npt": "관용 테이퍼나사 NPT (ASME B1.20.1)",
|
||||
"thread.form_rp": "관용 평행 암나사 Rp (ISO 7-1)",
|
||||
"thread.iso_metric": "ISO 미터",
|
||||
"thread.select_size": "크기 선택",
|
||||
"timeline.feature_hint": "두 번 클릭하여 편집 · 드래그하여 순서 변경 · 마우스 오른쪽 버튼을 클릭하여 더 보기",
|
||||
@@ -3197,7 +2913,6 @@
|
||||
"ui.3d_mouse_section": "3D 마우스",
|
||||
"ui.3d_mouse_show_in_view": "3D 뷰에 3D 마우스 표시",
|
||||
"ui.3d_mouse_show_in_view_tip": "왼쪽 아래에 3D 마우스 그림을 띄워 움직임을 따라 보여 줍니다.",
|
||||
"ui.3d_mouse_sketch_orbit": "스케치 편집 중 뷰 회전",
|
||||
"ui.3d_mouse_skip": "내 장치는 이 동작을 못 해요",
|
||||
"ui.3d_mouse_skip_tip": "이 동작을 양방향 모두 건너뛰고 보정에서 뺍니다.",
|
||||
"ui.3d_mouse_speed": "3D 마우스 속도",
|
||||
@@ -3330,7 +3045,6 @@
|
||||
"ui.agent_chat_no_control": "에이전트 채팅 엔드포인트가 시작되지 않아 에이전트가 디자인에 접근할 수 없습니다.",
|
||||
"ui.agent_chat_no_images": "이 에이전트는 이미지를 받지 않습니다",
|
||||
"ui.agent_chat_no_picture": "클립보드에 이미지가 없습니다",
|
||||
"ui.agent_chat_node_too_old": "Node.js {version}은 이 에이전트에 사용하기에는 너무 오래된 버전입니다. Node.js 20.10 이상을 설치한 후 새로 고침을 누르세요.",
|
||||
"ui.agent_chat_none_found": "코딩 에이전트를 찾지 못했습니다. 다음 중 하나를 설치한 뒤 새로 고침을 누르세요:",
|
||||
"ui.agent_chat_none_found_short": "에이전트를 찾지 못함",
|
||||
"ui.agent_chat_not_installed": "설치되지 않음",
|
||||
@@ -3365,7 +3079,7 @@
|
||||
"ui.agent_chat_send_now_tip": "에이전트의 현재 단계를 중지하고 이것을 다음으로 보냅니다 (Ctrl+Enter)",
|
||||
"ui.agent_chat_setup_body": "SolidCraft는 이 컴퓨터에 설치된 코딩 에이전트와 통신합니다. 환경 설정 › AI에서 에이전트를 선택하세요.",
|
||||
"ui.agent_chat_setup_title": "어시스턴트 설정",
|
||||
"ui.agent_chat_side": "채팅 도킹",
|
||||
"ui.agent_chat_side": "에이전트 채팅 위치",
|
||||
"ui.agent_chat_signed_in": "로그인됨",
|
||||
"ui.agent_chat_state_auth": "로그인 필요",
|
||||
"ui.agent_chat_state_busy": "작업 중…",
|
||||
@@ -3382,7 +3096,7 @@
|
||||
"ui.agent_chat_undone": "(취소됨)",
|
||||
"ui.agent_chat_view_attached": "뷰 첨부됨",
|
||||
"ui.agent_chat_waiting": "확인 대기 중",
|
||||
"ui.agent_cursor": "에이전트 포인터",
|
||||
"ui.agent_cursor": "에이전트 커서",
|
||||
"ui.agent_cursor_speed": "에이전트 커서 속도",
|
||||
"ui.agent_off_thread": "에이전트가 명령을 실행하는 동안 창 반응성 유지",
|
||||
"ui.agent_off_thread_note": "에이전트의 느린 명령(AI 패널 또는 MCP 클라이언트에서 실행)은 창과 별도로 계산됩니다. 뷰를 계속 회전하며 살펴볼 수 있고, 뷰의 칩에서 하나씩 중지할 수 있습니다. 끄면 각 명령이 창의 스레드에서 실행되며, 창은 명령이 끝날 때까지 기다립니다.",
|
||||
@@ -3425,14 +3139,14 @@
|
||||
"ui.ai_key_needs_https": "키는 HTTPS를 통해서만, 또는 이 컴퓨터로만 전송됩니다.",
|
||||
"ui.ai_key_note": "SolidCraft 설정이 아닌 시스템 키체인에 저장됩니다. 로컬 서버라면 비워 두세요.",
|
||||
"ui.ai_keychain_error": "시스템 키체인이 거부했습니다: {error}",
|
||||
"ui.ai_mode_all": "모두 실행",
|
||||
"ui.ai_mode_all": "모두 허용",
|
||||
"ui.ai_mode_all_note": "묻지 않고 실행합니다. 충돌 보고서는 계속 제외되고 폴더 제한도 그대로 적용됩니다.",
|
||||
"ui.ai_mode_all_warning": "에이전트가 묻지 않고 디자인을 저장, 덮어쓰기, 내보내기, 교체할 수 있습니다. 신뢰하는 에이전트와 저장된 디자인에서만 사용하세요.",
|
||||
"ui.ai_mode_ask": "먼저 묻기",
|
||||
"ui.ai_mode_ask": "매번 묻기",
|
||||
"ui.ai_mode_ask_note": "디자인의 모든 변경과 파일 작업이 사용자의 허용을 기다립니다.",
|
||||
"ui.ai_mode_edits": "편집 허용",
|
||||
"ui.ai_mode_edits": "모델 편집 자동 허용",
|
||||
"ui.ai_mode_edits_note": "모델링 명령은 묻지 않고 실행되며, 저장·열기·내보내기 등 파일 작업이나 파괴적인 작업은 계속 묻습니다.",
|
||||
"ui.ai_mode_global": "위와 동일",
|
||||
"ui.ai_mode_global": "위 설정 따름",
|
||||
"ui.ai_model": "모델",
|
||||
"ui.ai_model_hint": "qwen3:14b",
|
||||
"ui.ai_modes_scope": "이 설정은 SolidCraft 도구에 적용됩니다. 에이전트 자체 도구(셸, 파일 편집)는 에이전트에 모드가 있을 때 채팅 패널 머리글에서 고르는 에이전트 자체 모드를 따릅니다.",
|
||||
@@ -3451,7 +3165,7 @@
|
||||
"ui.ai_save": "저장",
|
||||
"ui.ai_settings": "AI 설정…",
|
||||
"ui.ai_show_features": "AI 기능 표시",
|
||||
"ui.ai_show_features_note": "끄면 모든 곳에서 어시스턴트를 숨기고 아무것도 시작하지 않습니다. 아래 항목이 모두 흐려집니다.\n\n에이전트 채팅: 이미 사용 중인 코딩 에이전트나 선택한 모델 엔드포인트가 열린 디자인을 다루는 패널입니다. 끄면 AI 버튼, 단축키, 메뉴 항목, 패널이 없고 아무것도 찾거나 시작하지 않습니다.",
|
||||
"ui.ai_show_features_note": "에이전트 채팅: 이미 사용 중인 코딩 에이전트나 선택한 모델 엔드포인트가 열린 디자인을 다루는 패널입니다. 끄면 AI 버튼, 단축키, 메뉴 항목, 패널이 없고 아무것도 찾거나 시작하지 않습니다.",
|
||||
"ui.ai_switch_tip": "이 대화의 에이전트 변경",
|
||||
"ui.ai_test": "테스트",
|
||||
"ui.ai_test_acp_ok": "응답: {agent}",
|
||||
@@ -3465,7 +3179,7 @@
|
||||
"ui.ai_tool_running": "실행 중",
|
||||
"ui.ai_tool_unfold": "클릭하면 결과를 표시",
|
||||
"ui.ai_tool_waiting": "대기 중",
|
||||
"ui.ai_toolbar_button": "애플리케이션 바의 어시스턴트 버튼",
|
||||
"ui.ai_toolbar_button": "도구 모음에 버튼 표시",
|
||||
"ui.ai_toolbar_button_note": "도구 모음 오른쪽 끝의 AI 버튼. Ctrl+Shift+A와 보기 메뉴로도 패널을 열 수 있습니다.",
|
||||
"ui.ai_undo_tip": "이 메시지에서 에이전트가 바꾼 모든 것을 한 번에 실행 취소",
|
||||
"ui.ai_url_hint": "https://example.com/v1",
|
||||
@@ -3537,10 +3251,6 @@
|
||||
"ui.colour_vision_sample": "예시",
|
||||
"ui.colour_vision_tritan": "청색각이상 (Tritan)",
|
||||
"ui.colour_vision_tritan_tip": "파란색 감각이 약하거나 없는 경우",
|
||||
"ui.comb_length": "빗 길이",
|
||||
"ui.comb_length_tip": "빗살의 길이: 자동 길이의 배수입니다. 자동 길이에서는 가장 긴 빗살이 표시된 곡선 크기의 5분의 1이 됩니다.",
|
||||
"ui.comb_teeth": "빗살 수",
|
||||
"ui.comb_teeth_tip": "각 빗의 빗살 수이며, 곡선을 따라 같은 간격으로 놓입니다. 빗살이 많을수록 곡률이 어떻게 바뀌는지 더 자세히 보입니다.",
|
||||
"ui.combine": "결합",
|
||||
"ui.coming_later": "추후 지원 예정",
|
||||
"ui.command_done": "{command} 완료",
|
||||
@@ -3565,24 +3275,11 @@
|
||||
"ui.construction.ribbon_label": "보조선",
|
||||
"ui.construction.side_hint": "보조 직선: 선이 뻗어 나갈 쪽 클릭; 마우스 오른쪽 버튼으로 다시 시작",
|
||||
"ui.context_menu": "컨텍스트 메뉴",
|
||||
"ui.continuity_crease": "꺾임",
|
||||
"ui.continuity_curvature": "곡률",
|
||||
"ui.continuity_curvature_change": "곡률 변화",
|
||||
"ui.continuity_curvature_change_tip": "곡률 변화(G3): 이음매에서 곡률의 변화도 부드럽습니다.",
|
||||
"ui.continuity_curvature_tip": "곡률 연속(G2): 반사가 꺾임 없이 이어집니다.",
|
||||
"ui.continuity_g0": "G0 꺾임",
|
||||
"ui.continuity_g1": "G1 접선",
|
||||
"ui.continuity_g2": "G2 곡률",
|
||||
"ui.continuity_gap": "틈",
|
||||
"ui.continuity_position": "위치",
|
||||
"ui.continuity_position_tip": "위치(G0): 면이 만나지만, 만나는 곳에 꺾임이 있습니다.",
|
||||
"ui.continuity_smooth_curvature_change": "부드러운 곡률 변화",
|
||||
"ui.continuity_smooth_curvature_change_tip": "부드러운 곡률 변화(G4): 이음매에서 곡률이 변하는 방식까지 부드럽게 이어집니다.",
|
||||
"ui.continuity_tangent": "접선",
|
||||
"ui.continuity_tangent_tip": "접선(G1): 꺾임은 없지만, 이음매에서 곡률이 급변합니다.",
|
||||
"ui.copy": "복사",
|
||||
"ui.copy_details": "세부 정보 복사",
|
||||
"ui.copy_details_tip": "찾아보거나 보고할 수 있도록 전체 메시지를 복사합니다.",
|
||||
"ui.corner": "코너",
|
||||
"ui.corners": "코너",
|
||||
"ui.counterbore": "카운터보어",
|
||||
@@ -3603,7 +3300,6 @@
|
||||
"ui.default_modelling_orientation": "기본 모델링 방향",
|
||||
"ui.delete": "삭제",
|
||||
"ui.delete_select_first": "먼저 삭제할 항목을 선택하세요: 피처, 바디, 컴포넌트 또는 스케치 형상",
|
||||
"ui.dependents_select_first": "먼저 종속 항목을 표시할 대상을 선택하세요",
|
||||
"ui.depth": "깊이",
|
||||
"ui.design": "디자인",
|
||||
"ui.design_properties.author": "작성자",
|
||||
@@ -3698,7 +3394,6 @@
|
||||
"ui.edit_sketch_empty": "편집할 스케치가 없습니다",
|
||||
"ui.ellipse": "타원",
|
||||
"ui.emboss_wrap_note": "지원되는 원통에 접하는 직교 선 프로파일은 자동으로 감깁니다. 대상을 고르려면 벽을 선택하세요.",
|
||||
"ui.end": "끝",
|
||||
"ui.end_radius": "끝 반지름",
|
||||
"ui.end_scale": "끝 배율",
|
||||
"ui.entities": "요소",
|
||||
@@ -3722,45 +3417,12 @@
|
||||
"ui.extend": "연장",
|
||||
"ui.extent_type": "범위 유형",
|
||||
"ui.extrude": "돌출",
|
||||
"ui.extrude_auto_to_object_tip": "면, 꼭짓점 또는 평행 평면을 선택하면 범위가 객체까지로 전환됩니다.",
|
||||
"ui.factor": "배율",
|
||||
"ui.fade": "페이드",
|
||||
"ui.fade_length": "페이드 길이",
|
||||
"ui.feature_history": "피처 기록",
|
||||
"ui.feature_options": "피처 옵션",
|
||||
"ui.features": "피처",
|
||||
"ui.file": "파일",
|
||||
"ui.fillet": "필렛",
|
||||
"ui.fillet_corner_ball": "볼",
|
||||
"ui.fillet_corner_ball_tip": "굴러가는 볼의 구면 일부로, 각 라운드에 접합니다(G1).",
|
||||
"ui.fillet_corner_curvature_tip": "곡률(G2): 부드러운 패치. 코너를 각 모서리를 따라 셋백만큼(입력하지 않으면 반지름의 {factor}배) 물려 패치로 채우므로, 반사가 꺾임 없이 지나갑니다.",
|
||||
"ui.fillet_corner_needs_constant": "부드러운 패치 코너(G2)에는 일정한 반지름과 정확한 반지름 맞춤이 필요합니다.",
|
||||
"ui.fillet_corner_patch": "부드러운 패치",
|
||||
"ui.fillet_corner_patch_tip": "곡률 연속(G2): 코너를 각 모서리를 따라 물려 패치로 채우므로, 반사가 꺾임 없이 지나갑니다.",
|
||||
"ui.fillet_corner_tangent_tip": "접선(G1): 굴러가는 볼 코너. 굴러가는 볼의 구면 일부로, 각 라운드에 접합니다.",
|
||||
"ui.fillet_error_corner": "여기에서는 부드러운 코너를 만들 수 없었습니다. 다른 셋백이나 접선 코너를 시도해 보세요.",
|
||||
"ui.fillet_error_failed": "이 모서리들에 이 필렛을 만들 수 없었습니다. 다른 반지름을 시도하거나 한 번에 둥글게 하는 모서리 수를 줄여 보세요.",
|
||||
"ui.fillet_error_setback_short": "이 코너의 패치에 비해 셋백이 너무 짧습니다. {least}부터 만들 수 있습니다. 더 긴 셋백을 시도해 보세요.",
|
||||
"ui.fillet_error_too_large": "이 모서리 옆의 면에 비해 반지름이 너무 큽니다. 더 작은 반지름을 시도하거나 '면 경계'를 '최대 반지름'으로 설정하세요.",
|
||||
"ui.fillet_fit": "면 경계",
|
||||
"ui.fillet_fit_exact": "정확한 반지름",
|
||||
"ui.fillet_fit_needs_constant": "반지름 맞춤에는 일정한 반지름이 필요합니다.",
|
||||
"ui.fillet_fit_reduce": "최대 반지름",
|
||||
"ui.fillet_fit_taper": "좁힌 반지름",
|
||||
"ui.fillet_fit_tip": "반지름이 모서리 옆 면이 허용하는 것보다 클 때.\n정확한 반지름: 지정한 반지름 그대로, 또는 둥글게 하지 않고 이유를 알리는 메시지.\n최대 반지름: 모든 모서리를 들어가는 가장 큰 반지름으로 둥글게 합니다.\n좁힌 반지름: 반지름을 유지하고 면이 모자란 곳에서만 라운드를 좁힙니다.",
|
||||
"ui.fillet_options_tip": "접선 체인: 선택한 모서리에서 부드럽게 이어지는 모서리도 둥글게 합니다.\n미리 보기: 편집하는 동안 결과를 뷰에 표시합니다.",
|
||||
"ui.fillet_setback": "셋백",
|
||||
"ui.fillet_setback_hint": "{factor} × 반지름",
|
||||
"ui.fillet_setback_least": "{least}보다 크게",
|
||||
"ui.fillet_setback_tip": "부드러운 코너 패치가 시작되는 위치: 코너에서 각 모서리를 따른 거리. 기본값은 반지름의 {factor}배. 최소 셋백(셋백 없이 라운드가 만나는 곳, 면이 서로 직각이면 반지름)보다 커야 합니다.",
|
||||
"ui.fillet_smooth_radius": "반지름 부드럽게 변화",
|
||||
"ui.fillet_turn": "꺾임",
|
||||
"ui.fillet_turn_mitre": "마이터",
|
||||
"ui.fillet_turn_swept": "회전",
|
||||
"ui.fillet_turn_tip": "두 라운드가 사이의 날카로운 코너 모서리를 돌아가는 곳(보스 밑동의 라운드가 세로 모서리를 도는 곳 등).\n마이터: 라운드가 계속 이어져 마이터로 만납니다.\n회전: 라운드가 코너 모서리에서 멈추고 그 둘레를 돕니다. 코너는 둘 모두에 접하는 토러스입니다. 반지름이 같은 라운드가 곧고 볼록한 코너 모서리를 돌 때만.",
|
||||
"ui.fillet_type_choices_tip": "일정한 반지름: 모서리 전체에 하나의 반지름.\n현 길이: 면이 어떤 각도로 만나든 라운드의 폭이 같습니다.\n가변 반지름: 양 끝의 반지름과, 추가하면 모서리를 따라 더.\n곡률 연속: 평면과 만나는 곳에서 반사가 꺾이지 않는 단면(G2).\n스플라인: 모서리를 따라 반지름이 설정한 제어 반지름을 따릅니다.",
|
||||
"ui.fillet_type_curvature": "곡률 연속",
|
||||
"ui.fillet_type_tip": "모서리를 가로지르는 단면. 곡률 연속: 평면 사이의 곡률 연속(G2) 단면으로, 반사가 면에서 라운드로 꺾임 없이 이어집니다.",
|
||||
"ui.filter_commands_or_keys": "명령 또는 키 필터",
|
||||
"ui.findInTimeline": "타임라인에서 찾기",
|
||||
"ui.findInWindow": "창에서 찾기",
|
||||
@@ -3801,7 +3463,6 @@
|
||||
"ui.heading_construct": "구성",
|
||||
"ui.heading_create": "만들기",
|
||||
"ui.heading_create_sketch": "스케치 만들기",
|
||||
"ui.heading_custom_view_navigator": "사용자 지정 뷰 탐색기",
|
||||
"ui.heading_cylinder": "원통",
|
||||
"ui.heading_design": "디자인",
|
||||
"ui.heading_draft": "구배",
|
||||
@@ -3868,7 +3529,6 @@
|
||||
"ui.hideOrigin": "원점 숨기기",
|
||||
"ui.hidePlanes": "평면 숨기기",
|
||||
"ui.hideProfile": "윤곽 숨기기",
|
||||
"ui.hide_origin_by_default": "새 디자인에서 원점 폴더 숨기기",
|
||||
"ui.history_back": "한 단계 뒤로",
|
||||
"ui.history_below_browser": "브라우저 아래",
|
||||
"ui.history_edge_hint": "가장자리를 더블클릭",
|
||||
@@ -3958,7 +3618,6 @@
|
||||
"ui.move": "이동",
|
||||
"ui.multiple": "다중",
|
||||
"ui.named_views": "명명된 뷰",
|
||||
"ui.nav_bridge_tip": "장치의 내비게이션 브리지에 연결합니다. 요청하기 전에는 꺼져 있습니다. 3D 입력 장치 자체 프로그램(예: 직접 만든 3D 마우스 드라이버)이 뷰를 움직이며 마우스 포인터는 그대로 사용할 수 있습니다. 연결은 이 컴퓨터의 이 포트만 사용합니다.",
|
||||
"ui.navigation": "탐색",
|
||||
"ui.needs_design": "{command}: 먼저 디자인을 열어 주세요",
|
||||
"ui.newSketchGroup": "새 스케치 그룹",
|
||||
@@ -3996,7 +3655,6 @@
|
||||
"ui.part_size_needs_a_part": "크기를 바꿀 삽입된 표준 부품(나사, 너트, 와셔 또는 핀)을 선택하십시오. 바디를 키우거나 줄이려면 축척을 사용하십시오.",
|
||||
"ui.part_size_shared": "부품을 다시 빌드하며 추가된 피처는 모두 대체됩니다. 이 구성요소의 모든 인스턴스가 업데이트됩니다.",
|
||||
"ui.paste": "붙여넣기",
|
||||
"ui.patch_continuity_corner_tip": "패치는 각 변을 따라 주변 면과 선택한 연속성으로 이어집니다. 주변 면끼리 서로 다른 곡률로 만나는 모서리에서는 그곳에서 접선(G1)으로 유지됩니다.",
|
||||
"ui.path": "경로",
|
||||
"ui.pattern_object_type_tip": "바디: 뷰나 브라우저에서 선택하는 솔리드 형상이며, 각각 통째로 복사됩니다.\n피처: 돌출이나 구멍 같은 기록의 단계이며, 기록에서 또는 그 단계가 만든 면에서 선택합니다. 그 단계가 반복됩니다.\n스케치는 둘 다 아닙니다. 곡선은 스케치 안의 패턴이나 대칭 복사로 반복하거나, 먼저 돌출하세요.",
|
||||
"ui.pattern_on_path": "경로 패턴",
|
||||
@@ -4007,7 +3665,6 @@
|
||||
"ui.pipe": "파이프",
|
||||
"ui.pitch": "피치",
|
||||
"ui.placement": "배치",
|
||||
"ui.placement_handles_tip": "모델 위로 피처를 드래그하는 마커와 회전하는 링입니다.",
|
||||
"ui.plane": "평면",
|
||||
"ui.plane_at_angle": "각도 평면",
|
||||
"ui.plugins": "플러그인",
|
||||
@@ -4172,8 +3829,6 @@
|
||||
"ui.start": "시작",
|
||||
"ui.start_over": "처음부터 다시",
|
||||
"ui.start_page": "시작 페이지",
|
||||
"ui.step_values": "화살표와 휠로 값 변경",
|
||||
"ui.step_values_tip": "값 필드에서 위/아래 화살표와 휠로 숫자를 변경합니다.",
|
||||
"ui.stitch_tolerance_hint": "엄격",
|
||||
"ui.stretch": "늘이기",
|
||||
"ui.supersampling": "슈퍼샘플링 (SSAA)",
|
||||
@@ -4294,7 +3949,7 @@
|
||||
"update.check_unavailable": "이 설치에서는 업데이트를 확인할 수 없습니다.",
|
||||
"update.checking": "확인 중…",
|
||||
"update.copy": "복사",
|
||||
"update.dmg_opened": "디스크 이미지를 확인하고 열었습니다: SolidCraft를 종료하고, 새 SolidCraft를 응용 프로그램 폴더로 끌어 기존 버전을 바꾼 뒤 SolidCraft를 다시 여세요.",
|
||||
"update.dmg_opened": "디스크 이미지를 확인하고 열었습니다: SolidCraft를 응용 프로그램 폴더로 끌어 기존 버전을 바꾼 뒤 SolidCraft를 다시 시작하세요.",
|
||||
"update.download": "다운로드",
|
||||
"update.download_open": "다운로드 후 열기",
|
||||
"update.downloading": "다운로드 및 확인 중… {done} / {total} MB",
|
||||
@@ -4329,13 +3984,5 @@
|
||||
"values must be finite numbers": "값은 유한한 숫자여야 합니다",
|
||||
"values must be numbers or expressions": "값은 숫자 또는 식이어야 합니다",
|
||||
"view.render_inspector": "렌더 검사기",
|
||||
"view_gizmo.arrows": "회전 화살표",
|
||||
"view_gizmo.home": "홈 버튼",
|
||||
"view_gizmo.no_such_body": "이 디자인에 그런 이름의 바디가 없습니다.",
|
||||
"view_gizmo.note": "뷰에서 바디를 선택하십시오. 바디는 환경설정과 설정 프로필에 저장되어 모든 디자인에 표시됩니다. 면을 클릭하면 그 면을 정면으로 봅니다.",
|
||||
"view_gizmo.texture": "텍스처",
|
||||
"view_gizmo.themed": "테마",
|
||||
"view_gizmo.too_detailed": "뷰 탐색기에 너무 세밀합니다 (삼각형 10,000개 초과)",
|
||||
"view_gizmo.too_thin": "뷰 탐색기에 너무 가늘고 깁니다: 가장 얇은 변이 가장 긴 변의 5분의 1 이상이어야 합니다",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ 선택 · Enter / Tab 완성 · Esc 닫기"
|
||||
}
|
||||
|
||||
@@ -477,7 +477,6 @@
|
||||
"Rectangular": "Segi empat tepat",
|
||||
"Rectangular Tube": "Tiub Segi Empat Tepat",
|
||||
"Reference face": "Muka rujukan",
|
||||
"Reference plane": "Satah rujukan",
|
||||
"Relief Depth": "Kedalaman Pelepasan",
|
||||
"Relief Width": "Lebar Pelepasan",
|
||||
"Remove Tools": "Buang Alat",
|
||||
@@ -557,7 +556,6 @@
|
||||
"Section": "Keratan",
|
||||
"Section (optional)": "Keratan (pilihan)",
|
||||
"Section position": "Kedudukan keratan",
|
||||
"Sections": "Keratan",
|
||||
"Sections (one sketch each)": "Keratan (satu lakaran setiap satu)",
|
||||
"Select Other": "Pilih Yang Lain",
|
||||
"Select Other: hold the left button still on the model (or right-click › Select Other…) to list everything under the cursor; click again on the same spot to select the next thing there. Double-click the middle button to go Home (Preferences › Navigation).": "Pilih Lain: tahan butang kiri pada model tanpa bergerak (atau klik kanan › Pilih Lain…) untuk senarai objek di bawah kursor. Klik tempat sama untuk objek seterusnya. Klik dua kali butang tengah untuk ke Utama (Tetapan › Navigasi).",
|
||||
@@ -861,7 +859,6 @@
|
||||
"cmd.component_extract": "Ekstrak ke Fail",
|
||||
"cmd.component_finish_edit": "Selesai Sunting",
|
||||
"cmd.component_from_bodies": "Cipta Komponen daripada Jasad",
|
||||
"cmd.component_hide_history": "Sembunyikan Sejarah Komponen",
|
||||
"cmd.component_insert_design": "Sisip Reka Bentuk",
|
||||
"cmd.component_links": "Senarai Pautan",
|
||||
"cmd.component_list": "Senarai Komponen",
|
||||
@@ -878,7 +875,6 @@
|
||||
"cmd.component_rename": "Tukar Nama Komponen",
|
||||
"cmd.component_replace_with_link": "Gantikan dengan Pautan",
|
||||
"cmd.component_revert_position": "Kembalikan Kedudukan",
|
||||
"cmd.component_show_history": "Papar Sejarah Komponen",
|
||||
"cmd.component_unlock": "Buka Kunci Kandungan",
|
||||
"cmd.component_update_links": "Kemas Kini Pautan",
|
||||
"cmd.config_activate": "Aktifkan Konfigurasi",
|
||||
@@ -889,7 +885,6 @@
|
||||
"cmd.config_rule": "Peraturan Konfigurasi",
|
||||
"cmd.config_rules": "Peraturan Konfigurasi",
|
||||
"cmd.config_table": "Paparkan Jadual Konfigurasi",
|
||||
"cmd.construct.plane.axis_point": "Satah Melalui Paksi dan Titik",
|
||||
"cmd.construct_axis_edge": "Paksi Melalui Tepi",
|
||||
"cmd.construct_axis_normal_to_face": "Paksi Serenjang dengan Muka",
|
||||
"cmd.construct_axis_through_cylinder": "Paksi Melalui Silinder/Kon/Torus",
|
||||
@@ -938,11 +933,6 @@
|
||||
"cmd.explode_show": "Papar Pandangan Terlerai",
|
||||
"cmd.expr_evaluate": "Nilaikan Ungkapan",
|
||||
"cmd.face_delete": "Padam Muka",
|
||||
"cmd.file.export_3mf": "Eksport 3MF",
|
||||
"cmd.file.export_obj": "Eksport OBJ",
|
||||
"cmd.file.export_step": "Eksport STEP",
|
||||
"cmd.file.export_stl": "Eksport STL",
|
||||
"cmd.file.export_urdf": "Eksport URDF",
|
||||
"cmd.file_export": "Eksport",
|
||||
"cmd.file_insert_mesh": "Sisip Mesh",
|
||||
"cmd.file_insert_step": "Sisip STEP",
|
||||
@@ -957,8 +947,9 @@
|
||||
"cmd.inspect_accessibility": "Analisis Kebolehcapaian",
|
||||
"cmd.inspect_center_of_mass": "Pusat Jisim",
|
||||
"cmd.inspect_clearance": "Kelegaan",
|
||||
"cmd.inspect_colored_components": "Aktifkan Warna Komponen",
|
||||
"cmd.inspect_colored_components_disable": "Nyahaktifkan Warna Komponen",
|
||||
"cmd.inspect_compare": "Bandingkan Jasad",
|
||||
"cmd.inspect_compare_picture": "Banding dengan Imej",
|
||||
"cmd.inspect_continuity": "Analisis Keselanjaran",
|
||||
"cmd.inspect_curvature_comb": "Analisis Sisir Kelengkungan",
|
||||
"cmd.inspect_curvature_map": "Analisis Peta Kelengkungan",
|
||||
@@ -1049,9 +1040,6 @@
|
||||
"cmd.pointcloud_edit": "Sunting Awan Titik",
|
||||
"cmd.pointcloud_insert": "Sisip Awan Titik",
|
||||
"cmd.pointcloud_list": "Awan Titik",
|
||||
"cmd.render.compare.divider": "Alih Pembahagi Perbandingan Render",
|
||||
"cmd.render.result.discard": "Buang Hasil Render",
|
||||
"cmd.render.result.select": "Pilih Hasil Render",
|
||||
"cmd.render_aging_describe": "Penuaan Permukaan Berlapis",
|
||||
"cmd.render_backends": "Enjin Render",
|
||||
"cmd.render_batch": "Render Semua Syot",
|
||||
@@ -1266,7 +1254,6 @@
|
||||
"cmd.timeline_rollback": "Alih Penanda Garis Masa",
|
||||
"cmd.timeline_stop": "Hentikan Main Sejarah",
|
||||
"cmd.timeline_suppress": "Nyahaktifkan Ciri",
|
||||
"cmd.ui.assistant": "Togol Pembantu",
|
||||
"cmd.view.render_browser": "Pelayar Render",
|
||||
"cmd.view.render_dock": "Alih Dok Render",
|
||||
"cmd.view.render_focus": "Pandangan Fokus Render",
|
||||
@@ -1301,11 +1288,9 @@
|
||||
"cmd.view_tool": "Alat navigasi",
|
||||
"cmd.view_top": "Pandangan Atas",
|
||||
"cmd.view_visual_style": "Gaya visual",
|
||||
"color.blue": "Biru",
|
||||
"color.copy": "Klik untuk menyalin nilai warna",
|
||||
"color.edit": "Klik untuk menyunting warna",
|
||||
"color.gamma": "Memaparkan nilai warna dalam ruang gamma 0-255",
|
||||
"color.green": "Hijau",
|
||||
"color.hex": "Heks",
|
||||
"color.hex_help": "Masukkan #rrggbb atau #rrggbbaa; enam digit mengekalkan kelegapan",
|
||||
"color.hex_hint": "#rrggbb",
|
||||
@@ -1313,7 +1298,6 @@
|
||||
"color.linear": "Memaparkan nilai warna dalam ruang linear 0-1",
|
||||
"color.recent": "Warna terkini",
|
||||
"color.recent_help": "Warna yang anda pilih dalam mana-mana medan warna dipaparkan di sini",
|
||||
"color.red": "Merah",
|
||||
"color.saturation": "Ketepuan (peratus)",
|
||||
"color.saturation_value": "Ketepuan dan nilai",
|
||||
"color.selected": "Warna dipilih",
|
||||
@@ -1321,8 +1305,6 @@
|
||||
"color.step_up": "Naikkan",
|
||||
"color.value": "Nilai, iaitu kecerahan (peratus)",
|
||||
"config.column_filter_empty": "Tiada lajur yang sepadan",
|
||||
"config.rule_parameter_columns": "Hanya parameter yang mempunyai lajur dalam jadual disenaraikan. Tambah satu dengan Tambah Lajur (+) untuk menggunakannya di sini.",
|
||||
"config.rule_suppression_columns": "Hanya ciri yang mempunyai lajur Nyahaktifkan dalam jadual disenaraikan. Tambah satu dengan Tambah Lajur (+) untuk mengawalnya di sini.",
|
||||
"contact.components_count": "{count} komponen",
|
||||
"contact.enabled_label": "diaktifkan",
|
||||
"coupling.gear_1": "Gear 1",
|
||||
@@ -1371,11 +1353,7 @@
|
||||
"dialog.pattern_sketch_not_object": "Pilih jasad atau ciri: lakaran bukan kedua-duanya. Ulang lengkungnya dengan Corak atau Cermin milik lakaran itu sendiri, atau semperit dahulu.",
|
||||
"dialog.select": "↖ Pilih",
|
||||
"dialog.selected_count": "↖ {count} dipilih",
|
||||
"dialog.selection_constraint": "Kekangan {id}",
|
||||
"dialog.selection_external_curve": "{sketch} · lengkung {id}",
|
||||
"dialog.selection_guidance": "Pilih input untuk diubah. Klik objek dipilih untuk nyahpilih; Ctrl/Cmd+klik juga menogol pilihan. Semak pratonton, kemudian tekan OK.",
|
||||
"dialog.selection_midpoint": "Titik tengah · {id}",
|
||||
"dialog.selection_profile": "{sketch} · profil {number}",
|
||||
"dialog.selection_step": "{step} daripada {total}: Pilih {input}",
|
||||
"dialog.step": "Langkah",
|
||||
"document.changed_on_disk": "{name} berubah pada cakera",
|
||||
@@ -1458,7 +1436,6 @@
|
||||
"help.construct.axis.two_points": "Mencipta paksi binaan melalui dua titik, bucu atau titik lakaran. Gunakan untuk paksi yang menghubungkan dua kedudukan yang diketahui.",
|
||||
"help.construct.plane.along_path": "Mencipta satah binaan yang serenjang dengan tepi atau lengkung, pada satu kedudukan sepanjangnya. Gunakan untuk melakar profil bagi Sapu atau keratan sesuatu laluan.",
|
||||
"help.construct.plane.at_angle": "Mencipta satah binaan yang dipusingkan pada satu sudut daripada satah atau muka, pada paksi atau tepi. Gunakan untuk lakaran condong, seperti braket bersudut.",
|
||||
"help.construct.plane.axis_point": "Mencipta satah binaan yang mengandungi satu paksi dan satu titik di luar paksi itu. Pilih titik asal atau paksi binaan, tepi lurus atau garis lakaran, kemudian bucu, titik lakaran atau titik binaan. Titik pada paksi tidak dapat menentukan satu satah yang unik.",
|
||||
"help.construct.plane.midplane": "Mencipta satah binaan di tengah-tengah antara dua satah atau muka rata, atau yang membahagi dua kedua-duanya di tempat ia bertemu pada satu sudut. Gunakan sebagai satah simetri untuk pencerminan.",
|
||||
"help.construct.plane.offset": "Mencipta satah binaan yang selari dengan satah atau muka rata, pada satu jarak atau melalui satu titik. Gunakan untuk melakar pada ketinggian tertentu di atas sesuatu muka.",
|
||||
"help.construct.plane.perpendicular": "Mencipta satah binaan yang memuatkan garis atau tepi dan berdiri serenjang dengan satah atau muka. Gunakan untuk melakar secara tegak pada model sepanjang tepi.",
|
||||
@@ -1835,8 +1812,6 @@
|
||||
"joint_move.blocked_hint": "Semak jenis sendi (seperti Tegar), status tetap, had sendi dan nilai yang dipacu parameter.",
|
||||
"joint_move.blocked_title": "Tidak dapat mengalihkan bahagian ini",
|
||||
"joint_move.coarse": "Langkah putaran dekat paksi (°)",
|
||||
"joint_move.conflict": "Satu sendi bagi bahagian ini bercanggah dengan yang lain: {joint}.",
|
||||
"joint_move.conflict_hint": "Sunting, nyahaktifkan atau padam sendi yang bercanggah supaya sendi lain dapat meletakkan bahagian itu.",
|
||||
"joint_move.distance": "Jarak dari mula",
|
||||
"joint_move.done": "Selesai",
|
||||
"joint_move.fine": "Langkah putaran jauh dari paksi (°)",
|
||||
@@ -1936,8 +1911,6 @@
|
||||
"motion.track_name": "Trek {number}",
|
||||
"nav.alias.help": "Shift+Alt+seret kiri mengorbit, Shift+Alt+seret tengah menyorot, Shift+Alt+seret kanan menzum; seret tengah juga menyorot dan seret kanan mengorbit",
|
||||
"nav.blender.help": "Seret tengah mengorbit, Shift+tengah menyorot, Ctrl+tengah menzum; seret kanan tidak menggerakkan kamera",
|
||||
"nav.creo.help": "Seret tengah mengorbit, Shift+tengah menyorot, Ctrl+seret tengah secara menegak menzum (ke bawah untuk zum masuk); tatal roda untuk zum",
|
||||
"nav.creo.label": "Creo",
|
||||
"nav.free_orbit.help": "Tetikus dan tetikus 3D berputar melalui kedua-dua kutub dan mengekalkan gelek. Pelekapan paksi dengan Shift dilumpuhkan.",
|
||||
"nav.free_orbit.label": "Orbit bebas",
|
||||
"nav.free_orbit.snap_help": "Tetikus dan tetikus 3D berputar melalui kedua-dua kutub dan mengekalkan gelek. Tahan Shift semasa menyeret orbit untuk melekapkan arah pandangan dan gelek.",
|
||||
@@ -2090,19 +2063,12 @@
|
||||
"parts.family.button_head_cap_screw": "Skru kepala butang",
|
||||
"parts.family.dowel_pin": "Pin selari (dowel)",
|
||||
"parts.family.hex_bolt": "Bolt / skru kepala heks",
|
||||
"parts.family.hex_cap_screw_inch": "Skru kepala heks (cap screw, inci)",
|
||||
"parts.family.hex_nut": "Nat heks",
|
||||
"parts.family.hex_nut_inch": "Nat heks (inci)",
|
||||
"parts.family.set_screw": "Skru penetap soket heks, hujung rata",
|
||||
"parts.family.socket_head_cap_screw": "Skru kepala soket",
|
||||
"parts.family.washer": "Sesendal biasa",
|
||||
"parts.family.washer_sae": "Sesendal biasa, SAE (sempit)",
|
||||
"parts.family.washer_uss": "Sesendal biasa, USS (lebar)",
|
||||
"parts.holes": "Lubang (pilihan)",
|
||||
"parts.placement_hint": "pilih satu atau lebih lubang untuk meletakkan bahagian pada setiap satu, atau tekan OK untuk meletakkannya di titik asal",
|
||||
"parts.system": "Sistem unit",
|
||||
"parts.system.inch": "Inci",
|
||||
"parts.system.metric": "Metrik",
|
||||
"pattern.alignment_2": "Penjajaran 2",
|
||||
"pattern.alignment_hint": "Bilangan termasuk asal. Tengah seimbangkan salinan; Balik pilih sisi lebih untuk bilangan genap.",
|
||||
"pattern.direction_1": "Arah 1",
|
||||
@@ -2164,8 +2130,6 @@
|
||||
"prefs.button_left": "Seret kiri",
|
||||
"prefs.button_middle": "Seret tengah",
|
||||
"prefs.button_right": "Seret kanan",
|
||||
"prefs.carousel_next": "Seterusnya",
|
||||
"prefs.carousel_previous": "Sebelumnya",
|
||||
"prefs.choose_font": "Pilih fail fon…",
|
||||
"prefs.click": "Klik",
|
||||
"prefs.contact_shading": "Lorekan sentuhan lembut",
|
||||
@@ -2178,8 +2142,6 @@
|
||||
"prefs.ctrl_cmd": "Ctrl / Cmd",
|
||||
"prefs.curved_surfaces": "Permukaan melengkung",
|
||||
"prefs.curves": "Permukaan melengkung",
|
||||
"prefs.cut_preview_tint": "Papar bahan yang dibuang dalam warna merah",
|
||||
"prefs.cut_preview_tint_help": "Pratonton mengisi bahan yang dibuang oleh sesuatu ciri, dan bentuk yang digantikannya, dengan bayang merah lut sinar. Apabila dimatikan, isian itu telus sepenuhnya dan hanya garis luarnya dilukis.",
|
||||
"prefs.dark_parts_shading": "Kekalkan bentuk bahagian yang gelap",
|
||||
"prefs.dark_parts_shading_help": "Lorekkan pandangan dalam cahaya linear dengan cahaya isian langit yang lembut, supaya muka yang gelap mengekalkan kecerunan dan tidak menjadi hitam",
|
||||
"prefs.dead_zone_note": "Jalur berlorek ialah zon mati: gerakan di dalamnya diabaikan.",
|
||||
@@ -2238,8 +2200,6 @@
|
||||
"prefs.interface_language": "Bahasa antara muka",
|
||||
"prefs.interface_note": "Rupa SolidCraft dan kedudukan bahagiannya.",
|
||||
"prefs.interval_minutes": "{n} min",
|
||||
"prefs.invert_curved_roll_arrows": "Songsangkan anak panah gelek melengkung",
|
||||
"prefs.invert_curved_roll_arrows_note": "Dua anak panah melengkung di sudut kubus menggelek pandangan dalam satah skrin. Hidup: anak panah ikut arah jam menggelek lawan arah jam dan anak panah lawan arah jam menggelek ikut arah jam. Mati: setiap anak panah menggelek pandangan mengikut arah lengkungannya.",
|
||||
"prefs.invert_navigator_arrows": "Songsangkan anak panah putaran kubus pandangan",
|
||||
"prefs.invert_navigator_arrows_note": "Anak panah di sekeliling kubus mengorbit kamera mengikut arah yang ditunjuknya, seperti dalam Fusion dan Inventor: anak panah di kiri menunjuk ke kanan dan mengayunkan kamera ke kanan. Mati: anak panah memutarkan kubus ke arah sisi tempat ia berada, jadi anak panah kiri mengayunkan kamera ke kiri. Anak panah gelek yang melengkung tidak terjejas.",
|
||||
"prefs.keep_work_on_tab_switch": "Kekalkan dialog dan alat terbuka apabila menukar reka bentuk",
|
||||
@@ -2299,8 +2259,6 @@
|
||||
"prefs.placement": "Penempatan",
|
||||
"prefs.prefer_free_sketch_points": "Utamakan titik lakaran yang boleh digerakkan apabila titik bertindih",
|
||||
"prefs.preview_choices": "Pratonton pilihan anda",
|
||||
"prefs.preview_tint_new_faces": "Warnakan permukaan baharu dalam pratonton",
|
||||
"prefs.preview_tint_new_faces_help": "Pratonton ciri yang mengubah sesuatu jasad boleh melukis permukaan yang ditambahnya dalam warna pratonton. Apabila dimatikan, jasad dilukis seperti biasa.",
|
||||
"prefs.print_face_highlight": "Serlahan muka cetakan",
|
||||
"prefs.print_face_highlight_tip": "Sejauh mana muka cetakan sesuatu jasad diwarnakan dan digaris dalam paparan. 0 % tidak menunjukkan serlahan.",
|
||||
"prefs.profile_active": "Profil aktif",
|
||||
@@ -2598,10 +2556,6 @@
|
||||
"render.base_color": "Warna asas · sRGB",
|
||||
"render.base_metallic": "Pecahan logam",
|
||||
"render.base_opaque_only": "Penyunting ini mengekalkan semantik jasad legap. Gunakan kawalan kaca yang khusus untuk transmisi optik.",
|
||||
"render.batch_all_shots": "Semua syot ({count})",
|
||||
"render.batch_empty": "Simpan satu syot sebelum merender semua syot.",
|
||||
"render.batch_limit": "Render Semua Syot dihadkan kepada 32 syot.",
|
||||
"render.batch_status": "{running} sedang berjalan · {waiting} menunggu",
|
||||
"render.bloom_enable": "Papar bloom",
|
||||
"render.bloom_help": "Pendaran kreatif untuk keluaran sahaja daripada serlahan linear positif, disebarkan dengan tiga laluan kotak ternormal. Jejari dalam piksel keluaran. Ditambah dalam cahaya paparan linear sebelum sRGB; bukan kesan kanta, kesan volumetrik atau peningkatan penumpuan. EXR mentah tidak berubah.",
|
||||
"render.bloom_opaque": "Bloom memerlukan keluaran legap. PNG telus ditolak, bukan diratakan secara senyap.",
|
||||
@@ -2712,13 +2666,6 @@
|
||||
"render.choose_look": "Pilih gaya dalam Penampilan dahulu",
|
||||
"render.chromatic_offset": "Ofset tepi kromatik",
|
||||
"render.clear_stage": "Buang pentas yang disimpan",
|
||||
"render.compare_divider": "Pembahagi perbandingan",
|
||||
"render.compare_live": "Langsung",
|
||||
"render.compare_live_preview": "Pratonton langsung",
|
||||
"render.compare_render": "Render {number}",
|
||||
"render.compare_side": "Render {number} · {shot}",
|
||||
"render.compare_summary": "{shot} · {width} × {height} · PNG · {seconds} s · {renderer} · dirakam pada semakan {revision}",
|
||||
"render.compare_swap": "Tukar",
|
||||
"render.copy_request": "Salin JSON",
|
||||
"render.cpu_aa_help": "Sampel ruang: lebih banyak sampel menambah baik tepi dan menambah masa render.",
|
||||
"render.cpu_area_add": "Tambah lampu segi empat tepat CPU",
|
||||
@@ -3468,10 +3415,6 @@
|
||||
"render.result_retained": "Memaparkan imej siap yang terakhir; render yang lebih baharu belum menggantikannya.",
|
||||
"render.result_sequence": "Titik Semak",
|
||||
"render.result_title": "Hasil Render",
|
||||
"render.results_choose": "Hasil yang dikekalkan",
|
||||
"render.results_label": "Render {number} · {shot} · {seconds} s",
|
||||
"render.results_stale_label": "{result} [lapuk]",
|
||||
"render.results_unnamed": "Imej",
|
||||
"render.return_design": "Kembali ke Reka Bentuk",
|
||||
"render.review_stale": "Tinjauan ini sudah lapuk. Selesaikan atau semak semula sebelum menggunakan.",
|
||||
"render.ribbon_aging": "Penuaan",
|
||||
@@ -3955,7 +3898,10 @@
|
||||
"the two origins are on the same component": "kedua-dua titik asal berada pada komponen yang sama",
|
||||
"thread.automatic_size": "Saiz automatik",
|
||||
"thread.designation": "Penamaan",
|
||||
"thread.direction": "Arah bebenang",
|
||||
"thread.iso_metric": "Metrik ISO",
|
||||
"thread.left_hand": "Tangan kiri",
|
||||
"thread.right_hand": "Tangan kanan",
|
||||
"thread.select_size": "Pilih saiz",
|
||||
"timeline.delete_after_marker_one": "Padam 1 Ciri Selepas Penanda Sejarah",
|
||||
"timeline.delete_after_marker_other": "Padam {count} Ciri Selepas Penanda Sejarah",
|
||||
@@ -4129,7 +4075,6 @@
|
||||
"ui.agent_chat_auth_terminal": "Jalankan arahan ini dalam terminal, kemudian tekan Sambung semula:",
|
||||
"ui.agent_chat_auth_title": "Log masuk ke ejen",
|
||||
"ui.agent_chat_cancel": "Batal",
|
||||
"ui.agent_chat_cancel_timeout": "Ejen tidak bertindak balas kepada Hentikan dan telah diputuskan sambungannya. Sambung semula untuk meneruskan.",
|
||||
"ui.agent_chat_cannot_start": "Terpasang, tetapi tidak bermula: {error}",
|
||||
"ui.agent_chat_changes": "{n} perubahan, satu Buat Asal",
|
||||
"ui.agent_chat_changes_n": "{n} perubahan",
|
||||
@@ -4139,13 +4084,11 @@
|
||||
"ui.agent_chat_command": "Arahan ejen",
|
||||
"ui.agent_chat_connect": "Sambung",
|
||||
"ui.agent_chat_copy": "Salin",
|
||||
"ui.agent_chat_copy_message": "Salin mesej",
|
||||
"ui.agent_chat_ctx_bodies": "Jasad",
|
||||
"ui.agent_chat_ctx_elements": "Elemen",
|
||||
"ui.agent_chat_ctx_features": "Ciri",
|
||||
"ui.agent_chat_ctx_more": "+{n} lagi",
|
||||
"ui.agent_chat_ctx_other": "Lain-lain",
|
||||
"ui.agent_chat_custom_heading": "Model tersuai",
|
||||
"ui.agent_chat_custom_hint": "Program yang guna Agent Client Protocol melalui stdin dan stdout. Dijalankan tanpa shell.",
|
||||
"ui.agent_chat_denied": "Ditolak",
|
||||
"ui.agent_chat_deny": "Tolak",
|
||||
@@ -4189,16 +4132,8 @@
|
||||
"ui.agent_chat_mode_all": "Semua",
|
||||
"ui.agent_chat_mode_ask": "Tanya",
|
||||
"ui.agent_chat_mode_edits": "Suntingan",
|
||||
"ui.agent_chat_model_custom": "Guna tersuai: {model}",
|
||||
"ui.agent_chat_model_no_tools": "{model} tidak dapat menggunakan alat SolidCraft, jadi hanya boleh berbual. Pilih model yang menyokong alat.",
|
||||
"ui.agent_chat_model_rejected": "Ejen tidak menerima {model}: {error}",
|
||||
"ui.agent_chat_model_search": "Cari model…",
|
||||
"ui.agent_chat_model_tip": "Model yang digunakan oleh ejen. Pilihan anda disimpan untuk ejen ini.",
|
||||
"ui.agent_chat_models_failed": "Tidak dapat menyenaraikan model: {error}",
|
||||
"ui.agent_chat_models_heading": "Model yang tersedia",
|
||||
"ui.agent_chat_models_listing": "Menyenaraikan model…",
|
||||
"ui.agent_chat_models_none": "Titik akhir tidak menyenaraikan sebarang model",
|
||||
"ui.agent_chat_models_not_a_list": "Jawapan itu bukan senarai model",
|
||||
"ui.agent_chat_new": "Sembang baharu",
|
||||
"ui.agent_chat_new_confirm": "Mulakan sembang baharu? Sembang semasa akan dikosongkan.",
|
||||
"ui.agent_chat_next_picture": "Gambar seterusnya",
|
||||
@@ -4472,21 +4407,14 @@
|
||||
"ui.construction.contact_hint": "Tangen: pilih bulatan/lengkok atau splin/lengkung kon terbuka, kemudian titik sentuh (lengkung bentuk bebas: titik hujung)",
|
||||
"ui.construction.external_hint": "Tangen: pilih bulatan atau lengkok, satu titik luar, kemudian penyelesaian yang dikehendaki",
|
||||
"ui.construction.free_hint": "Garis binaan: klik dua titik; klik kanan untuk mula semula",
|
||||
"ui.construction.normal_needs_length": "Garis tak terhingga sentiasa garis binaan: taip panjang untuk melukis garis biasa",
|
||||
"ui.construction.point_hint": "Garis binaan: klik titik yang akan dilalui",
|
||||
"ui.construction.reference_hint": "Garis binaan: pilih garis lurus, kemudian satu titik; klik kanan untuk menukar rujukan",
|
||||
"ui.construction.ribbon_label": "Garis bantu",
|
||||
"ui.construction.side_hint": "Garis binaan: klik sisi yang dituju garis; klik kanan untuk mula semula",
|
||||
"ui.context_menu": "Menu konteks",
|
||||
"ui.continuity_crease": "Lipatan",
|
||||
"ui.continuity_curvature": "Kelengkungan",
|
||||
"ui.continuity_curvature_change": "Perubahan kelengkungan",
|
||||
"ui.continuity_g0": "Lipatan G0",
|
||||
"ui.continuity_g1": "Tangen G1",
|
||||
"ui.continuity_g2": "Kelengkungan G2",
|
||||
"ui.continuity_gap": "Sela",
|
||||
"ui.continuity_position": "Kedudukan",
|
||||
"ui.continuity_tangent": "Tangen",
|
||||
"ui.copy": "Salin",
|
||||
"ui.corner": "Sudut",
|
||||
"ui.corners": "Sudut",
|
||||
@@ -4747,7 +4675,6 @@
|
||||
"ui.hideOrigin": "Sembunyi Titik Asal",
|
||||
"ui.hidePlanes": "Sembunyi Satah",
|
||||
"ui.hideProfile": "Sembunyi Profil",
|
||||
"ui.hide_component_history": "Sembunyikan Sejarahnya",
|
||||
"ui.hide_origin_by_default": "Sembunyi titik asal secara lalai",
|
||||
"ui.hide_origin_note": "Reka bentuk baharu bermula dengan folder Titik Asal disembunyikan. Sesuatu reka bentuk mengekalkan tetapan Titik Asalnya sendiri sebaik sahaja anda memaparkan atau menyembunyikannya.",
|
||||
"ui.history_back": "Undur satu langkah",
|
||||
@@ -4755,7 +4682,6 @@
|
||||
"ui.history_edge_hint": "klik dua kali pada tepi",
|
||||
"ui.history_first": "Pergi ke permulaan",
|
||||
"ui.history_forward": "Maju satu langkah",
|
||||
"ui.history_hide_hidden": "Sembunyikan Komponen Tersembunyi",
|
||||
"ui.history_hide_names": "Sembunyi nama",
|
||||
"ui.history_last": "Pergi ke penghujung",
|
||||
"ui.history_marker_tip": "Penanda sejarah: seret untuk mengundurkan model; ciri baharu diletakkan di sini",
|
||||
@@ -4765,12 +4691,10 @@
|
||||
"ui.history_play_button_show": "Papar Butang Main",
|
||||
"ui.history_play_finish_action": "Selesaikan tindakan semasa sebelum memainkan Sejarah",
|
||||
"ui.history_play_roll_back": "Alih penanda Sejarah ke belakang sebelum memainkan",
|
||||
"ui.history_show_hidden": "Papar Komponen Tersembunyi",
|
||||
"ui.history_show_names": "Papar nama",
|
||||
"ui.history_sketch_listed_under": "Disenaraikan di bawah {feature}",
|
||||
"ui.history_title": "Sejarah",
|
||||
"ui.history_toggle_names": "Papar atau sembunyikan nama ciri",
|
||||
"ui.history_waits_for_editor": "Buat Asal, Buat Semula dan Sejarah menunggu sehingga penyunting ciri dibuka",
|
||||
"ui.hole": "Lubang",
|
||||
"ui.hole_tap_type": "Jenis Bebenang Lubang",
|
||||
"ui.hole_type": "Jenis Lubang",
|
||||
@@ -4786,7 +4710,6 @@
|
||||
"ui.insert_design.saved_file": "{name} mempunyai perubahan yang belum disimpan, jadi fail tersimpannya disisipkan. Simpan reka bentuk itu, kemudian gunakan Kemas Kini Pautan untuk membawa masuk perubahan.",
|
||||
"ui.insert_design.unsaved": "Perubahan belum disimpan",
|
||||
"ui.inspect": "Periksa",
|
||||
"ui.instances": "Salinan",
|
||||
"ui.intersect": "Silang",
|
||||
"ui.isolate": "Asingkan",
|
||||
"ui.join": "Cantum",
|
||||
@@ -5051,7 +4974,6 @@
|
||||
"ui.showDimension": "Papar Dimensi",
|
||||
"ui.showOrigin": "Papar Titik Asal",
|
||||
"ui.showPlanes": "Papar Satah",
|
||||
"ui.show_component_history": "Papar Sejarahnya",
|
||||
"ui.show_constraints": "Kekangan",
|
||||
"ui.show_construction_geometries": "Geometri Binaan",
|
||||
"ui.show_dimensions": "Dimensi",
|
||||
@@ -5234,7 +5156,7 @@
|
||||
"update.check_unavailable": "Semakan kemas kini tidak tersedia untuk pemasangan ini.",
|
||||
"update.checking": "Menyemak…",
|
||||
"update.copy": "Salin",
|
||||
"update.dmg_opened": "Imej cakera telah dibuka dan disahkan: keluar dari SolidCraft, seret SolidCraft yang baharu ke Applications untuk menggantikan salinan lama, kemudian buka SolidCraft semula.",
|
||||
"update.dmg_opened": "Imej cakera telah dibuka dan disahkan: seret SolidCraft ke Applications untuk menggantikan salinan lama, kemudian mulakan semula SolidCraft.",
|
||||
"update.download": "Muat turun",
|
||||
"update.download_open": "Muat turun dan buka",
|
||||
"update.downloading": "Memuat turun dan mengesahkan… {done} daripada {total} MB",
|
||||
@@ -5278,7 +5200,6 @@
|
||||
"web.open_link": "Buka reka bentuk yang dipautkan?",
|
||||
"web.open_link_confirm": "Terdapat perubahan yang belum disimpan. Buka reka bentuk yang dipautkan dalam tab baharu?",
|
||||
"↑ ↓ choose · Enter / Tab complete · Esc dismiss": "↑ ↓ pilih · Enter / Tab lengkapkan · Esc tutup",
|
||||
"↑ ↓ choose · Enter applies this value": "↑ ↓ pilih · Enter menggunakan nilai ini",
|
||||
"↑ ↓ choose · Enter applies what you typed · Esc dismiss": "↑ ↓ pilih · Enter menggunakan apa yang anda taip · Esc tutup",
|
||||
"⌘Enter continues on GitHub.": "⌘Enter diteruskan di GitHub."
|
||||
}
|
||||
|
||||