Compare commits
56 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b5728f284b | |||
| dc32cff620 | |||
| 531fc04db8 | |||
| 4907429439 | |||
| 192ef78ff3 | |||
| 44a0be8d94 | |||
| 9cba66c14d | |||
| 4afd93331f | |||
| 698621dfdf | |||
| 5fc23436ca | |||
| 76879f8b39 | |||
| 514b0c84f9 | |||
| ddca1054ab | |||
| 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",
|
||||
]
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -31,8 +31,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);
|
||||
@@ -50,14 +48,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:
|
||||
@@ -66,14 +60,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
|
||||
@@ -117,6 +103,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: native, and the whole workspace or solidcraft-gpu selected. The
|
||||
# job's `if` and the `ci` job's check both read this, so they cannot disagree.
|
||||
@@ -151,6 +138,7 @@ jobs:
|
||||
run: |
|
||||
native=false
|
||||
kernel=false
|
||||
goldens=false
|
||||
windows=false
|
||||
full=true
|
||||
packages=
|
||||
@@ -191,12 +179,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
|
||||
@@ -226,6 +214,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"
|
||||
@@ -233,6 +228,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.
|
||||
@@ -247,12 +246,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
|
||||
@@ -265,6 +265,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) run with the other tests, when solidcraft-gpu is
|
||||
@@ -278,6 +279,7 @@ jobs:
|
||||
echo "native=$native"
|
||||
echo "gpu=$gpu"
|
||||
echo "kernel=$kernel"
|
||||
echo "goldens=$goldens"
|
||||
echo "windows=$windows"
|
||||
echo "oracle=$oracle"
|
||||
echo "doctests=$doctests"
|
||||
@@ -301,7 +303,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
|
||||
@@ -348,10 +350,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Cargo check
|
||||
run: cargo check --workspace --all-targets --locked
|
||||
|
||||
@@ -370,10 +369,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Clippy
|
||||
env:
|
||||
FULL: ${{ needs.changes.outputs.full }}
|
||||
@@ -416,10 +412,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -490,10 +483,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -504,8 +494,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.
|
||||
@@ -519,23 +514,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
|
||||
@@ -609,10 +610,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 }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
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:
|
||||
@@ -627,14 +625,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
|
||||
@@ -768,10 +760,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -829,10 +818,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
@@ -871,10 +857,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
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
|
||||
@@ -898,10 +881,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
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
|
||||
@@ -1022,10 +1002,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh wasm32-unknown-unknown
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- run: cargo xtask wasm
|
||||
|
||||
riscv64-gates:
|
||||
@@ -1047,10 +1024,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' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh riscv64gc-unknown-linux-gnu
|
||||
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.
|
||||
@@ -1066,13 +1040,11 @@ 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:
|
||||
CARGO_TARGET_RISCV64GC_UNKNOWN_LINUX_GNU_LINKER: riscv64-linux-gnu-gcc
|
||||
# SMLib has no prebuilt library for RISC-V: its sources are compiled with this.
|
||||
CXX_riscv64gc_unknown_linux_gnu: riscv64-linux-gnu-g++
|
||||
PKG_CONFIG_SYSROOT_DIR: /
|
||||
PKG_CONFIG_PATH: /usr/lib/riscv64-linux-gnu/pkgconfig:/usr/share/pkgconfig
|
||||
PKG_CONFIG_ALLOW_CROSS: 1
|
||||
|
||||
@@ -53,9 +53,6 @@ jobs:
|
||||
with:
|
||||
key: docker-web
|
||||
save-if: ${{ github.ref == 'refs/heads/main' }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh wasm32-unknown-unknown
|
||||
- name: Install trunk
|
||||
run: |
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
|
||||
@@ -29,9 +29,6 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
- name: Build the CLI
|
||||
run: cargo build --profile ci -p solidcraft-cli
|
||||
- name: Import and check every entry
|
||||
|
||||
@@ -1,109 +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/ on SMLib, 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"
|
||||
- "scripts/fetch-smlib.sh"
|
||||
- ".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"
|
||||
- "scripts/fetch-smlib.sh"
|
||||
- ".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: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
version: v0.16.0
|
||||
- name: Build the CLI (release, SMLib)
|
||||
run: cargo build --release --locked -p solidcraft-cli --features smlib
|
||||
- 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
|
||||
@@ -48,9 +48,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: generated-files
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
- name: THIRD-PARTY-LICENSES.txt
|
||||
run: cargo xtask licences
|
||||
- name: docs/features.md
|
||||
|
||||
@@ -1,234 +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; the ladder runs whenever any
|
||||
# area does. One job then builds solidcraft-cli once (release, SMLib prebuilt), 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 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/fetch-smlib.sh"
|
||||
- "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 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/fetch-smlib.sh"
|
||||
- "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"
|
||||
python3 -I scripts/ci/kernel_rig_plan.py --paths "$RUNNER_TEMP/rig-paths" --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: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
version: v0.16.0
|
||||
- name: Build the CLI (release, SMLib) and the rig's runner
|
||||
run: |
|
||||
cargo build --release --locked -p solidcraft-cli --features smlib
|
||||
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
|
||||
@@ -41,9 +41,6 @@ jobs:
|
||||
with:
|
||||
targets: wasm32-unknown-unknown
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh wasm32-unknown-unknown
|
||||
- name: Install mdbook and trunk
|
||||
run: |
|
||||
mkdir -p "$HOME/.local/bin"
|
||||
|
||||
@@ -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
|
||||
@@ -20,8 +20,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
|
||||
@@ -42,25 +40,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
|
||||
@@ -125,9 +104,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-icons
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
- run: cargo build --release --locked -p solidcraft-cli
|
||||
- run: |
|
||||
. packaging/env.sh
|
||||
@@ -167,9 +143,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-linux-${{ matrix.arch }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh
|
||||
- name: Build and package (AppImage, deb, rpm, tar.gz)
|
||||
run: packaging/linux/package.sh
|
||||
- name: Check the packages
|
||||
@@ -337,7 +310,7 @@ jobs:
|
||||
name: macOS build ${{ matrix.arch }}
|
||||
needs: version
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: macos-15
|
||||
runs-on: macos-14
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -355,9 +328,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-macos-${{ matrix.arch }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh ${{ matrix.target }}
|
||||
- name: Build
|
||||
run: packaging/macos/package.sh --build-only ${{ matrix.target }}
|
||||
# Not named release-*: the release job collects every release-* artifact as an asset.
|
||||
@@ -374,7 +344,7 @@ jobs:
|
||||
name: macOS universal
|
||||
needs: [version, macos-build]
|
||||
if: needs.version.outputs.build == 'true'
|
||||
runs-on: macos-15
|
||||
runs-on: macos-14
|
||||
env:
|
||||
SOLIDCRAFT_VERSION: ${{ needs.version.outputs.version }}
|
||||
SOLIDCRAFT_BUILD_DATE: ${{ needs.version.outputs.date }}
|
||||
@@ -456,9 +426,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-windows-${{ matrix.arch }}
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh ${{ matrix.target }}
|
||||
- name: Install WiX
|
||||
shell: pwsh
|
||||
run: |
|
||||
@@ -590,9 +557,6 @@ jobs:
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: release-web
|
||||
- name: Fetch SMLib (crates/smlib/SMLIB_REV)
|
||||
shell: bash
|
||||
run: scripts/fetch-smlib.sh wasm32-unknown-unknown
|
||||
- name: Install trunk
|
||||
run: |
|
||||
curl -fsSL "https://github.com/trunk-rs/trunk/releases/download/v${TRUNK_VERSION}/trunk-x86_64-unknown-linux-gnu.tar.gz" \
|
||||
@@ -608,8 +572,7 @@ jobs:
|
||||
release:
|
||||
name: GitHub Release or dry-run artifacts
|
||||
needs: [version, linux, flatpak, 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
|
||||
|
||||
@@ -101,22 +101,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 |
|
||||
@@ -308,6 +292,53 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/model/colored_components_off.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/combine.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/component.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
|
||||
### Render workspace icon drafts
|
||||
|
||||
Original SolidCraft drawings, Open for owner review; no runtime assignments.
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| assets/icons/ui/design/svg/model/render_all_shots.svg | SolidCraft contributors | original Camera icon master (64 grid): stacked camera bodies with a lower-right play badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_appearance.svg | SolidCraft contributors | original Looks icon master (64 grid): shaded look sphere on the top corner of an accent-shaded cube | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_aspect_frame.svg | SolidCraft contributors | original Camera icon master (64 grid): wide rounded frame with two opposite corner brackets | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_backdrop.svg | SolidCraft contributors | original Studio icon master (64 grid): grey cube in front of a tall upright accent panel | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_cancel.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round stop badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_check.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round check badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_compare.svg | SolidCraft contributors | original Output icon master (64 grid): two grey pictures split by a vertical slider with opposed arrows | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_copy_look.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere in a square frame offset from a second frame outline | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_execute.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a lower-right round play badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_export_image.svg | SolidCraft contributors | original Output icon master (64 grid): picture with a large outward diagonal arrow at the upper right | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_exposure.svg | SolidCraft contributors | original Camera icon master (64 grid): split light and accent disc with plus and minus marks | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_fit_product.svg | SolidCraft contributors | original Camera icon master (64 grid): grey cube inside four corner brackets | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_frame_guides.svg | SolidCraft contributors | original Camera icon master (64 grid): rounded frame with a rule-of-thirds grid and four accent nodes | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_from_material.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere struck through above a plain grey slab | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_ground.svg | SolidCraft contributors | original Studio icon master (64 grid): grey cube on a slab with an accent contact patch | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_lens.svg | SolidCraft contributors | original Camera icon master (64 grid): concentric lens rings around an accent centre | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_library.svg | SolidCraft contributors | original Studio icon master (64 grid): row of three look spheres, middle one accent, on a shelf bar | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_area.svg | SolidCraft contributors | original Lights icon master (64 grid): slanted flat panel above a dotted accent stem | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_by_highlight.svg | SolidCraft contributors | original Lights icon master (64 grid): shaded sphere with a ringed highlight target and dotted stem arriving from above | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_point.svg | SolidCraft contributors | original Lights icon master (64 grid): round source disc with centre dot above a dotted accent stem | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_light_spot.svg | SolidCraft contributors | original Lights icon master (64 grid): cone source with curved rim above a dotted accent stem | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_lighting_presets.svg | SolidCraft contributors | original Studio icon master (64 grid): two stacked cards, the front one holding a light bulb | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_output_size.svg | SolidCraft contributors | original Output icon master (64 grid): picture frame with a diagonal double-headed dimension arrow | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_paint.svg | SolidCraft contributors | original Looks icon master (64 grid): flat brush with an accent tip, light ferrule and slate handle | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_pick_up.svg | SolidCraft contributors | original Looks icon master (64 grid): eyedropper with a grey bulb, slate crossbar and accent sampling tip | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_quality.svg | SolidCraft contributors | original Output icon master (64 grid): three rising bars, the tallest in accent | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_replace_all.svg | SolidCraft contributors | original Looks icon master (64 grid): grey sample sphere with an arrow to an accent sphere beside a second one | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_results.svg | SolidCraft contributors | original Output icon master (64 grid): stack of two pictures, the front one showing a landscape | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_same_look.svg | SolidCraft contributors | original Looks icon master (64 grid): look sphere and three cubes, the two matching cubes ringed | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_shot.svg | SolidCraft contributors | original Camera icon master (64 grid): camera body with a lower-right plus badge | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_shots_strip.svg | SolidCraft contributors | original Camera icon master (64 grid): strip of three small frames, the middle one in accent | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_solo_light.svg | SolidCraft contributors | original Lights icon master (64 grid): point source and dotted stem inside four corner focus brackets | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_sun.svg | SolidCraft contributors | original Lights icon master (64 grid): source disc with eight short radial rays | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_transparent_background.svg | SolidCraft contributors | original Output icon master (64 grid): grey cube on a checker tile | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_update_shot.svg | SolidCraft contributors | original Camera icon master (64 grid): camera body with a lower-right refresh badge | MIT OR Apache-2.0 |
|
||||
|
||||
### UI icon masters (continued)
|
||||
|
||||
| File | Author | Source | Licence |
|
||||
|---|---|---|---|
|
||||
| assets/icons/ui/design/svg/model/compute.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/configuration_table.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/conic.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
@@ -397,6 +428,7 @@ Editable drawing code, native projects and shipped exports are collected under `
|
||||
| assets/icons/ui/design/svg/flat/shield.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/status_recovery.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/frame_member.svg | SolidCraft contributors | original icon master (64 grid) | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/render_environment.svg | SolidCraft contributors | original Studio icon master (64 grid): dome with a grey cube inside | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/placeholder.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/plane.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
| assets/icons/ui/design/svg/model/plane_angle.svg | SolidCraft contributors | two-tone modelling icon master (64 grid), original SolidCraft artwork | MIT OR Apache-2.0 |
|
||||
@@ -605,6 +637,11 @@ copied into SolidCraft.
|
||||
|
||||
| Directory | Source | Licence | Changes |
|
||||
|---|---|---|---|
|
||||
| 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
|
||||
|
||||
|
||||
@@ -158,11 +158,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)).
|
||||
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.
|
||||
|
||||
@@ -221,7 +219,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
|
||||
@@ -241,8 +240,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.
|
||||
|
||||
|
||||
@@ -188,6 +188,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"
|
||||
@@ -208,6 +217,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"
|
||||
@@ -520,6 +535,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"
|
||||
@@ -767,6 +791,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"
|
||||
@@ -789,6 +824,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"
|
||||
@@ -859,6 +907,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"
|
||||
@@ -1036,6 +1090,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"
|
||||
@@ -1182,7 +1249,7 @@ dependencies = [
|
||||
"bitflags 2.13.2",
|
||||
"emath",
|
||||
"epaint",
|
||||
"itertools",
|
||||
"itertools 0.15.0",
|
||||
"log",
|
||||
"nohash-hasher",
|
||||
"profiling",
|
||||
@@ -1600,8 +1667,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"
|
||||
@@ -4053,7 +4208,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"block2 0.6.2",
|
||||
"eframe",
|
||||
@@ -4087,24 +4242,23 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-3dx-client"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-cli"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-mcp",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-derive"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
@@ -4113,7 +4267,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-doc"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"serde",
|
||||
@@ -4122,7 +4276,6 @@ dependencies = [
|
||||
"solidcraft-derive",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-render",
|
||||
"solidcraft-sketch",
|
||||
"thiserror 2.0.21",
|
||||
@@ -4131,7 +4284,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-engine"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4143,7 +4296,6 @@ dependencies = [
|
||||
"solidcraft-geom",
|
||||
"solidcraft-io",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-profile",
|
||||
"solidcraft-render",
|
||||
"solidcraft-sketch",
|
||||
"thiserror 2.0.21",
|
||||
@@ -4153,7 +4305,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-geom"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4162,7 +4314,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-gpu"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"log",
|
||||
@@ -4182,7 +4334,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-io"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"cadmpeg-core",
|
||||
"cadmpeg-ir",
|
||||
@@ -4200,14 +4352,13 @@ dependencies = [
|
||||
"solidcraft-doc",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-kernel",
|
||||
"solidcraft-profile",
|
||||
"thiserror 2.0.21",
|
||||
"zip",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-kernel"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"log",
|
||||
"proptest",
|
||||
@@ -4215,34 +4366,28 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"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.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"solidcraft-engine",
|
||||
"solidcraft-gpu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-profile"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-render"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"image",
|
||||
"moxcms",
|
||||
@@ -4256,7 +4401,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-sketch"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
@@ -4265,37 +4410,16 @@ dependencies = [
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-smlib"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"flate2",
|
||||
"log",
|
||||
"serde_json",
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
"web-time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-space-mouse"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-step"
|
||||
version = "0.24.0"
|
||||
dependencies = [
|
||||
"solidcraft-geom",
|
||||
"solidcraft-profile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-ui-egui"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"arboard",
|
||||
"egui",
|
||||
@@ -4325,7 +4449,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "solidcraft-web"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"eframe",
|
||||
"egui",
|
||||
@@ -4386,6 +4510,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"
|
||||
@@ -4623,6 +4757,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"
|
||||
@@ -5722,10 +5994,9 @@ checksum = "e450f9b2ed1dff33c94c12589a87338689467b9c4f5d8a5710bd09a847d2c8a7"
|
||||
|
||||
[[package]]
|
||||
name = "xtask"
|
||||
version = "0.24.0"
|
||||
version = "0.24.1"
|
||||
dependencies = [
|
||||
"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.24.0"
|
||||
version = "0.24.1"
|
||||
edition = "2024"
|
||||
license = "MIT OR Apache-2.0"
|
||||
rust-version = "1.95"
|
||||
@@ -16,11 +16,8 @@ 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-gpu = { path = "crates/gpu" }
|
||||
solidcraft-doc = { path = "crates/doc" }
|
||||
@@ -48,7 +45,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"
|
||||
|
||||
@@ -121,7 +125,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
|
||||
@@ -144,9 +150,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,9 @@ name = "solvecraft-cli"
|
||||
path = "src/alias.rs"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu", "smlib", "profile"]
|
||||
default = ["raytrace-cpu"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
# Link SMLib into the kernel (solidcraft-engine/smlib; needs scripts/fetch-smlib.sh). SMLib is the
|
||||
# only kernel, so without it the command line models nothing.
|
||||
smlib = ["solidcraft-engine/smlib"]
|
||||
# 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]
|
||||
solidcraft-engine = { workspace = true }
|
||||
@@ -40,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()})
|
||||
@@ -233,7 +233,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,7 +1,7 @@
|
||||
//! SolidCraft headless command line.
|
||||
//!
|
||||
//! ```text
|
||||
//! solidcraft-cli run <script.json|design.solidcraft|part.step|mesh.3mf> [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors] [--validation MODE]
|
||||
//! solidcraft-cli run <script.json|design.solidcraft|part.step|mesh.3mf> [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]
|
||||
//! solidcraft-cli eval <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--json-out FILE] (alias: inspect) measurements as JSON
|
||||
//! solidcraft-cli script <design.solidcraft> [--out script.json] the design's history as a command script
|
||||
//! solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json] loading timings as JSON lines
|
||||
@@ -9,13 +9,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)
|
||||
@@ -32,11 +31,9 @@ mod bench_agent;
|
||||
mod bench_assistant;
|
||||
mod crashes;
|
||||
mod fuzz;
|
||||
mod kernel_cases;
|
||||
mod logger;
|
||||
mod oracle;
|
||||
mod preview;
|
||||
mod profiling;
|
||||
mod recipe;
|
||||
mod render;
|
||||
mod spatial;
|
||||
@@ -59,19 +56,19 @@ struct Command {
|
||||
}
|
||||
|
||||
/// `run`'s lines in the usage.
|
||||
const USAGE_RUN: &str = " solidcraft-cli run <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--profile-out trace.json] [--json-out FILE] [--allow-errors] [--validation each_step|each_feature|off]\n solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--validation: when SMLib's full check runs on the features computed (diagnostics.validation), default each_feature)\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.solidcraft|part.step> [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] [--timings] [--profile] [--json-out FILE] [--allow-errors]\n solidcraft-cli run --in <design.solidcraft|part.step> [--out FILE]...\n (a timeline feature that fails fails the run and nothing is written; --allow-errors writes anyway)\n (--timings prints {timings: [{index, command, ms}], total_ms} for a script, with --results its results too;\n --profile adds each command's profile: {boolean_ms, tessellate_ms, other_ms}, the kernel's share)\n";
|
||||
/// `eval`'s lines in the usage.
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--json-out FILE] [--profile] [--profile-out trace.json] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.solidcraft|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n (--profile and --profile-out as for run)\n";
|
||||
const USAGE_EVAL: &str = " solidcraft-cli eval <script.json|design.solidcraft|part.step> [--in design.solidcraft|part.step] [--json-out FILE] (alias: inspect)\n solidcraft-cli eval --in <script.json|design.solidcraft|part.step> [--json-out FILE]\n (with both inputs, runs the positional script on --in before measuring; does not automatically save the modified design)\n (--json-out FILE writes the JSON that run (with --results, else the measurement) and eval print to FILE instead of stdout,\n as plain UTF-8: a shell's > can re-encode it, e.g. Windows PowerShell adds a byte-order mark)\n";
|
||||
/// `script`'s lines in the usage.
|
||||
const USAGE_SCRIPT: &str = " solidcraft-cli script <design.solidcraft|script.json> [--out script.json]\n (the design's history as a command script that eval and run replay)\n";
|
||||
/// `bench-open`'s lines in the usage.
|
||||
const USAGE_BENCH_OPEN: &str = " solidcraft-cli bench-open <design.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json] [--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.solidcraft | part.step> [--parse-only] [--save OUT.solidcraft] [--display LEVEL [--edit NAME EXPR]] [--ops script.json]\n (--ops: after opening, run each command of the script and undo it, timing the command, the\n evaluations it caused and the view it leaves: placing and meshing what changed)\n";
|
||||
/// `bench-agent`'s lines in the usage.
|
||||
const USAGE_BENCH_AGENT: &str = " solidcraft-cli bench-agent [--scale F] [--save design.solidcraft] [--script-out script.json]\n";
|
||||
/// `bench-assistant`'s lines in the usage.
|
||||
const USAGE_BENCH_ASSISTANT: &str = " solidcraft-cli bench-assistant --connect 127.0.0.1:PORT [--token T] --base-url URL --model NAME [--tasks 1,2,5] [--repeat N] [--timeout-s 300] [--json out.json] [--markdown out.md]\n";
|
||||
/// `snapshot`'s lines in the usage.
|
||||
const USAGE_SNAPSHOT: &str = " solidcraft-cli snapshot <script|design> [--in design.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27] [--quality coarse|normal|fine|ultra]\n solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON;\n --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.solidcraft|part.step] --out shot.png [--width W] [--height H] [--view NAME | --camera JSON] [--no-grid] [--no-axes]\n [--body NAME:opacity=0.3,color=#rrggbb]... [--tool NAME]... [--section z=27]\n solidcraft-cli snapshot --in <design.solidcraft|part.step> --out shot.png ...\n (as with run, --in starts from a design and the script runs on top of it; an option snapshot doesn't take is an error;\n --view: iso, front, back, top, bottom, left, right, or the name of a view saved in the design with view.save;\n --camera: {target: [x,y,z], yaw, pitch, distance, fov?} in mm and radians, as the MCP screenshot takes it (docs/mcp.md);\n --no-grid and --no-axes leave the ground grid (on Z 0) and the origin's axes out of the picture;\n --body: how one body is drawn, as appearance.assign takes it: opacity 0-1 (below 1 is see-through, so what is inside\n it shows) and color #rrggbb, either or both, once per body;\n --tool: draw this body solid in the tool colour and every other body see-through, so a cut's or a join's tool body\n is visible inside the material it meets; a later --body overrides it;\n --section: cut the model as inspect.section does, so the inside shows: z=27 (the plane through that world\n coordinate, keeping the smaller side), add ,flip to keep the other side, or XY, XZ, YZ, or its own JSON)\n";
|
||||
/// `render`'s lines in the usage.
|
||||
const USAGE_RENDER: &str = " solidcraft-cli render <script|design> --out image.png [--setup NAME] [--width W] [--height H] [--transparent] [--overwrite] [--timeout SECONDS]\n [--progressive] [--samples N] [--max-depth N] [--seed N] [--exposure EV] [--lighting FILE.json] [--environment FILE.json] [--tone-map reinhard|aces_fitted]\n";
|
||||
/// `exec`'s lines in the usage.
|
||||
@@ -82,8 +79,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] [--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>; --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";
|
||||
@@ -121,7 +116,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 },
|
||||
@@ -137,19 +131,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;
|
||||
}
|
||||
};
|
||||
let r = match args.first().map(String::as_str) {
|
||||
// Asked-for help is not an error: the command's usage on stdout, exit 0. It is read
|
||||
// before the command runs, so `--help` never starts a run (a fuzz run, a server).
|
||||
@@ -183,7 +164,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") => cmd_mcp(&args[1..]),
|
||||
@@ -216,11 +196,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) => {
|
||||
@@ -278,15 +253,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) {
|
||||
s.execute("doc.open", &json!({"path": path})).map_err(|e| e.to_string())?;
|
||||
return Ok(s);
|
||||
@@ -294,7 +262,7 @@ fn load_with(path: &str, check: solidcraft_engine::schema::ParamCheck, validatio
|
||||
let bytes = solidcraft_engine::io::vfs::read(path, solidcraft_engine::io::MAX_DESIGN_BYTES).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);
|
||||
}
|
||||
@@ -340,16 +308,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") {
|
||||
@@ -381,34 +339,21 @@ fn print_json(v: &Value, file: Option<&str>) -> Result<(), String> {
|
||||
fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
install_renderer();
|
||||
let json_out = json_out(args)?;
|
||||
known_args(
|
||||
"run",
|
||||
args,
|
||||
&["--in", "--out", "--save", "--json-out", "--profile-out", "--validation"],
|
||||
&["--quiet", "--results", "--timings", "--profile", "--allow-errors"],
|
||||
true,
|
||||
)?;
|
||||
let validation = validation_flag("run", args)?;
|
||||
known_args("run", args, &["--in", "--out", "--save", "--json-out"], &["--quiet", "--results", "--timings", "--profile", "--allow-errors"], true)?;
|
||||
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, solidcraft_engine::schema::ParamCheck::Off, validation)?,
|
||||
None => {
|
||||
let mut s = Session::default();
|
||||
s.set_validation(validation);
|
||||
s
|
||||
}
|
||||
Some(b) => load(b)?,
|
||||
None => Session::default(),
|
||||
};
|
||||
let needs = if want_results { "--results needs a script" } else { "--timings needs a script" };
|
||||
let v = read_json(path.ok_or(needs)?)?;
|
||||
@@ -423,7 +368,7 @@ fn cmd_run(args: &[String]) -> Result<(), String> {
|
||||
};
|
||||
(s, Some(out))
|
||||
} else {
|
||||
(open_validating(base, path, validation)?, None)
|
||||
(open(base, path)?, 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
|
||||
@@ -450,12 +395,6 @@ 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.
|
||||
fn open(base: Option<&str>, path: Option<&String>) -> Result<Session, String> {
|
||||
open_validating(base, path, solidcraft_engine::kernel::Validation::default())
|
||||
}
|
||||
|
||||
/// [`open`], the features computed under `validation`.
|
||||
fn open_validating(base: Option<&str>, path: Option<&String>, validation: solidcraft_engine::kernel::Validation) -> Result<Session, String> {
|
||||
let load = |p: &str| load_with(p, solidcraft_engine::schema::ParamCheck::Off, validation);
|
||||
match (base, path) {
|
||||
(Some(b), Some(p)) => {
|
||||
let mut s = load(b)?;
|
||||
@@ -468,15 +407,6 @@ fn open_validating(base: Option<&str>, path: Option<&String>, validation: solidc
|
||||
}
|
||||
}
|
||||
|
||||
/// `--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.
|
||||
@@ -514,7 +444,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))?;
|
||||
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())?;
|
||||
@@ -596,7 +526,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,
|
||||
)?;
|
||||
@@ -609,16 +539,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))?;
|
||||
// How the bodies are drawn and where the model is cut, through the commands the app's
|
||||
// Appearance dialog and Section Analysis run (`preview`).
|
||||
@@ -638,12 +558,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});
|
||||
@@ -880,7 +797,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)
|
||||
@@ -936,7 +853,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);
|
||||
@@ -1006,7 +923,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;
|
||||
|
||||
@@ -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, SMLib'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(())
|
||||
}
|
||||
}
|
||||
@@ -196,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();
|
||||
@@ -1338,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}"));
|
||||
@@ -1360,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}"));
|
||||
@@ -1379,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}"));
|
||||
@@ -1396,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}"));
|
||||
@@ -1736,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))
|
||||
});
|
||||
|
||||
@@ -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,6 +1,5 @@
|
||||
//! 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 std::path::{Path, PathBuf};
|
||||
|
||||
@@ -22,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();
|
||||
@@ -52,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)"));
|
||||
}
|
||||
|
||||
@@ -139,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();
|
||||
@@ -147,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;
|
||||
@@ -191,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.
|
||||
@@ -288,56 +288,3 @@ fn undoing_an_edit_after_opening_reuses_the_saved_results() {
|
||||
let _ = std::fs::remove_file(&path);
|
||||
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, 10 faces and 7928.743041 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(), 10);
|
||||
assert!((v - 7928.743041).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()));
|
||||
}
|
||||
|
||||
@@ -1,403 +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 std::path::{Path, PathBuf};
|
||||
use std::process::Command;
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solidcraft_engine::Session;
|
||||
use solidcraft_engine::doc::{FaceAttachmentStatus, Feature, FeatureKind, ModelBody, ModelState, PlaneRef, naming};
|
||||
use solidcraft_engine::geom::Vec3;
|
||||
|
||||
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"));
|
||||
}
|
||||
@@ -31,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())
|
||||
@@ -139,14 +139,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.solidcraft")).unwrap()).unwrap();
|
||||
let at = |height: &str, file: &str| {
|
||||
@@ -169,13 +171,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())
|
||||
|
||||
@@ -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");
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -48,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,15 +14,8 @@ name = "solidcraft-web"
|
||||
path = "src/main.rs"
|
||||
|
||||
[features]
|
||||
default = ["smlib", "profile"]
|
||||
default = []
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
# Link SMLib into the kernel (solidcraft-engine/smlib; needs scripts/fetch-smlib.sh
|
||||
# wasm32-unknown-unknown, or the sources and wasi-sdk in WASI_SDK_PATH, crates/smlib/build.rs). SMLib
|
||||
# is the only kernel, so without it the web app models nothing.
|
||||
smlib = ["solidcraft-engine/smlib"]
|
||||
# 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 }
|
||||
|
||||
@@ -8,15 +8,9 @@ publish = false
|
||||
default-run = "solidcraft"
|
||||
|
||||
[features]
|
||||
default = ["raytrace-cpu", "bundled-render-library", "smlib", "profile"]
|
||||
default = ["raytrace-cpu", "bundled-render-library"]
|
||||
bundled-render-library = ["solidcraft-engine/bundled-render-library"]
|
||||
raytrace-cpu = ["solidcraft-engine/raytrace-cpu"]
|
||||
# Link SMLib into the kernel (solidcraft-engine/smlib; needs scripts/fetch-smlib.sh). SMLib is the
|
||||
# only kernel, so without it the app models nothing.
|
||||
smlib = ["solidcraft-engine/smlib"]
|
||||
# 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
|
||||
|
||||
@@ -679,8 +679,6 @@ fn main() -> eframe::Result {
|
||||
#[cfg(target_os = "macos")]
|
||||
objc2_foundation::NSProcessInfo::processInfo().setProcessName(objc2_foundation::ns_string!("SolidCraft"));
|
||||
mark("main");
|
||||
// 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() {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Render workspace icon drafts
|
||||
|
||||
All ids below are **Open** for owner review, except `render_scene` and `render_image`,
|
||||
which are existing accepted icons used only as references. No commands, toolbar
|
||||
entries, browser selectors or runtime request manifests are changed by this draft set.
|
||||
`render_remove_override` is the one remaining alias (of `render_from_material`);
|
||||
`render_execute`, `render_environment` and `render_fit_product` now have their own
|
||||
masters in `design/svg/model/`. Accepted source masters stay unchanged.
|
||||
|
||||
Review with `python3 -B assets/icons/ui/design/tools/icon_status.py --serve`,
|
||||
search `render_`, and select Open. See [STYLE_GUIDE.md](STYLE_GUIDE.md) for
|
||||
acceptance; drafts do not imply implemented commands.
|
||||
|
||||
The finished references in each row were read and previewed in both themes
|
||||
before authoring. Draft contact sheets (96, 24 and 16 px, both themes) are local
|
||||
review evidence, not shipped assets. The icons are grouped in five families, each
|
||||
with one shared design rule.
|
||||
|
||||
## Lights
|
||||
|
||||
Rule: a slate source glyph with light passive fill, 2.55-unit outlines and a dotted
|
||||
accent stem (three dots, 4.25 diameter, 7 units apart) showing the direction.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Light area | `render_light_area` | `box`, `camera` | Slanted flat panel on the canonical axes above the dotted stem. |
|
||||
| Light spot | `render_light_spot` | `camera`, `material` | Cone with a curved rim above the dotted stem. |
|
||||
| Light point | `render_light_point` | `camera`, `material` | Round source disc with a centre dot above the dotted stem. |
|
||||
| Sun | `render_sun` | `sphere`, `camera` | Source disc of the same size with eight short radial rays instead of a stem. |
|
||||
| Light by highlight | `render_light_by_highlight` | `sphere`, `material` | Shaded sphere with a ringed highlight target; the dotted stem arrives from above. |
|
||||
| Solo light | `render_solo_light` | `fit`, `camera` | Point source and stem inside four corner focus brackets. |
|
||||
|
||||
## Looks
|
||||
|
||||
Rule: the lit look sphere (top-left highlight, bottom-right shade) plus one cue that
|
||||
says what happens to it; receiving bodies stay canonical grey or cube shapes.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Appearance | `render_appearance` | `material`, `box` | Shaded look sphere on the top corner of an accent-shaded cube: a look assigned to a body. |
|
||||
| Paint | `render_paint` | `material`, `paste` | Flat brush with an accent tip, light ferrule and slate handle. |
|
||||
| Pick up | `render_pick_up` | `material`, `copy` | Eyedropper with a grey bulb, slate crossbar and accent sampling tip. |
|
||||
| Replace all | `render_replace_all` | `copy`, `box` | Grey sample sphere with an arrow to an accent sphere, beside a second accent sphere. |
|
||||
| Copy look | `render_copy_look` | `material`, `copy` | Look sphere in a square frame, offset from a second frame outline. |
|
||||
| Remove override | `render_remove_override` | `appearance`, `box` | Alias of `render_from_material`: the custom look is crossed off. |
|
||||
| Same look | `render_same_look` | `select`, `box` | Look sphere and three cubes; the two matching cubes are ringed. |
|
||||
| From material | `render_from_material` | `material`, `extrude` | Look sphere struck through above a plain grey slab, so the material shows. |
|
||||
|
||||
## Studio
|
||||
|
||||
Rule: the standard grey cube stands in the set-up it is lit or placed in.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Library | `render_library` | `library`, `copy` | Row of three look spheres, the middle one accent, on a shelf bar. |
|
||||
| Lighting presets | `render_lighting_presets` | `new`, `render_scene` | Two stacked cards, the front one holding a light bulb. |
|
||||
| Environment | `render_environment` | `environment_map`, `render_scene` | Dome with a grey cube inside. |
|
||||
| Ground | `render_ground` | `box`, `render_scene` | Grey cube on a slab with an accent contact patch. |
|
||||
| Backdrop | `render_backdrop` | `render_scene`, `render_image` | Grey cube in front of a tall upright accent panel. |
|
||||
|
||||
## Camera and shots
|
||||
|
||||
Rule: one chunky camera body with a lower-right round badge for shot actions; the
|
||||
other camera icons are simple frames.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Shot | `render_shot` | `camera`, `new` | Camera body with a plus badge: save a shot. |
|
||||
| Update shot | `render_update_shot` | `camera`, `undo` | Camera body with a refresh badge. |
|
||||
| Render all shots | `render_all_shots` | `copy`, `render_image` | Stacked camera bodies with a play badge. |
|
||||
| Shots strip | `render_shots_strip` | `camera`, `copy` | Strip of three small frames, the middle one in accent. |
|
||||
| Fit product | `render_fit_product` | `fit`, `camera` | Grey cube inside four corner brackets. |
|
||||
| Lens / field of view | `render_lens` | `camera`, `fit` | Concentric lens rings around an accent centre. |
|
||||
| Exposure | `render_exposure` | `material`, `environment_map` | Disc split light and accent, with plus and minus marks. |
|
||||
| Aspect frame | `render_aspect_frame` | `render_image`, `fit` | Wide rounded frame with two opposite corner brackets. |
|
||||
| Frame guides | `render_frame_guides` | `render_image`, `fit` | Rounded frame with a rule-of-thirds grid and four accent nodes. |
|
||||
|
||||
## Output and result
|
||||
|
||||
Rule: one picture base (frame with landscape) with a lower-right round badge; the
|
||||
size, background and quality icons draw their own simple subject.
|
||||
|
||||
| Meaning | Id | Finished references | Drawing |
|
||||
|---|---|---|---|
|
||||
| Render image | `render_execute` | `render_image`, `camera` | Picture with a play badge: create a rendered image. |
|
||||
| Render output size | `render_output_size` | `render_image`, `fit` | Picture frame with a diagonal double-headed dimension arrow. |
|
||||
| Transparent background | `render_transparent_background` | `material`, `render_image` | Grey cube on a checker tile. |
|
||||
| Quality | `render_quality` | `list`, `library` | Three rising bars, the tallest in accent. |
|
||||
| Check render | `render_check` | `render_image`, `new` | Picture with a check badge. |
|
||||
| Cancel render | `render_cancel` | `render_image`, `delete` | Picture with a square stop badge. |
|
||||
| Export image | `render_export_image` | `render_image`, `copy` | Picture with a large outward diagonal arrow at the upper right. |
|
||||
| Compare | `render_compare` | `render_image`, `material` | Two grey pictures split by a vertical slider with opposed arrows. |
|
||||
| Results | `render_results` | `render_image`, `copy` | Stack of two pictures, the front one showing a landscape. |
|
||||
@@ -45,3 +45,7 @@ sketch_chamfer_equal = { master = "sketch_chamfer", accent = "sketch" }
|
||||
sketch_chamfer_two = { master = "sketch_chamfer_two_distances", accent = "sketch" }
|
||||
offset_one_side = { master = "offset", accent = "sketch" }
|
||||
bom = "list"
|
||||
|
||||
# Render workspace drafts: shared meanings; owner review remains Open.
|
||||
# Removing a look override returns the body to its material look.
|
||||
render_remove_override = "render_from_material"
|
||||
|
||||
@@ -101,6 +101,7 @@
|
||||
"error": "b7188fcdfadf1512ddd2896805a14618179789f87047b219560a7dab74bd360f",
|
||||
"explode": "2aecb05d5f05aff1ab629066cb9bea8718ea213ac70948ef71a3ced3321f4cb2",
|
||||
"export": "c8c2abf007e43e896479d78798e3976ba9eda809507c820bb38538f77db696ac",
|
||||
"export_tray": "f18606e1f63726c57a0787dced4fb8a221b4a4fc18b88b0bbdee743e546cb489",
|
||||
"extend": "28c63df352c77d02feffe44cf09fb0fc1d48f7905aaabbfa6dba601dbb70f8e8",
|
||||
"extrude": "9cc1538971c5d25467aaed23f739899d9d102fd07f86472ac3197c54ca2e1144",
|
||||
"eye": "2449167c8410826dc1106b687c12a309d5bea8f7817b897943e80f182a163c2d",
|
||||
@@ -122,6 +123,7 @@
|
||||
"hole": "f82d47751f826e08682db847a270897474661bfe9f35b6d3678f49a203172563",
|
||||
"home": "ab5f1c41d377687e4c5a808dfdccf2757b33dd43f934d44df43d6e9118176853",
|
||||
"import": "e343540eb49fdb1e53c4d54b9e60ae513b3103df6c9133048bed878e58067455",
|
||||
"import_tray": "c6aec9556dc8ecb6c953d6bf1f384f220c68167a92e444950c24ded81f66a0b6",
|
||||
"include": "f5b80ad3e06e3b863fcfbe415ec912e4e09653dd519f9ac207819339fe34730f",
|
||||
"infinite": "53fb781131078d2f8e71be23aa610d9f9d9c8bb79ba5f97fa397475e9ff8c3bd",
|
||||
"inspect": "d2b4644b2d5f1aa50cb576b9265e275fa91201e296c2bc79865f810067d538e9",
|
||||
@@ -206,8 +208,45 @@
|
||||
"redo": "28e925493fa9e5f52e2c8e3beeb288af13fdb4f85f3cd92270096a10561e7a1a",
|
||||
"refold": "1fa6a8604af7d04c01bc31d0e5875eb2cc91f5fa6bfc5491dd6680d3299eb26f",
|
||||
"rename": "6887c5467101dca1d213154020d3b03be9b2704ac310de9d0a8ebde62c013bf5",
|
||||
"render_all_shots": "9c04f49624cc2685472041cb3f3e95da2c5f32deeebb9723f4a8e3c369680a5b",
|
||||
"render_appearance": "53287b30915a266d8a3858d5bef24b93ed11f8d612c20c445d3032f400a925cc",
|
||||
"render_aspect_frame": "5d74b263eeb03eb0494b3774817135b8f119caff205a8b31c480d7dd0e5a8554",
|
||||
"render_backdrop": "d27f7d6da7a31c5944dd0ff67c21838690f000f2d87b0bf5274bef0744de34ce",
|
||||
"render_cancel": "2f1d8b3039007ccfc224390758e05d6202fa2eabf63724c744217dcab5d58e5e",
|
||||
"render_check": "65ccf0407d80937c6931cbf87b6d0e628cfb62ca3e841483d02dd9de19c3717c",
|
||||
"render_compare": "1b2d3f3920782490b96b4d14a7cc9506eff98ff2efae00f86d911fabcabc7cb0",
|
||||
"render_copy_look": "25e9138a839a7e6b8225b48cd2f36daba42a928eff541fe616f3df39a1bc82b7",
|
||||
"render_environment": "50cb18380d9310f6e04084c24ac85529599966b1d90c6bef514f4e63f9b30854",
|
||||
"render_execute": "e4d9a66f4569e00c4b4b2ac05a80a9f3eba4e59802c62d3af5bb951ba9153cbf",
|
||||
"render_export_image": "2456f55fa3b50ab84ae871963d4a6b6e92f486cf6ed3039f3a2f07f11192a655",
|
||||
"render_exposure": "60eb5fa040aefe872ab2de66099493513a494ab758a23d443753d033eaf3a05d",
|
||||
"render_fit_product": "8e58b293a39a06fca4cda0543adc2afef3d0041fe34b0bc0c800afb2d5635123",
|
||||
"render_frame_guides": "cb1df01051de42047e38de9dd2a96fcb5b67bd5979cb5756412890400b69e5bc",
|
||||
"render_from_material": "c928daa803a28f90ce24096731b8de711c6cd3215259ac9cd761b6be2e1b9618",
|
||||
"render_ground": "2b900b433870cbc4bd0282e54c604ec696aa28354c753a13b09806f5e87d99c3",
|
||||
"render_image": "e3dd622591de4fdc211f9bd3c6dcb54b01a308e8bcebd02bb80dd6b3982ce2a5",
|
||||
"render_lens": "79bb615d46a05ea5ffc9e2e905cc25f266b3b7e761c160f7d25bbdb3dea0a430",
|
||||
"render_library": "828af74034a62616c774ab0cbe70b7cf9fd42778b574febfb1812f8aa17fd49b",
|
||||
"render_light_area": "b1f3ee596ee1d35f6e8444b54e94f36a34c2129697c88e5286dbf38565f50816",
|
||||
"render_light_by_highlight": "5b37c8c6aa27ac2002dd8c8094e415c64e9eb315a55c4f146e328314e8f639a2",
|
||||
"render_light_point": "a602cb5fa26f04f1bb9be7c14e5a9a9ce52b37550dfd3211a55e915f941f5803",
|
||||
"render_light_spot": "a9413de906266bec0165b4cb06ecac8e36dce0b6ddec4bc4cc6ff4f152601483",
|
||||
"render_lighting_presets": "e0a08433f548a9dd139296107b04b1d4c57ff04303c9bee32d896e2ea06a8d07",
|
||||
"render_output_size": "f08fe354e4d5a6dd21af29cb077ceed96c413c68f48d9f72548ae1614337f88b",
|
||||
"render_paint": "e3d3202205a5a90906348cae519340d38f475e42039a7f60c835cf7a21081da1",
|
||||
"render_pick_up": "df67d10f333a3c9e367897d599a4be510f10ed36c24763c26817f22d24ff35b7",
|
||||
"render_quality": "28fe44cdd2b2f58b91d91dcf20a6dfbd22cf3db6008e1db179717523ecc4889e",
|
||||
"render_remove_override": "c928daa803a28f90ce24096731b8de711c6cd3215259ac9cd761b6be2e1b9618",
|
||||
"render_replace_all": "3bbf830619a91be186b9dc91726b8e47c293556d2ac8c93d87c71014c9f3abcd",
|
||||
"render_results": "94e78898d88eef415e56ca5ec1e056089a2a5aa709abc69e5b8103d6096a019c",
|
||||
"render_same_look": "2b5c69c523a1c949c22638c378803e77123095fddd36cfa58e94b2fd91ca61d7",
|
||||
"render_scene": "1c4d7350f6f17ad637a7d633cb65a64c298af20321b1dbdac01b51d47a0e2822",
|
||||
"render_shot": "4428080faa94f276c8a1d20eb38d04835ee5ae9d6337402371a5edf09f97ddc5",
|
||||
"render_shots_strip": "1dcf5cc8e704691985284e0ca06e03b22540ea9d4cd23f4fb8675801ae7e69ec",
|
||||
"render_solo_light": "abb9f8993be108a79d491bfaca32cde59d07a69db3ead58195e8ca0d8e8d909b",
|
||||
"render_sun": "d86b51f3d4d83df4e932a22bb20d567513529a2eaaa42b93f744412eed2be901",
|
||||
"render_transparent_background": "71dab4763516053734d4eec964b0899716d617597fe0d09e3bfa0cc36224dd7a",
|
||||
"render_update_shot": "22d82ad332f8325395596c4bec4c1ec9bbb72d9fc4cd62ad658a93d7f5f803b9",
|
||||
"reorder": "c45755c8f23575415397bffb316eed9f2d8c014f451ab6f8c3123aca1a99b675",
|
||||
"replace_face": "fcee556c684a1138a30a2574ea565c98ff29f9368c2e861d0fb861155a57d28b",
|
||||
"rest": "eab64961b5340bdbbd92affe0c9b8477dc297a3b49e05e39d5fa71c0f2fda66c",
|
||||
@@ -224,10 +263,17 @@
|
||||
"screw": "5e30570785360d6a591ea22862a82f1cf62228385c1e96382536db8a453ac791",
|
||||
"section": "cdd13f99f15f6d59bfa21c747810608d6afd00cb8207faf0337928c8521e7eae",
|
||||
"select": "fdf6431270006ca7409c60c37cfed59572de04229f104ec723e6010ffa2b7163",
|
||||
"send": "f83b6cdab792f9567a81d46d7828ed20795513c57a2c7d00fbbe6bd14d2f5848",
|
||||
"settings": "9193dd3a1007c43660d0d47b6499cb2571338b67949e1a99df112057b9bf2b8a",
|
||||
"settings_keyboard": "4968c70788094365af9c6e2b1c15b2aac45cf21212a14b8a256daf1ffe17f41a",
|
||||
"settings_monitor": "6fcea26e2fc1673c76c6e7fadac4a91812ed43250cfb363c4f40c0cacfbd7aa5",
|
||||
"settings_mouse": "47754752ea6fb37ec9686c0460740935a4db30fb267f1136a9246aec9a17b3f1",
|
||||
"settings_palette": "a060015a750ff4367c65c04b83bf43720e13a38d8dee35330e1d8880f89eaa22",
|
||||
"settings_plug": "79c00b84b2d1d28e3bf6096639c75476b7e1ee0a326c34ad76a79ad866c0102e",
|
||||
"sheet": "774e30923ef6f37dd9c0b2bc3e45705e83b3a76700bf071cd41155a365d0daa4",
|
||||
"sheet_rules": "5d3a2f37d3b4a4262b3ddbdae53d61f29e0f8e284dd54006da25a7acebfa1114",
|
||||
"shell": "a1959ea0d0441a479bf654b6e549e2e44a0bfdc9cda4eb46fe1fbfd884de7e38",
|
||||
"shield": "5e2d2d157309e0daa72f7c53073dfdb0a474def972a041cd172567f1d1e485ec",
|
||||
"silhouette": "2a848aed2f247b1c0439f830a34a513c93bc5493c40044c821d493d863ad996e",
|
||||
"sketch": "b52e5862b1709a9a7e3ca00d7436d126fbfc01b38080006fa4e17dc581dfa103",
|
||||
"sketch_break": "67a5fab332616e6d6a9aeae3bedc04ee0768179143ba34b795bdd98cea04bb24",
|
||||
@@ -261,6 +307,7 @@
|
||||
"status_error": "b7188fcdfadf1512ddd2896805a14618179789f87047b219560a7dab74bd360f",
|
||||
"status_warning": "1ac4f0ccfac2c01f6f66cb42188f9ae053c4aef9c5bbd249f1fb09611eb4d825",
|
||||
"stitch": "6c3205d64007b771ed78d6f8055867578439fb25d591563b32dbd3343c98732d",
|
||||
"stop": "36967eb4d423370d0d6c679b0a71f8d09f4fb2c73eed3e8cc50c604c44b0c32b",
|
||||
"style": "6c2b04234b82ea4858c80190cc7208c60f549358253005afc35ab4e29506b578",
|
||||
"suppress": "c54fbc01c12c0175d78cd63f692e8aaf73fc2cac79752b2a3ad821ac32734343",
|
||||
"surface": "ba0e839ce0ee03e7ff95a2389a5ecb20bd80c713064d9b1bd5f461f4a80d1921",
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render all shots — every saved camera view at once</title>
|
||||
<path d="M27.1 12L29.26 12L31.42 6.96Q31.85 5.52 33.22 5.52L36.82 5.52Q38.19 5.52 38.62 6.96L40.78 12L51.58 12Q55.18 12 55.18 15.6L55.18 31.44Q55.18 35.04 51.58 35.04L27.1 35.04Q23.5 35.04 23.5 31.44L23.5 15.6Q23.5 12 27.1 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M16.6 21L18.76 21L20.92 15.96Q21.35 14.52 22.72 14.52L26.32 14.52Q27.69 14.52 28.12 15.96L30.28 21L41.08 21Q44.68 21 44.68 24.6L44.68 40.44Q44.68 44.04 41.08 44.04L16.6 44.04Q13 44.04 13 40.44L13 24.6Q13 21 16.6 21Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6.1 30L8.26 30L10.42 24.96Q10.85 23.52 12.22 23.52L15.82 23.52Q17.19 23.52 17.62 24.96L19.78 30L30.58 30Q34.18 30 34.18 33.6L34.18 49.44Q34.18 53.04 30.58 53.04L6.1 53.04Q2.5 53.04 2.5 49.44L2.5 33.6Q2.5 30 6.1 30Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="18.34" cy="41.52" r="7.56" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M43.9 41.7L53.3 47.5L43.9 53.3Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,14 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Appearance — a body wearing the look sphere</title>
|
||||
<path d="M17.33 24.8L38.98 37.3L38.98 62.3L17.33 49.8Z" fill="#328bd3"/>
|
||||
<path d="M60.63 24.8L38.98 37.3L38.98 62.3L60.63 49.8Z" fill="#236ead"/>
|
||||
<path d="M38.98 12.3L60.63 24.8L38.98 37.3L17.33 24.8Z" fill="#b9ddf8"/>
|
||||
<path d="M38.98 12.3L60.63 24.8L60.63 49.8L38.98 62.3L17.33 49.8L17.33 24.8Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M17.33 24.8L38.98 37.3L60.63 24.8" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M38.98 37.3L38.98 62.3" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="18.98" cy="17.3" r="16.88" fill="#ffffff"/>
|
||||
<circle cx="18.98" cy="17.3" r="14" fill="#328bd3"/>
|
||||
<path d="M32.14 12.51A14 14 0 0 1 14.19 30.46A18.31 18.31 0 0 0 32.14 12.51Z" fill="#236ead"/>
|
||||
<circle cx="14.22" cy="12.54" r="3.78" fill="#b9ddf8"/>
|
||||
<circle cx="18.98" cy="17.3" r="14" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Aspect frame — the shape of the picture</title>
|
||||
<path d="M13 17L51 17A4 4 0 0 1 55 21L55 43A4 4 0 0 1 51 47L13 47A4 4 0 0 1 9 43L9 21A4 4 0 0 1 13 17Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M4.5 23.5L4.5 12.5L15.5 12.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M59.5 40.5L59.5 51.5L48.5 51.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 614 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Backdrop — the coloured wall behind the product</title>
|
||||
<path d="M30.69 4L54.25 17.6L54.25 41.6L30.69 28Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L30 48.4L18.91 42Z" fill="#b8c3cf"/>
|
||||
<path d="M41.09 29.2L30 35.6L30 48.4L41.09 42Z" fill="#8e9faf"/>
|
||||
<path d="M30 22.8L41.09 29.2L30 35.6L18.91 29.2Z" fill="#e2e8ef"/>
|
||||
<path d="M30 22.8L41.09 29.2L41.09 42L30 48.4L18.91 42L18.91 29.2Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L41.09 29.2" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L30 48.4" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 928 B |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Cancel render — stop the running render</title>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 40.8L17.8 27.12L30.68 38.64L38.04 32.88L50 42.96L50 48L4 48Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<circle cx="36.2" cy="22.8" r="4.2" fill="#8e9faf"/>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M44.1 42.9L50.9 42.9A1.2 1.2 0 0 1 52.1 44.1L52.1 50.9A1.2 1.2 0 0 1 50.9 52.1L44.1 52.1A1.2 1.2 0 0 1 42.9 50.9L42.9 44.1A1.2 1.2 0 0 1 44.1 42.9Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Check render — verify the request is ready, without rendering</title>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 40.8L17.8 27.12L30.68 38.64L38.04 32.88L50 42.96L50 48L4 48Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<circle cx="36.2" cy="22.8" r="4.2" fill="#8e9faf"/>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M41.9 47.7L45.9 51.9L53.3 43.1" fill="none" stroke="#ffffff" stroke-width="3.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1,19 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Compare — live preview and render side by side</title>
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="cmpL"><path d="M4 12L32 12L32 52L4 52Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#cmpL)">
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="#b8c3cf"/>
|
||||
</g>
|
||||
<defs><clipPath id="cmp"><path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#cmp)">
|
||||
<path d="M4 44L20.8 28.8L36.48 41.6L45.44 35.2L60 46.4L60 52L4 52Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 5L32 59" fill="none" stroke="#ffffff" stroke-width="8.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="14.5" fill="#ffffff"/>
|
||||
<path d="M32 5L32 59" fill="none" stroke="#236ead" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="12.8" fill="#236ead"/>
|
||||
<path d="M23.4 32L29.8 25.6L29.8 38.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M40.6 32L34.2 25.6L34.2 38.4Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Copy Look — the look in front of a copy outline</title>
|
||||
<path d="M8 40L8 12.5A4.5 4.5 0 0 1 12.5 8L40 8" fill="none" stroke="currentColor" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M24 19L51 19A5 5 0 0 1 56 24L56 51A5 5 0 0 1 51 56L24 56A5 5 0 0 1 19 51L19 24A5 5 0 0 1 24 19Z" fill="none" stroke="currentColor" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="37.5" cy="37.5" r="12" fill="#328bd3"/>
|
||||
<path d="M48.78 33.4A12 12 0 0 1 33.4 48.78A15.69 15.69 0 0 0 48.78 33.4Z" fill="#236ead"/>
|
||||
<circle cx="33.42" cy="33.42" r="3.24" fill="#b9ddf8"/>
|
||||
<circle cx="37.5" cy="37.5" r="12" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 841 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Environment — a sky dome lighting the product</title>
|
||||
<path d="M4.04 42A25.96 25.96 0 0 1 55.96 42A25.96 14.99 0 0 1 4.04 42Z" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L30 48.4L18.91 42Z" fill="#b8c3cf"/>
|
||||
<path d="M41.09 29.2L30 35.6L30 48.4L41.09 42Z" fill="#8e9faf"/>
|
||||
<path d="M30 22.8L41.09 29.2L30 35.6L18.91 29.2Z" fill="#e2e8ef"/>
|
||||
<path d="M30 22.8L41.09 29.2L41.09 42L30 48.4L18.91 42L18.91 29.2Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L41.09 29.2" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L30 48.4" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 948 B |
@@ -0,0 +1,12 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render image — start rendering the picture</title>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 40.8L17.8 27.12L30.68 38.64L38.04 32.88L50 42.96L50 48L4 48Z" fill="#328bd3"/>
|
||||
</g>
|
||||
<path d="M9 12L45 12A5 5 0 0 1 50 17L50 43A5 5 0 0 1 45 48L9 48A5 5 0 0 1 4 43L4 17A5 5 0 0 1 9 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M43.9 41.7L53.3 47.5L43.9 53.3Z" fill="#ffffff" stroke="#ffffff" stroke-width="1.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 993 B |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Export image — save the finished picture as a PNG file</title>
|
||||
<path d="M9 18L43 18A5 5 0 0 1 48 23L48 49A5 5 0 0 1 43 54L9 54A5 5 0 0 1 4 49L4 23A5 5 0 0 1 9 18Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="pic1"><path d="M9 18L43 18A5 5 0 0 1 48 23L48 49A5 5 0 0 1 43 54L9 54A5 5 0 0 1 4 49L4 23A5 5 0 0 1 9 18Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#pic1)">
|
||||
<path d="M4 46.8L17.2 33.12L29.52 44.64L36.56 38.88L48 48.96L48 54L4 54Z" fill="#8e9faf"/>
|
||||
</g>
|
||||
<path d="M9 18L43 18A5 5 0 0 1 48 23L48 49A5 5 0 0 1 43 54L9 54A5 5 0 0 1 4 49L4 23A5 5 0 0 1 9 18Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28 36L47.77 17.55" fill="none" stroke="#ffffff" stroke-width="11.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58 8L53.52 25.18L40.55 11.29Z" fill="#ffffff" stroke="#ffffff" stroke-width="6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28 36L47.77 17.55" fill="none" stroke="#236ead" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58 8L53.52 25.18L40.55 11.29Z" fill="#236ead" stroke="#236ead" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Exposure — brighten or darken the picture</title>
|
||||
<circle cx="32" cy="32" r="25" fill="#e2e8ef"/>
|
||||
<path d="M32 7A25 25 0 0 1 32 57Z" fill="#236ead"/>
|
||||
<circle cx="32" cy="32" r="25" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M14 32L27 32M20.5 25.5L20.5 38.5" fill="none" stroke="#34465b" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37 32L50 32" fill="none" stroke="#ffffff" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 630 B |
@@ -0,0 +1,13 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Fit product — frame the whole product in the view</title>
|
||||
<path d="M15.98 22.75L32 32L32 50.5L15.98 41.25Z" fill="#b8c3cf"/>
|
||||
<path d="M48.02 22.75L32 32L32 50.5L48.02 41.25Z" fill="#8e9faf"/>
|
||||
<path d="M32 13.5L48.02 22.75L32 32L15.98 22.75Z" fill="#e2e8ef"/>
|
||||
<path d="M32 13.5L48.02 22.75L48.02 41.25L32 50.5L15.98 41.25L15.98 22.75Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15.98 22.75L32 32L48.02 22.75" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L32 50.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.5 18.5L5.5 5.5L18.5 5.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58.5 18.5L58.5 5.5L45.5 5.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M58.5 45.5L58.5 58.5L45.5 58.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.5 45.5L5.5 58.5L18.5 58.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Frame guides — rule of thirds over the picture</title>
|
||||
<path d="M10 14L54 14A4 4 0 0 1 58 18L58 46A4 4 0 0 1 54 50L10 50A4 4 0 0 1 6 46L6 18A4 4 0 0 1 10 14Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M23.33 15.3L23.33 48.7M7.3 26L56.7 26M40.67 15.3L40.67 48.7M7.3 38L56.7 38" fill="none" stroke="#6f879c" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="23.33" cy="26" r="3.6" fill="#236ead"/>
|
||||
<circle cx="23.33" cy="38" r="3.6" fill="#236ead"/>
|
||||
<circle cx="40.67" cy="26" r="3.6" fill="#236ead"/>
|
||||
<circle cx="40.67" cy="38" r="3.6" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 742 B |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>From Material — custom look crossed off, material shows</title>
|
||||
<path d="M6 38L32 53.01L32 57.01L6 42Z" fill="#b8c3cf"/>
|
||||
<path d="M58 38L32 53.01L32 57.01L58 42Z" fill="#8e9faf"/>
|
||||
<path d="M32 22.99L58 38L32 53.01L6 38Z" fill="#e2e8ef"/>
|
||||
<path d="M32 22.99L58 38L58 42L32 57.01L6 42L6 38Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6 38L32 53.01L58 38" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 53.01L32 57.01" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="22" r="15" fill="#328bd3"/>
|
||||
<path d="M46.1 16.87A15 15 0 0 1 26.87 36.1A19.62 19.62 0 0 0 46.1 16.87Z" fill="#236ead"/>
|
||||
<circle cx="26.9" cy="16.9" r="4.05" fill="#b9ddf8"/>
|
||||
<circle cx="32" cy="22" r="15" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21 11L43 33M43 11L21 33" fill="none" stroke="#ffffff" stroke-width="6.65" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21 11L43 33M43 11L21 33" fill="none" stroke="currentColor" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Ground — the floor the product stands on, with its contact shadow</title>
|
||||
<path d="M5.06 42L30 56.4L30 58.8L5.06 44.4Z" fill="#328bd3"/>
|
||||
<path d="M54.94 42L30 56.4L30 58.8L54.94 44.4Z" fill="#236ead"/>
|
||||
<path d="M30 27.6L54.94 42L30 56.4L5.06 42Z" fill="#b9ddf8"/>
|
||||
<path d="M30 27.6L54.94 42L54.94 44.4L30 58.8L5.06 44.4L5.06 42Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M5.06 42L30 56.4L54.94 42" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 56.4L30 58.8" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L45.93 44.8L34.85 51.2L18.91 42Z" fill="#328bd3"/>
|
||||
<path d="M18.91 29.2L30 35.6L30 48.4L18.91 42Z" fill="#b8c3cf"/>
|
||||
<path d="M41.09 29.2L30 35.6L30 48.4L41.09 42Z" fill="#8e9faf"/>
|
||||
<path d="M30 22.8L41.09 29.2L30 35.6L18.91 29.2Z" fill="#e2e8ef"/>
|
||||
<path d="M30 22.8L41.09 29.2L41.09 42L30 48.4L18.91 42L18.91 29.2Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.91 29.2L30 35.6L41.09 29.2" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30 35.6L30 48.4" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Lens — the camera lens and its field of view</title>
|
||||
<circle cx="32" cy="32" r="27" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="19" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="32" r="8.5" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="21.75" cy="21.75" r="3" fill="#e2e8ef"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 587 B |
@@ -0,0 +1,17 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Appearance library — looks on a shelf</title>
|
||||
<circle cx="13" cy="28.35" r="9.5" fill="#b8c3cf"/>
|
||||
<path d="M21.93 25.1A9.5 9.5 0 0 1 9.75 37.28A12.42 12.42 0 0 0 21.93 25.1Z" fill="#8e9faf"/>
|
||||
<circle cx="9.77" cy="25.12" r="2.57" fill="#e2e8ef"/>
|
||||
<circle cx="13" cy="28.35" r="9.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="51" cy="28.35" r="9.5" fill="#b8c3cf"/>
|
||||
<path d="M59.93 25.1A9.5 9.5 0 0 1 47.75 37.28A12.42 12.42 0 0 0 59.93 25.1Z" fill="#8e9faf"/>
|
||||
<circle cx="47.77" cy="25.12" r="2.57" fill="#e2e8ef"/>
|
||||
<circle cx="51" cy="28.35" r="9.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="28.35" r="13.97" fill="#ffffff"/>
|
||||
<circle cx="32" cy="28.35" r="11.5" fill="#328bd3"/>
|
||||
<path d="M42.81 24.42A11.5 11.5 0 0 1 28.07 39.16A15.04 15.04 0 0 0 42.81 24.42Z" fill="#236ead"/>
|
||||
<circle cx="28.09" cy="24.44" r="3.11" fill="#b9ddf8"/>
|
||||
<circle cx="32" cy="28.35" r="11.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M7 42.35L57 42.35A2 2 0 0 1 59 44.35L59 46.35A2 2 0 0 1 57 48.35L7 48.35A2 2 0 0 1 5 46.35L5 44.35A2 2 0 0 1 7 42.35Z" fill="#8e9faf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Area light — angled panel and dotted direction stem</title>
|
||||
<path d="M23.34 11L54.52 29L40.66 37L9.48 19Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 448 B |
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Light by highlight — dotted aim stem and sphere target</title>
|
||||
<circle cx="32" cy="9" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="16" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="23" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="42" r="15" fill="#e2e8ef"/>
|
||||
<path d="M44.29 33.4A15 15 0 0 1 23.4 54.29A19.62 19.62 0 0 0 44.29 33.4Z" fill="#b8c3cf"/>
|
||||
<circle cx="32" cy="42" r="15" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="37.5" r="5.5" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 712 B |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Point light — source disc and dotted direction stem</title>
|
||||
<circle cx="32" cy="23" r="15" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="23" r="2.125" fill="#34465b"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 485 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Spot light — cone source and dotted direction stem</title>
|
||||
<path d="M32 8L49 33Q32 43 15 33Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 435 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Lighting presets — a stack of ready-made light set-ups</title>
|
||||
<path d="M24 5L54 5A5 5 0 0 1 59 10L59 40A5 5 0 0 1 54 45L24 45A5 5 0 0 1 19 40L19 10A5 5 0 0 1 24 5Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10 17L42 17A5 5 0 0 1 47 22L47 54A5 5 0 0 1 42 59L10 59A5 5 0 0 1 5 54L5 22A5 5 0 0 1 10 17Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M18.3 41.55A11.5 11.5 0 1 1 33.7 41.55L33.2 45.55L18.8 45.55Z" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M21.3 45.55L30.7 45.55A2.5 2.5 0 0 1 33.2 48.05L33.2 50.05A2.5 2.5 0 0 1 30.7 52.55L21.3 52.55A2.5 2.5 0 0 1 18.8 50.05L18.8 48.05A2.5 2.5 0 0 1 21.3 45.55Z" fill="#8e9faf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1000 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Output size — the picture size in pixels</title>
|
||||
<path d="M9 12L55 12A5 5 0 0 1 60 17L60 47A5 5 0 0 1 55 52L9 52A5 5 0 0 1 4 47L4 17A5 5 0 0 1 9 12Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.08 38.79L41.92 25.21" fill="none" stroke="#236ead" stroke-width="5.6" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M51 19L45.33 31.97L36.86 19.59Z" fill="#236ead" stroke="#236ead" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M13 45L18.67 32.03L27.14 44.41Z" fill="#236ead" stroke="#236ead" stroke-width="2" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 750 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Paint — flat brush with look-coloured bristles</title>
|
||||
<path d="M26.13 59.09L22.24 55.2L22.94 48.83L16.58 49.54L14.46 47.42L15.17 41.06L8.8 41.76L4.91 37.87L19.06 23.73L40.27 44.94Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M22.24 20.55L28.25 14.54L37.09 16.31L46.28 7.11A7.5 7.5 0 0 1 56.89 17.72L47.69 26.91L49.46 35.75L43.45 41.76Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 589 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Pick Up — eyedropper with a look-coloured tube</title>
|
||||
<path d="M8.96 52.22Q15.32 44.44 14.97 37.01L28.4 23.58L40.42 35.6L26.99 49.03Q19.56 48.68 11.78 55.04Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M30.52 20.04L39.01 11.56A9.5 9.5 0 0 1 52.44 24.99L43.96 33.48Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M25.22 18.98L29.46 14.74L49.26 34.54L45.02 38.78Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 682 B |
@@ -0,0 +1,6 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render quality — draft, balanced or high</title>
|
||||
<path d="M10 38L17 38A3.5 3.5 0 0 1 20.5 41.5L20.5 52.5A3.5 3.5 0 0 1 17 56L10 56A3.5 3.5 0 0 1 6.5 52.5L6.5 41.5A3.5 3.5 0 0 1 10 38Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M28.5 24L35.5 24A3.5 3.5 0 0 1 39 27.5L39 52.5A3.5 3.5 0 0 1 35.5 56L28.5 56A3.5 3.5 0 0 1 25 52.5L25 27.5A3.5 3.5 0 0 1 28.5 24Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M47 8L54 8A3.5 3.5 0 0 1 57.5 11.5L57.5 52.5A3.5 3.5 0 0 1 54 56L47 56A3.5 3.5 0 0 1 43.5 52.5L43.5 11.5A3.5 3.5 0 0 1 47 8Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 848 B |
@@ -0,0 +1,18 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Replace All — every use of a look switched to another</title>
|
||||
<circle cx="12.5" cy="13.3" r="10" fill="#b8c3cf"/>
|
||||
<path d="M21.9 9.88A10 10 0 0 1 9.08 22.7A13.08 13.08 0 0 0 21.9 9.88Z" fill="#8e9faf"/>
|
||||
<circle cx="9.1" cy="9.9" r="2.7" fill="#e2e8ef"/>
|
||||
<circle cx="12.5" cy="13.3" r="10" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M36 36.8L25.39 34.68L33.88 26.19Z" fill="#236ead"/>
|
||||
<path d="M21 21.8L30.34 31.14" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="52.5" cy="35.3" r="9" fill="#328bd3"/>
|
||||
<path d="M60.96 32.22A9 9 0 0 1 49.42 43.76A11.77 11.77 0 0 0 60.96 32.22Z" fill="#236ead"/>
|
||||
<circle cx="49.44" cy="32.24" r="2.43" fill="#b9ddf8"/>
|
||||
<circle cx="52.5" cy="35.3" r="9" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="46.5" cy="47.3" r="14.68" fill="#ffffff"/>
|
||||
<circle cx="46.5" cy="47.3" r="12" fill="#328bd3"/>
|
||||
<path d="M57.78 43.2A12 12 0 0 1 42.4 58.58A15.69 15.69 0 0 0 57.78 43.2Z" fill="#236ead"/>
|
||||
<circle cx="42.42" cy="43.22" r="3.24" fill="#b9ddf8"/>
|
||||
<circle cx="46.5" cy="47.3" r="12" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,11 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Render results — open the finished renders</title>
|
||||
<path d="M21 6L55 6A5 5 0 0 1 60 11L60 35A5 5 0 0 1 55 40L21 40A5 5 0 0 1 16 35L16 11A5 5 0 0 1 21 6Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M9 17.5L43 17.5A7.5 7.5 0 0 1 50.5 25L50.5 51A7.5 7.5 0 0 1 43 58.5L9 58.5A7.5 7.5 0 0 1 1.5 51L1.5 25A7.5 7.5 0 0 1 9 17.5Z" fill="#ffffff"/>
|
||||
<path d="M9 20L43 20A5 5 0 0 1 48 25L48 51A5 5 0 0 1 43 56L9 56A5 5 0 0 1 4 51L4 25A5 5 0 0 1 9 20Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="res"><path d="M9 20L43 20A5 5 0 0 1 48 25L48 51A5 5 0 0 1 43 56L9 56A5 5 0 0 1 4 51L4 25A5 5 0 0 1 9 20Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#res)">
|
||||
<path d="M4 48.8L17.2 35.12L29.52 46.64L36.56 40.88L48 50.96L48 56L4 56Z" fill="#328bd3"/>
|
||||
</g>
|
||||
<path d="M9 20L43 20A5 5 0 0 1 48 25L48 51A5 5 0 0 1 43 56L9 56A5 5 0 0 1 4 51L4 25A5 5 0 0 1 9 20Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,27 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Same Look — the bodies wearing this look selected</title>
|
||||
<path d="M6.3 41.5L15.83 47L15.83 58L6.3 52.5Z" fill="#b8c3cf"/>
|
||||
<path d="M25.36 41.5L15.83 47L15.83 58L25.36 52.5Z" fill="#8e9faf"/>
|
||||
<path d="M15.83 36L25.36 41.5L15.83 47L6.3 41.5Z" fill="#e2e8ef"/>
|
||||
<path d="M15.83 36L25.36 41.5L25.36 52.5L15.83 58L6.3 52.5L6.3 41.5Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M6.3 41.5L15.83 47L25.36 41.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M15.83 47L15.83 58" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 12L45.83 17L45.83 27L37.17 22Z" fill="#328bd3"/>
|
||||
<path d="M54.48 12L45.83 17L45.83 27L54.48 22Z" fill="#236ead"/>
|
||||
<path d="M45.83 7L54.48 12L45.83 17L37.17 12Z" fill="#b9ddf8"/>
|
||||
<path d="M45.83 7L54.48 12L54.48 22L45.83 27L37.17 22L37.17 12Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 12L45.83 17L54.48 12" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M45.83 17L45.83 27" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.33 4L54.33 4A4.5 4.5 0 0 1 58.83 8.5L58.83 25.5A4.5 4.5 0 0 1 54.33 30L37.33 30A4.5 4.5 0 0 1 32.83 25.5L32.83 8.5A4.5 4.5 0 0 1 37.33 4Z" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 42L45.83 47L45.83 57L37.17 52Z" fill="#328bd3"/>
|
||||
<path d="M54.48 42L45.83 47L45.83 57L54.48 52Z" fill="#236ead"/>
|
||||
<path d="M45.83 37L54.48 42L45.83 47L37.17 42Z" fill="#b9ddf8"/>
|
||||
<path d="M45.83 37L54.48 42L54.48 52L45.83 57L37.17 52L37.17 42Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.17 42L45.83 47L54.48 42" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M45.83 47L45.83 57" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M37.33 34L54.33 34A4.5 4.5 0 0 1 58.83 38.5L58.83 55.5A4.5 4.5 0 0 1 54.33 60L37.33 60A4.5 4.5 0 0 1 32.83 55.5L32.83 38.5A4.5 4.5 0 0 1 37.33 34Z" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="15.83" cy="17" r="11.5" fill="#328bd3"/>
|
||||
<path d="M26.64 13.07A11.5 11.5 0 0 1 11.9 27.81A15.04 15.04 0 0 0 26.64 13.07Z" fill="#236ead"/>
|
||||
<circle cx="11.92" cy="13.09" r="3.11" fill="#b9ddf8"/>
|
||||
<circle cx="15.83" cy="17" r="11.5" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.8 KiB |
@@ -0,0 +1,8 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Shot — save the camera view as a named shot</title>
|
||||
<path d="M9 19L12 19L15 12Q15.6 10 17.5 10L22.5 10Q24.4 10 25 12L28 19L43 19Q48 19 48 24L48 46Q48 51 43 51L9 51Q4 51 4 46L4 24Q4 19 9 19Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="26" cy="35" r="10.5" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M42 47.5L53 47.5M47.5 42L47.5 53" fill="none" stroke="#ffffff" stroke-width="3.4" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 759 B |
@@ -0,0 +1,7 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Shots strip — show or hide the strip of shot thumbnails</title>
|
||||
<path d="M8 19L56 19A5 5 0 0 1 61 24L61 40A5 5 0 0 1 56 45L8 45A5 5 0 0 1 3 40L3 24A5 5 0 0 1 8 19Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M10.5 24.5L19.5 24.5A2.5 2.5 0 0 1 22 27L22 37A2.5 2.5 0 0 1 19.5 39.5L10.5 39.5A2.5 2.5 0 0 1 8 37L8 27A2.5 2.5 0 0 1 10.5 24.5Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M27.5 24.5L36.5 24.5A2.5 2.5 0 0 1 39 27L39 37A2.5 2.5 0 0 1 36.5 39.5L27.5 39.5A2.5 2.5 0 0 1 25 37L25 27A2.5 2.5 0 0 1 27.5 24.5Z" fill="#328bd3" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M44.5 24.5L53.5 24.5A2.5 2.5 0 0 1 56 27L56 37A2.5 2.5 0 0 1 53.5 39.5L44.5 39.5A2.5 2.5 0 0 1 42 37L42 27A2.5 2.5 0 0 1 44.5 24.5Z" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Solo light — point source and dotted stem inside focus brackets</title>
|
||||
<path d="M13.5 5L5.5 5L5.5 13M50.5 5L58.5 5L58.5 13M58.5 51.5L58.5 59.5L50.5 59.5M13.5 59.5L5.5 59.5L5.5 51.5" fill="none" stroke="currentColor" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="23" r="15" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="32" cy="23" r="2.125" fill="#34465b"/>
|
||||
<circle cx="32" cy="44" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="51" r="2.125" fill="#236ead"/>
|
||||
<circle cx="32" cy="58" r="2.125" fill="#236ead"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 713 B |
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Sun — source disc with short radial rays</title>
|
||||
<circle cx="32" cy="32" r="15" fill="#e2e8ef" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 5.5L32 10.5M32 53.5L32 58.5M5.5 32L10.5 32M53.5 32L58.5 32M13.26 13.26L16.8 16.8M47.2 47.2L50.74 50.74M13.26 50.74L16.8 47.2M47.2 16.8L50.74 13.26" fill="none" stroke="#236ead" stroke-width="4.25" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 523 B |
@@ -0,0 +1,16 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Transparent background — render with an alpha background</title>
|
||||
<path d="M11 6L53 6A5 5 0 0 1 58 11L58 53A5 5 0 0 1 53 58L11 58A5 5 0 0 1 6 53L6 11A5 5 0 0 1 11 6Z" fill="#e2e8ef"/>
|
||||
<defs><clipPath id="chk"><path d="M11 6L53 6A5 5 0 0 1 58 11L58 53A5 5 0 0 1 53 58L11 58A5 5 0 0 1 6 53L6 11A5 5 0 0 1 11 6Z" fill="none"/></clipPath></defs>
|
||||
<g clip-path="url(#chk)">
|
||||
<path d="M6 19L19 19L19 32L6 32ZM6 45L19 45L19 58L6 58ZM19 6L32 6L32 19L19 19ZM19 32L32 32L32 45L19 45ZM32 19L45 19L45 32L32 32ZM32 45L45 45L45 58L32 58ZM45 6L58 6L58 19L45 19ZM45 32L58 32L58 45L45 45Z" fill="#b9ddf8"/>
|
||||
</g>
|
||||
<path d="M11 6L53 6A5 5 0 0 1 58 11L58 53A5 5 0 0 1 53 58L11 58A5 5 0 0 1 6 53L6 11A5 5 0 0 1 11 6Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 18L44.12 25L44.12 39L32 46L19.88 39L19.88 25Z" fill="#ffffff" stroke="#ffffff" stroke-width="7.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M19.88 25L32 32L32 46L19.88 39Z" fill="#b8c3cf"/>
|
||||
<path d="M44.12 25L32 32L32 46L44.12 39Z" fill="#8e9faf"/>
|
||||
<path d="M32 18L44.12 25L32 32L19.88 25Z" fill="#e2e8ef"/>
|
||||
<path d="M32 18L44.12 25L44.12 39L32 46L19.88 39L19.88 25Z" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M19.88 25L32 32L44.12 25" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M32 32L32 46" fill="none" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.6 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<title>Update shot — overwrite the active shot with the current camera</title>
|
||||
<path d="M9 19L12 19L15 12Q15.6 10 17.5 10L22.5 10Q24.4 10 25 12L28 19L43 19Q48 19 48 24L48 46Q48 51 43 51L9 51Q4 51 4 46L4 24Q4 19 9 19Z" fill="#b8c3cf" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="26" cy="35" r="10.5" fill="#b9ddf8" stroke="#34465b" stroke-width="2.55" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<circle cx="47.5" cy="47.5" r="14" fill="#ffffff"/>
|
||||
<circle cx="47.5" cy="47.5" r="12" fill="#236ead"/>
|
||||
<path d="M50.97 43.36A5.4 5.4 0 1 1 43.08 44.4" fill="none" stroke="#ffffff" stroke-width="3" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
<path d="M46.06 40.14L39.8 42.11L46.35 46.7Z" fill="#ffffff" stroke="#ffffff" stroke-width="0.8" stroke-linejoin="round" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 930 B |
@@ -1478,6 +1478,8 @@
|
||||
"prefs.graphics_note": "وازن بين السرعة والنعومة. كل خيار يعرض أثره على عيّنة.",
|
||||
"prefs.graphics_presets_note": "الإعدادات المسبقة تضبط الخيارات أدناه معًا.",
|
||||
"prefs.history": "السجل",
|
||||
"prefs.history_origin_bodies": "إظهار الأصل والأجسام في الأعلى",
|
||||
"prefs.history_origin_bodies_note": "يبدأ السجل على اليسار أو تحت المستعرض بمجلد الأجسام للمكوّن النشط وبالأصل، مع أيقونات الإظهار والقوائم كما في المستعرض. لا يتغير السجل في الأسفل.",
|
||||
"prefs.history_sketches": "الرسومات",
|
||||
"prefs.history_sketches_in_order": "بالترتيب",
|
||||
"prefs.history_sketches_note": "عند ضبط الرسومات على «تحت ميزتها»، يعرض السجل على اليسار كل رسم بمستوى واحد للداخل، تحت أول ميزة بُنيت منه، متى كان عريضًا بما يكفي لإظهار الأسماء. أما الخط الزمني في الأسفل فيحتفظ دائمًا بترتيب الإنشاء.",
|
||||
@@ -2254,7 +2256,10 @@
|
||||
"the parts library is not available": "مكتبة القطع غير متاحة",
|
||||
"thread.automatic_size": "حجم تلقائي",
|
||||
"thread.designation": "التسمية",
|
||||
"thread.direction": "اتجاه السن اللولبي",
|
||||
"thread.iso_metric": "متري ISO",
|
||||
"thread.left_hand": "يساري",
|
||||
"thread.right_hand": "يميني",
|
||||
"thread.select_size": "اختر الحجم",
|
||||
"timeline.delete_after_marker_one": "حذف ميزة واحدة بعد مؤشر السجل",
|
||||
"timeline.delete_after_marker_other": "حذف {count} ميزات بعد مؤشر السجل",
|
||||
|
||||
@@ -1148,6 +1148,8 @@
|
||||
"pick a hole to seat it, or OK places it at the origin": "Wählen Sie eine Bohrung zum Einsetzen, oder OK platziert es im Ursprung",
|
||||
"pick one or two vertices, edges or faces": "Wählen Sie ein oder zwei Ecken, Kanten oder Flächen",
|
||||
"prefs.cpu": "CPU (Software)",
|
||||
"prefs.history_origin_bodies": "Ursprung und Körper oben zeigen",
|
||||
"prefs.history_origin_bodies_note": "Der Verlauf links oder unter dem Browser beginnt mit dem Körper-Ordner der aktiven Komponente und dem Ursprung, mit Augen und Menüs wie im Browser. Der Verlauf unten bleibt unverändert.",
|
||||
"prefs.minutes_suffix": " min",
|
||||
"properties.title": "Eigenschaften: {name}",
|
||||
"recovery.days_ago": "vor {count} Tagen",
|
||||
|
||||
@@ -1478,6 +1478,8 @@
|
||||
"prefs.graphics_note": "Trade speed for smoothness. Each choice shows its effect on a sample.",
|
||||
"prefs.graphics_presets_note": "Presets set the choices below together.",
|
||||
"prefs.history": "History",
|
||||
"prefs.history_origin_bodies": "Show origin and bodies at the top",
|
||||
"prefs.history_origin_bodies_note": "The History on the left or under the browser starts with the active component's Bodies folder and the Origin, with their eyes and menus as in the browser. The History at the bottom is unchanged.",
|
||||
"prefs.history_sketches": "Sketches",
|
||||
"prefs.history_sketches_in_order": "In order",
|
||||
"prefs.history_sketches_note": "With Sketches set to Under their feature, the History at the left lists each sketch one level in, under the first feature built from it, whenever it is wide enough for names. The timeline at the bottom always keeps the creation order.",
|
||||
@@ -2254,7 +2256,10 @@
|
||||
"the parts library is not available": "the parts library is not available",
|
||||
"thread.automatic_size": "Automatic size",
|
||||
"thread.designation": "Designation",
|
||||
"thread.direction": "Thread direction",
|
||||
"thread.iso_metric": "ISO metric",
|
||||
"thread.left_hand": "Left-hand",
|
||||
"thread.right_hand": "Right-hand",
|
||||
"thread.select_size": "Select size",
|
||||
"timeline.delete_after_marker_one": "Delete 1 Feature After History Marker",
|
||||
"timeline.delete_after_marker_other": "Delete {count} Features After History Marker",
|
||||
|
||||
@@ -1478,6 +1478,8 @@
|
||||
"prefs.graphics_note": "איזון בין מהירות לחלקות. כל בחירה מציגה את השפעתה על דוגמה.",
|
||||
"prefs.graphics_presets_note": "הגדרות קבועות מראש קובעות יחד את הבחירות שלמטה.",
|
||||
"prefs.history": "ציר הזמן",
|
||||
"prefs.history_origin_bodies": "הצגת הראשית והגופים למעלה",
|
||||
"prefs.history_origin_bodies_note": "ההיסטוריה משמאל או מתחת לדפדפן מתחילה בתיקיית הגופים של הרכיב הפעיל ובראשית, עם סמלי העין והתפריטים כמו בדפדפן. ההיסטוריה למטה לא משתנה.",
|
||||
"prefs.history_sketches": "סקיצות",
|
||||
"prefs.history_sketches_in_order": "לפי הסדר",
|
||||
"prefs.history_sketches_note": "כשהסקיצות מוגדרות ל„מתחת לפיצ'ר שלהן”, ציר הזמן משמאל מציג כל סקיצה רמה אחת פנימה, מתחת לפיצ'ר הראשון שנבנה ממנה, בכל פעם שהוא רחב מספיק להצגת שמות. ציר הזמן בתחתית שומר תמיד על סדר היצירה.",
|
||||
@@ -2254,7 +2256,10 @@
|
||||
"the parts library is not available": "ספריית החלקים אינה זמינה",
|
||||
"thread.automatic_size": "גודל אוטומטי",
|
||||
"thread.designation": "סימון",
|
||||
"thread.direction": "כיוון ההברגה",
|
||||
"thread.iso_metric": "ISO מטרי",
|
||||
"thread.left_hand": "שמאלית",
|
||||
"thread.right_hand": "ימנית",
|
||||
"thread.select_size": "בחירת גודל",
|
||||
"timeline.delete_after_marker_one": "מחיקת פיצ'ר אחד אחרי סמן ציר הזמן",
|
||||
"timeline.delete_after_marker_other": "מחיקת {count} פיצ'רים אחרי סמן ציר הזמן",
|
||||
|
||||
@@ -1153,7 +1153,10 @@
|
||||
"the parts library is not available": "la libreria delle parti non è disponibile",
|
||||
"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.group": "Nuova cartella di lavorazioni",
|
||||
"timeline.move_many": "Sposta elementi della sequenza temporale",
|
||||
|
||||
@@ -1478,6 +1478,8 @@
|
||||
"prefs.graphics_note": "速度と滑らかさのバランスを調整します。各選択肢の効果をサンプルで確認できます。",
|
||||
"prefs.graphics_presets_note": "プリセットは以下の設定をまとめて変更します。",
|
||||
"prefs.history": "履歴",
|
||||
"prefs.history_origin_bodies": "原点とボディを先頭に表示",
|
||||
"prefs.history_origin_bodies_note": "左側またはブラウザー下の履歴の先頭に、アクティブなコンポーネントのボディフォルダーと原点を、ブラウザーと同じ表示切り替えとメニュー付きで表示します。下部の履歴は変わりません。",
|
||||
"prefs.history_sketches": "スケッチ",
|
||||
"prefs.history_sketches_in_order": "作成順",
|
||||
"prefs.history_sketches_note": "スケッチ:「フィーチャーの下」では、左側の履歴に名前が表示される幅があるとき、スケッチをそれから作成した最初のフィーチャーの下に 1 段字下げして表示します。下部のタイムラインは常に作成順です。",
|
||||
@@ -2254,7 +2256,10 @@
|
||||
"the parts library is not available": "パーツライブラリは利用できません",
|
||||
"thread.automatic_size": "サイズを自動選択",
|
||||
"thread.designation": "ねじの呼び",
|
||||
"thread.direction": "ねじの方向",
|
||||
"thread.iso_metric": "ISOメートルねじ",
|
||||
"thread.left_hand": "左ねじ",
|
||||
"thread.right_hand": "右ねじ",
|
||||
"thread.select_size": "サイズを選択",
|
||||
"timeline.delete_after_marker_one": "履歴マーカー以降の 1 個のフィーチャーを削除",
|
||||
"timeline.delete_after_marker_other": "履歴マーカー以降の {count} 個のフィーチャーを削除",
|
||||
|
||||
@@ -1242,6 +1242,8 @@
|
||||
"prefs.graphics_note": "속도와 부드러움 중에서 선택합니다. 각 선택의 효과가 샘플에 표시됩니다.",
|
||||
"prefs.graphics_presets_note": "프리셋은 아래의 선택 항목을 한 번에 설정합니다.",
|
||||
"prefs.history": "이력",
|
||||
"prefs.history_origin_bodies": "원점과 바디를 맨 위에 표시",
|
||||
"prefs.history_origin_bodies_note": "왼쪽 또는 브라우저 아래의 히스토리가 활성 구성 요소의 바디 폴더와 원점으로 시작하며, 브라우저처럼 표시 아이콘과 메뉴가 있습니다. 아래쪽 히스토리는 바뀌지 않습니다.",
|
||||
"prefs.hold_stop_snapping": "누르고 있으면 스냅 중지",
|
||||
"prefs.icons_only": "아이콘만",
|
||||
"prefs.imported_file": "가져온 파일",
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"ui.continuity_smooth_curvature_change": "Smooth curvature change",
|
||||
"ui.continuity_smooth_curvature_change_tip": "Smooth curvature change (G4): even the way the curvature changes runs on smoothly across the seam.",
|
||||
"ui.fillet_corner_tangent_tip": "Tangent (G1): a rolling-ball corner, a piece of the rolling ball's sphere tangent to each round.",
|
||||
"ui.fillet_corner_curvature_tip": "Curvature (G2): a smooth patch. The corner is set back along each edge by the Setback ({factor} × the radius unless you type one) and filled with a patch, so reflections run through it without a kink."
|
||||
},
|
||||
"ar": {
|
||||
"ui.continuity_smooth_curvature_change": "تغيّر انحناء سلس",
|
||||
"ui.continuity_smooth_curvature_change_tip": "تغيّر انحناء سلس (G4): حتى طريقة تغيّر الانحناء تستمر بسلاسة عبر خط الالتقاء.",
|
||||
"ui.fillet_corner_tangent_tip": "مماس (G1): زاوية بالكرة المتدحرجة، جزء من سطح الكرة المتدحرجة مماسّ لكل تقويس.",
|
||||
"ui.fillet_corner_curvature_tip": "الانحناء (G2): رقعة ناعمة. تُرجَع الزاوية على طول كل حافة بمقدار الإرجاع ({factor} ضعف نصف القطر ما لم تكتب قيمة) وتُملأ برقعة، فتمر الانعكاسات عبرها دون انكسار."
|
||||
},
|
||||
"de": {
|
||||
"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.fillet_corner_tangent_tip": "Tangential (G1): eine Kugelecke, ein Stück der Kugel des rollenden Balls, tangential 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."
|
||||
},
|
||||
"he": {
|
||||
"ui.continuity_smooth_curvature_change": "שינוי עקמומיות חלק",
|
||||
"ui.continuity_smooth_curvature_change_tip": "שינוי עקמומיות חלק (G4): גם האופן שבו העקמומיות משתנה ממשיך בצורה חלקה לאורך התפר.",
|
||||
"ui.fillet_corner_tangent_tip": "משיק (G1): פינת כדור מתגלגל, חלק מספירת הכדור המתגלגל, משיק לכל עיגול.",
|
||||
"ui.fillet_corner_curvature_tip": "עקמומיות (G2): טלאי חלק. הפינה נסוגה לאורך כל קצה במרחק הנסיגה (פי {factor} מהרדיוס אלא אם תקלידו ערך) ומתמלאת בטלאי, כך שההשתקפויות עוברות בה בלי שבר."
|
||||
},
|
||||
"ja": {
|
||||
"ui.continuity_smooth_curvature_change": "なめらかな曲率変化",
|
||||
"ui.continuity_smooth_curvature_change_tip": "なめらかな曲率変化 (G4): 境目で曲率の変わり方そのものもなめらかに続きます。",
|
||||
"ui.fillet_corner_tangent_tip": "接線 (G1): 転がるボールのコーナー。転がるボールの球面の一部で、各フィレットに接します。",
|
||||
"ui.fillet_corner_curvature_tip": "曲率 (G2): なめらかなパッチ。コーナーを各エッジに沿ってセットバック分 (入力しなければ半径の {factor} 倍) 後退させてパッチで埋め、映り込みが折れずに通ります。"
|
||||
},
|
||||
"ko": {
|
||||
"ui.continuity_smooth_curvature_change": "부드러운 곡률 변화",
|
||||
"ui.continuity_smooth_curvature_change_tip": "부드러운 곡률 변화(G4): 이음매에서 곡률이 변하는 방식까지 부드럽게 이어집니다.",
|
||||
"ui.fillet_corner_tangent_tip": "접선(G1): 굴러가는 볼 코너. 굴러가는 볼의 구면 일부로, 각 라운드에 접합니다.",
|
||||
"ui.fillet_corner_curvature_tip": "곡률(G2): 부드러운 패치. 코너를 각 모서리를 따라 셋백만큼(입력하지 않으면 반지름의 {factor}배) 물려 패치로 채우므로, 반사가 꺾임 없이 지나갑니다."
|
||||
},
|
||||
"zh": {
|
||||
"ui.continuity_smooth_curvature_change": "平滑曲率变化",
|
||||
"ui.continuity_smooth_curvature_change_tip": "平滑曲率变化 (G4):连曲率的变化方式在接缝处也平滑延续。",
|
||||
"ui.fillet_corner_tangent_tip": "相切 (G1):滚球角,滚球球面的一部分,与每个圆角相切。",
|
||||
"ui.fillet_corner_curvature_tip": "曲率 (G2):平滑曲面片。角点沿每条边后退一个后退距离(未输入时为半径的 {factor} 倍),并用曲面片填充,反射穿过时没有折痕。"
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"ui.continuity_position": "Position",
|
||||
"ui.continuity_tangent": "Tangent",
|
||||
"ui.continuity_curvature": "Curvature",
|
||||
"ui.continuity_curvature_change": "Curvature change",
|
||||
"ui.continuity_crease": "Crease",
|
||||
"ui.continuity_curvature_change_tip": "Curvature change (G3): the curvature also changes smoothly across the seam.",
|
||||
"ui.continuity_curvature_tip": "Curvature-continuous (G2): reflections run through without a kink.",
|
||||
"ui.continuity_position_tip": "Position (G0): the surfaces meet, with a crease where they do.",
|
||||
"ui.continuity_tangent_tip": "Tangent (G1): no crease, though the curvature jumps across the seam."
|
||||
},
|
||||
"ar": {
|
||||
"ui.continuity_position": "الموضع",
|
||||
"ui.continuity_tangent": "مماس",
|
||||
"ui.continuity_curvature": "الانحناء",
|
||||
"ui.continuity_curvature_change": "تغيّر الانحناء",
|
||||
"ui.continuity_crease": "تجعّد",
|
||||
"ui.continuity_curvature_change_tip": "تغيّر الانحناء (G3): يتغير الانحناء أيضًا بسلاسة عبر خط الالتقاء.",
|
||||
"ui.continuity_curvature_tip": "متصل الانحناء (G2): تمر الانعكاسات دون انكسار.",
|
||||
"ui.continuity_position_tip": "الموضع (G0): تلتقي الأسطح، مع تجعّد عند التقائها.",
|
||||
"ui.continuity_tangent_tip": "مماس (G1): لا تجعّد، لكن الانحناء يقفز عبر خط الالتقاء."
|
||||
},
|
||||
"cs": {
|
||||
"ui.continuity_position": "Poloha",
|
||||
"ui.continuity_tangent": "Tečnost",
|
||||
"ui.continuity_curvature": "Křivost",
|
||||
"ui.continuity_curvature_change": "Změna křivosti",
|
||||
"ui.continuity_crease": "Hrana"
|
||||
},
|
||||
"da": {
|
||||
"ui.continuity_position": "Positionskontinuitet",
|
||||
"ui.continuity_tangent": "Tangentkontinuitet",
|
||||
"ui.continuity_curvature": "Krumning",
|
||||
"ui.continuity_curvature_change": "Ændring i krumning",
|
||||
"ui.continuity_crease": "Knæk"
|
||||
},
|
||||
"de": {
|
||||
"ui.continuity_position": "Position",
|
||||
"ui.continuity_tangent": "Tangential",
|
||||
"ui.continuity_curvature": "Krümmung",
|
||||
"ui.continuity_curvature_change": "Krümmungsänderung",
|
||||
"ui.continuity_crease": "Knick",
|
||||
"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_position_tip": "Position (G0): Die Flächen treffen sich, mit einem Knick an der Naht.",
|
||||
"ui.continuity_tangent_tip": "Tangential (G1): kein Knick, aber die Krümmung springt an der Naht."
|
||||
},
|
||||
"es": {
|
||||
"ui.continuity_position": "Posición",
|
||||
"ui.continuity_tangent": "Tangente",
|
||||
"ui.continuity_curvature": "Curvatura",
|
||||
"ui.continuity_curvature_change": "Cambio de curvatura",
|
||||
"ui.continuity_crease": "Arista"
|
||||
},
|
||||
"fr": {
|
||||
"ui.continuity_position": "Position",
|
||||
"ui.continuity_tangent": "Tangence",
|
||||
"ui.continuity_curvature": "Courbure",
|
||||
"ui.continuity_curvature_change": "Variation de courbure",
|
||||
"ui.continuity_crease": "Angle vif"
|
||||
},
|
||||
"gl": {
|
||||
"ui.continuity_position": "Posición",
|
||||
"ui.continuity_tangent": "Tanxente",
|
||||
"ui.continuity_curvature": "Curvatura",
|
||||
"ui.continuity_curvature_change": "Cambio de curvatura",
|
||||
"ui.continuity_crease": "Aresta"
|
||||
},
|
||||
"he": {
|
||||
"ui.continuity_position": "מיקום",
|
||||
"ui.continuity_tangent": "משיק",
|
||||
"ui.continuity_curvature": "עקמומיות",
|
||||
"ui.continuity_curvature_change": "שינוי עקמומיות",
|
||||
"ui.continuity_crease": "שבר",
|
||||
"ui.continuity_curvature_change_tip": "שינוי עקמומיות (G3): גם העקמומיות משתנה בצורה חלקה לאורך התפר.",
|
||||
"ui.continuity_curvature_tip": "רציף בעקמומיות (G2): ההשתקפויות עוברות בלי שבר.",
|
||||
"ui.continuity_position_tip": "מיקום (G0): המשטחים נפגשים, עם שבר במקום המפגש.",
|
||||
"ui.continuity_tangent_tip": "משיק (G1): אין שבר, אך העקמומיות קופצת לאורך התפר."
|
||||
},
|
||||
"hu": {
|
||||
"ui.continuity_position": "Pozíció",
|
||||
"ui.continuity_tangent": "Érintő",
|
||||
"ui.continuity_curvature": "Görbület",
|
||||
"ui.continuity_curvature_change": "Görbületváltozás",
|
||||
"ui.continuity_crease": "Törés"
|
||||
},
|
||||
"id": {
|
||||
"ui.continuity_position": "Posisi",
|
||||
"ui.continuity_tangent": "Tangen",
|
||||
"ui.continuity_curvature": "Kelengkungan",
|
||||
"ui.continuity_curvature_change": "Perubahan kelengkungan",
|
||||
"ui.continuity_crease": "Lipatan"
|
||||
},
|
||||
"it": {
|
||||
"ui.continuity_position": "Posizione",
|
||||
"ui.continuity_tangent": "Tangente",
|
||||
"ui.continuity_curvature": "Curvatura",
|
||||
"ui.continuity_curvature_change": "Variazione di curvatura",
|
||||
"ui.continuity_crease": "Spigolo vivo"
|
||||
},
|
||||
"ja": {
|
||||
"ui.continuity_position": "位置",
|
||||
"ui.continuity_tangent": "接線",
|
||||
"ui.continuity_curvature": "曲率",
|
||||
"ui.continuity_curvature_change": "曲率変化",
|
||||
"ui.continuity_crease": "折れ",
|
||||
"ui.continuity_curvature_change_tip": "曲率変化 (G3): 境目で曲率の変化もなめらかです。",
|
||||
"ui.continuity_curvature_tip": "曲率連続 (G2): 映り込みが折れずに通ります。",
|
||||
"ui.continuity_position_tip": "位置 (G0): 面はつながりますが、境目に折れがあります。",
|
||||
"ui.continuity_tangent_tip": "接線 (G1): 折れはありませんが、境目で曲率が飛びます。"
|
||||
},
|
||||
"ko": {
|
||||
"ui.continuity_position": "위치",
|
||||
"ui.continuity_tangent": "접선",
|
||||
"ui.continuity_curvature": "곡률",
|
||||
"ui.continuity_curvature_change": "곡률 변화",
|
||||
"ui.continuity_crease": "꺾임",
|
||||
"ui.continuity_curvature_change_tip": "곡률 변화(G3): 이음매에서 곡률의 변화도 부드럽습니다.",
|
||||
"ui.continuity_curvature_tip": "곡률 연속(G2): 반사가 꺾임 없이 이어집니다.",
|
||||
"ui.continuity_position_tip": "위치(G0): 면이 만나지만, 만나는 곳에 꺾임이 있습니다.",
|
||||
"ui.continuity_tangent_tip": "접선(G1): 꺾임은 없지만, 이음매에서 곡률이 급변합니다."
|
||||
},
|
||||
"ms": {
|
||||
"ui.continuity_position": "Kedudukan",
|
||||
"ui.continuity_tangent": "Tangen",
|
||||
"ui.continuity_curvature": "Kelengkungan",
|
||||
"ui.continuity_curvature_change": "Perubahan kelengkungan",
|
||||
"ui.continuity_crease": "Lipatan"
|
||||
},
|
||||
"nb": {
|
||||
"ui.continuity_position": "Posisjon",
|
||||
"ui.continuity_tangent": "Tangent",
|
||||
"ui.continuity_curvature": "Krumning",
|
||||
"ui.continuity_curvature_change": "Krumningsendring",
|
||||
"ui.continuity_crease": "Knekk"
|
||||
},
|
||||
"nl": {
|
||||
"ui.continuity_position": "Positie",
|
||||
"ui.continuity_tangent": "Tangentieel",
|
||||
"ui.continuity_curvature": "Kromming",
|
||||
"ui.continuity_curvature_change": "Krommingsverandering",
|
||||
"ui.continuity_crease": "Knik"
|
||||
},
|
||||
"pl": {
|
||||
"ui.continuity_position": "Położenie",
|
||||
"ui.continuity_tangent": "Styczność",
|
||||
"ui.continuity_curvature": "Krzywizna",
|
||||
"ui.continuity_curvature_change": "Zmiana krzywizny",
|
||||
"ui.continuity_crease": "Załamanie"
|
||||
},
|
||||
"pt-BR": {
|
||||
"ui.continuity_position": "Posição",
|
||||
"ui.continuity_tangent": "Tangente",
|
||||
"ui.continuity_curvature": "Curvatura",
|
||||
"ui.continuity_curvature_change": "Variação de curvatura",
|
||||
"ui.continuity_crease": "Vinco"
|
||||
},
|
||||
"ro": {
|
||||
"ui.continuity_position": "Poziție",
|
||||
"ui.continuity_tangent": "Tangent",
|
||||
"ui.continuity_curvature": "Curbură",
|
||||
"ui.continuity_curvature_change": "Variația curburii",
|
||||
"ui.continuity_crease": "Muchie"
|
||||
},
|
||||
"ru": {
|
||||
"ui.continuity_position": "Положение",
|
||||
"ui.continuity_tangent": "Касание",
|
||||
"ui.continuity_curvature": "Кривизна",
|
||||
"ui.continuity_curvature_change": "Изменение кривизны",
|
||||
"ui.continuity_crease": "Излом"
|
||||
},
|
||||
"sv": {
|
||||
"ui.continuity_position": "Läge",
|
||||
"ui.continuity_tangent": "Tangent",
|
||||
"ui.continuity_curvature": "Krökning",
|
||||
"ui.continuity_curvature_change": "Krökningsändring",
|
||||
"ui.continuity_crease": "Veck"
|
||||
},
|
||||
"tr": {
|
||||
"ui.continuity_position": "Konum",
|
||||
"ui.continuity_tangent": "Teğet",
|
||||
"ui.continuity_curvature": "Eğrilik",
|
||||
"ui.continuity_curvature_change": "Eğrilik değişimi",
|
||||
"ui.continuity_crease": "Kırık"
|
||||
},
|
||||
"uk": {
|
||||
"ui.continuity_position": "Положення",
|
||||
"ui.continuity_tangent": "Дотична",
|
||||
"ui.continuity_curvature": "Кривизна",
|
||||
"ui.continuity_curvature_change": "Зміна кривини",
|
||||
"ui.continuity_crease": "Злам"
|
||||
},
|
||||
"vi": {
|
||||
"ui.continuity_position": "Vị trí liên tục",
|
||||
"ui.continuity_tangent": "Tiếp tuyến",
|
||||
"ui.continuity_curvature": "Độ cong",
|
||||
"ui.continuity_curvature_change": "Thay đổi độ cong",
|
||||
"ui.continuity_crease": "Nếp gập"
|
||||
},
|
||||
"zh": {
|
||||
"ui.continuity_position": "位置",
|
||||
"ui.continuity_tangent": "相切",
|
||||
"ui.continuity_curvature": "曲率",
|
||||
"ui.continuity_curvature_change": "曲率变化",
|
||||
"ui.continuity_crease": "折痕",
|
||||
"ui.continuity_curvature_change_tip": "曲率变化 (G3):曲率在接缝处的变化也是平滑的。",
|
||||
"ui.continuity_curvature_tip": "曲率连续 (G2):反射平滑穿过,没有折痕。",
|
||||
"ui.continuity_position_tip": "位置 (G0):曲面相接,但接缝处有折痕。",
|
||||
"ui.continuity_tangent_tip": "相切 (G1):没有折痕,但曲率在接缝处突变。"
|
||||
},
|
||||
"zh-TW": {
|
||||
"ui.continuity_position": "位置",
|
||||
"ui.continuity_tangent": "相切",
|
||||
"ui.continuity_curvature": "曲率",
|
||||
"ui.continuity_curvature_change": "曲率變化",
|
||||
"ui.continuity_crease": "摺痕"
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"ui.comb_length": "Comb length",
|
||||
"ui.comb_length_tip": "How long the teeth are: a multiple of the automatic length, which makes the longest tooth a fifth of the size of the curves shown.",
|
||||
"ui.comb_teeth": "Teeth",
|
||||
"ui.comb_teeth_tip": "How many teeth each comb has, evenly spaced along the curve. More teeth show more detail of how the curvature changes."
|
||||
},
|
||||
"ar": {
|
||||
"ui.comb_length": "طول المشط",
|
||||
"ui.comb_length_tip": "طول الأسنان: مضاعف للطول التلقائي الذي يجعل أطول سنّ خُمس حجم المنحنيات المعروضة.",
|
||||
"ui.comb_teeth": "الأسنان",
|
||||
"ui.comb_teeth_tip": "عدد أسنان كل مشط، موزّعة بالتساوي على طول المنحنى. كلما زادت الأسنان ظهرت تفاصيل أكثر عن تغيّر الانحناء."
|
||||
},
|
||||
"de": {
|
||||
"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."
|
||||
},
|
||||
"he": {
|
||||
"ui.comb_length": "אורך המסרק",
|
||||
"ui.comb_length_tip": "אורך השיניים: כפולה של האורך האוטומטי, שבו השן הארוכה ביותר היא חמישית מגודל העקומות המוצגות.",
|
||||
"ui.comb_teeth": "שיניים",
|
||||
"ui.comb_teeth_tip": "כמה שיניים יש לכל מסרק, במרווחים שווים לאורך העקומה. יותר שיניים מראות בפירוט רב יותר איך העקמומיות משתנה."
|
||||
},
|
||||
"ja": {
|
||||
"ui.comb_length": "コームの長さ",
|
||||
"ui.comb_length_tip": "歯の長さ: 自動の長さの倍率です。自動の長さでは、いちばん長い歯が表示中の曲線の大きさの 5 分の 1 になります。",
|
||||
"ui.comb_teeth": "歯の数",
|
||||
"ui.comb_teeth_tip": "各コームの歯の数です。曲線に沿って等間隔に並びます。歯を増やすと、曲率の変わり方がより細かく見えます。"
|
||||
},
|
||||
"ko": {
|
||||
"ui.comb_length": "빗 길이",
|
||||
"ui.comb_length_tip": "빗살의 길이: 자동 길이의 배수입니다. 자동 길이에서는 가장 긴 빗살이 표시된 곡선 크기의 5분의 1이 됩니다.",
|
||||
"ui.comb_teeth": "빗살 수",
|
||||
"ui.comb_teeth_tip": "각 빗의 빗살 수이며, 곡선을 따라 같은 간격으로 놓입니다. 빗살이 많을수록 곡률이 어떻게 바뀌는지 더 자세히 보입니다."
|
||||
},
|
||||
"zh": {
|
||||
"ui.comb_length": "梳长",
|
||||
"ui.comb_length_tip": "梳齿的长度:自动长度的倍数。自动长度使最长的梳齿为所示曲线尺寸的五分之一。",
|
||||
"ui.comb_teeth": "梳齿数",
|
||||
"ui.comb_teeth_tip": "每个梳的梳齿数,沿曲线均匀分布。梳齿越多,越能看清曲率如何变化。"
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"ui.fillet_corner_ball": "Ball",
|
||||
"ui.fillet_corner_ball_tip": "A piece of the rolling ball's sphere, tangent (G1) to each round.",
|
||||
"ui.fillet_corner_needs_constant": "Smooth patch corners (G2) need Constant radius and Exact fit.",
|
||||
"ui.fillet_corner_patch": "Smooth patch",
|
||||
"ui.fillet_corner_patch_tip": "Curvature-continuous (G2): the corner is set back along each edge and filled with a patch, so reflections run through it without a kink.",
|
||||
"ui.fillet_fit_needs_constant": "Radius fit needs Constant radius.",
|
||||
"ui.fillet_setback": "Setback",
|
||||
"ui.fillet_setback_hint": "{factor} × radius",
|
||||
"ui.fillet_setback_tip": "Where the smooth corner patch starts: how far along each edge from the corner. The default is {factor} × the radius. It must be more than the least setback, where the rounds meet without one: the radius where the faces are square to each other.",
|
||||
"ui.fillet_smooth_radius": "Smooth radius change",
|
||||
"ui.fillet_type_curvature": "Curvature continuous",
|
||||
"ui.fillet_type_tip": "The section across the edge. Curvature continuous: a curvature-continuous (G2) section between planar faces, so reflections run from the faces into the round without a kink."
|
||||
},
|
||||
"ar": {
|
||||
"ui.fillet_corner_ball": "كرة",
|
||||
"ui.fillet_corner_ball_tip": "جزء من سطح الكرة المتدحرجة، مماسّ (G1) لكل تقويس.",
|
||||
"ui.fillet_corner_needs_constant": "زوايا الرقعة الناعمة (G2) تتطلب نصف قطر ثابتًا وملاءمة دقيقة لنصف القطر.",
|
||||
"ui.fillet_corner_patch": "رقعة ناعمة",
|
||||
"ui.fillet_corner_patch_tip": "متصل الانحناء (G2): تُرجَع الزاوية على طول كل حافة وتُملأ برقعة، فتمر الانعكاسات عبرها دون انكسار.",
|
||||
"ui.fillet_fit_needs_constant": "ملاءمة نصف القطر تتطلب نصف قطر ثابتًا.",
|
||||
"ui.fillet_setback": "مسافة الإرجاع",
|
||||
"ui.fillet_setback_hint": "{factor} × نصف القطر",
|
||||
"ui.fillet_setback_tip": "حيث تبدأ رقعة الزاوية الناعمة: المسافة على طول كل حافة من الزاوية. الافتراضي {factor} ضعف نصف القطر. يجب أن تكون أكبر من أقل مسافة إرجاع، حيث تلتقي الاستدارات بدونها: نصف القطر حيث تكون الأوجه متعامدة.",
|
||||
"ui.fillet_smooth_radius": "تغيّر ناعم لنصف القطر",
|
||||
"ui.fillet_type_curvature": "متصل الانحناء",
|
||||
"ui.fillet_type_tip": "المقطع عبر الحافة. متصل الانحناء: مقطع متصل الانحناء (G2) بين وجوه مستوية، فتمر الانعكاسات من الوجوه إلى التقويس دون انكسار."
|
||||
},
|
||||
"de": {
|
||||
"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_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_fit_needs_constant": "Radiusanpassung braucht einen konstanten Radius.",
|
||||
"ui.fillet_setback": "Rücksprung",
|
||||
"ui.fillet_setback_hint": "{factor} × Radius",
|
||||
"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_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."
|
||||
},
|
||||
"he": {
|
||||
"ui.fillet_corner_ball": "כדור",
|
||||
"ui.fillet_corner_ball_tip": "חלק מספירת הכדור המתגלגל, משיק (G1) לכל עיגול.",
|
||||
"ui.fillet_corner_needs_constant": "פינות עם טלאי חלק (G2) דורשות רדיוס קבוע והתאמת רדיוס מדויקת.",
|
||||
"ui.fillet_corner_patch": "טלאי חלק",
|
||||
"ui.fillet_corner_patch_tip": "רציף בעקמומיות (G2): הפינה נסוגה לאורך כל קצה ומתמלאת בטלאי, כך שההשתקפויות עוברות בה בלי שבר.",
|
||||
"ui.fillet_fit_needs_constant": "התאמת רדיוס דורשת רדיוס קבוע.",
|
||||
"ui.fillet_setback": "נסיגה",
|
||||
"ui.fillet_setback_hint": "{factor} × רדיוס",
|
||||
"ui.fillet_setback_tip": "היכן מתחיל טלאי הפינה החלק: המרחק לאורך כל קצה מהפינה. ברירת המחדל היא פי {factor} מהרדיוס. עליה להיות גדולה מהנסיגה המזערית, שבה העיגולים נפגשים בלעדיה: הרדיוס כשהפאות ניצבות זו לזו.",
|
||||
"ui.fillet_smooth_radius": "שינוי רדיוס חלק",
|
||||
"ui.fillet_type_curvature": "רציף בעקמומיות",
|
||||
"ui.fillet_type_tip": "החתך לרוחב הקצה. רציף בעקמומיות: חתך רציף בעקמומיות (G2) בין פאות מישוריות, כך שההשתקפויות עוברות מהפאות אל העיגול בלי שבר."
|
||||
},
|
||||
"ja": {
|
||||
"ui.fillet_corner_ball": "ボール",
|
||||
"ui.fillet_corner_ball_tip": "転がるボールの球面の一部で、各フィレットに接します (G1)。",
|
||||
"ui.fillet_corner_needs_constant": "スムーズパッチのコーナー (G2) には、一定の半径と厳密な半径フィットが必要です。",
|
||||
"ui.fillet_corner_patch": "スムーズパッチ",
|
||||
"ui.fillet_corner_patch_tip": "曲率連続 (G2): コーナーを各エッジに沿って後退させてパッチで埋め、映り込みが折れずに通ります。",
|
||||
"ui.fillet_fit_needs_constant": "半径フィットには一定の半径が必要です。",
|
||||
"ui.fillet_setback": "セットバック",
|
||||
"ui.fillet_setback_hint": "{factor} × 半径",
|
||||
"ui.fillet_setback_tip": "なめらかなコーナーパッチが始まる位置: コーナーから各エッジに沿った距離。既定値は半径の {factor} 倍。最小セットバック (セットバックなしでラウンド同士が交わる位置。面どうしが直角なら半径) より大きくする必要があります。",
|
||||
"ui.fillet_smooth_radius": "半径をなめらかに変化",
|
||||
"ui.fillet_type_curvature": "曲率連続",
|
||||
"ui.fillet_type_tip": "エッジを横切る断面。曲率連続: 平面の間の曲率連続 (G2) 断面で、映り込みが面からフィレットへ折れずにつながります。"
|
||||
},
|
||||
"ko": {
|
||||
"ui.fillet_corner_ball": "볼",
|
||||
"ui.fillet_corner_ball_tip": "굴러가는 볼의 구면 일부로, 각 라운드에 접합니다(G1).",
|
||||
"ui.fillet_corner_needs_constant": "부드러운 패치 코너(G2)에는 일정한 반지름과 정확한 반지름 맞춤이 필요합니다.",
|
||||
"ui.fillet_corner_patch": "부드러운 패치",
|
||||
"ui.fillet_corner_patch_tip": "곡률 연속(G2): 코너를 각 모서리를 따라 물려 패치로 채우므로, 반사가 꺾임 없이 지나갑니다.",
|
||||
"ui.fillet_fit_needs_constant": "반지름 맞춤에는 일정한 반지름이 필요합니다.",
|
||||
"ui.fillet_setback": "셋백",
|
||||
"ui.fillet_setback_hint": "{factor} × 반지름",
|
||||
"ui.fillet_setback_tip": "부드러운 코너 패치가 시작되는 위치: 코너에서 각 모서리를 따른 거리. 기본값은 반지름의 {factor}배. 최소 셋백(셋백 없이 라운드가 만나는 곳, 면이 서로 직각이면 반지름)보다 커야 합니다.",
|
||||
"ui.fillet_smooth_radius": "반지름 부드럽게 변화",
|
||||
"ui.fillet_type_curvature": "곡률 연속",
|
||||
"ui.fillet_type_tip": "모서리를 가로지르는 단면. 곡률 연속: 평면 사이의 곡률 연속(G2) 단면으로, 반사가 면에서 라운드로 꺾임 없이 이어집니다."
|
||||
},
|
||||
"zh": {
|
||||
"ui.fillet_corner_ball": "球面",
|
||||
"ui.fillet_corner_ball_tip": "滚球球面的一部分,与每个圆角相切 (G1)。",
|
||||
"ui.fillet_corner_needs_constant": "平滑曲面片角点 (G2) 需要恒定半径和精确的半径拟合。",
|
||||
"ui.fillet_corner_patch": "平滑曲面片",
|
||||
"ui.fillet_corner_patch_tip": "曲率连续 (G2):角点沿每条边后退,并用曲面片填充,反射穿过时没有折痕。",
|
||||
"ui.fillet_fit_needs_constant": "半径拟合需要恒定半径。",
|
||||
"ui.fillet_setback": "后退距离",
|
||||
"ui.fillet_setback_hint": "{factor} × 半径",
|
||||
"ui.fillet_setback_tip": "平滑角点曲面片开始的位置:从角点沿每条边的距离。默认值为半径的 {factor} 倍。必须大于最小后退距离(不后退时圆角相交之处;面相互垂直时即半径)。",
|
||||
"ui.fillet_smooth_radius": "半径平滑变化",
|
||||
"ui.fillet_type_curvature": "曲率连续",
|
||||
"ui.fillet_type_tip": "横跨边的截面。曲率连续:平面之间的曲率连续 (G2) 截面,反射从面平滑过渡到圆角,没有折痕。"
|
||||
}
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"ui.copy_details": "Copy Details",
|
||||
"ui.copy_details_tip": "Copy the full message, to look it up or to report it.",
|
||||
"ui.fillet_error_corner": "The smooth corner could not be built here. Try another Setback, or Tangent corners.",
|
||||
"ui.fillet_error_failed": "This fillet could not be built on these edges. Try another radius, or round fewer edges at once.",
|
||||
"ui.fillet_error_too_large": "The radius is too large for the faces beside these edges. Try a smaller radius, or set Face limits to Largest radius.",
|
||||
"ui.fillet_fit": "Face limits",
|
||||
"ui.fillet_fit_exact": "Exact radius",
|
||||
"ui.fillet_fit_reduce": "Largest radius",
|
||||
"ui.fillet_fit_taper": "Narrowed radius",
|
||||
"ui.fillet_fit_tip": "When the radius is larger than the faces beside an edge allow.\nExact radius: the radius as given, or nothing rounded and a message saying why.\nLargest radius: every edge rounded with the largest radius that fits.\nNarrowed radius: the radius kept, the round narrowed only where the faces run out.",
|
||||
"ui.fillet_options_tip": "Tangent chain: also round the edges that continue smoothly from the picked ones.\nPreview: show the result in the view while you edit.",
|
||||
"ui.fillet_type_choices_tip": "Constant radius: one radius all along the edge.\nChord length: the width across the round stays the same, whatever angle the faces meet at.\nVariable radius: a radius at each end, and more along the edge if you add them.\nCurvature continuous: a section without a kink in reflections where it meets flat faces (G2).\nSpline: the radius along the edge follows control radii you set."
|
||||
},
|
||||
"ar": {
|
||||
"ui.copy_details": "نسخ التفاصيل",
|
||||
"ui.copy_details_tip": "انسخ الرسالة كاملة للبحث عنها أو للإبلاغ عنها.",
|
||||
"ui.fillet_error_corner": "تعذّر بناء الزاوية الناعمة هنا. جرّب مسافة إرجاع أخرى، أو زوايا مماسّة.",
|
||||
"ui.fillet_error_failed": "تعذّر بناء هذا التقويس على هذه الحواف. جرّب نصف قطر آخر، أو قوّس حوافّ أقل في المرة الواحدة.",
|
||||
"ui.fillet_error_too_large": "نصف القطر كبير جدًا على الوجوه المجاورة لهذه الحواف. جرّب نصف قطر أصغر، أو اختر في «حدود الوجوه» الخيار «أكبر نصف قطر».",
|
||||
"ui.fillet_fit": "حدود الوجوه",
|
||||
"ui.fillet_fit_exact": "نصف قطر دقيق",
|
||||
"ui.fillet_fit_reduce": "أكبر نصف قطر",
|
||||
"ui.fillet_fit_taper": "نصف قطر مُضيَّق",
|
||||
"ui.fillet_fit_tip": "عندما يكون نصف القطر أكبر مما تسمح به الوجوه المجاورة للحافة.\nنصف قطر دقيق: نصف القطر كما هو، أو لا تقويس ورسالة توضّح السبب.\nأكبر نصف قطر: تُقوَّس كل حافة بأكبر نصف قطر يتّسع.\nنصف قطر مُضيَّق: يبقى نصف القطر، ويُضيَّق التقويس فقط حيث تنتهي الوجوه.",
|
||||
"ui.fillet_options_tip": "سلسلة مماسّة: قوّس أيضًا الحواف التي تتصل بسلاسة بالحواف المختارة.\nمعاينة: اعرض النتيجة في العرض أثناء التحرير.",
|
||||
"ui.fillet_type_choices_tip": "نصف قطر ثابت: نصف قطر واحد على طول الحافة.\nطول الوتر: يبقى عرض التقويس نفسه مهما كانت الزاوية بين الوجوه.\nنصف قطر متغيّر: نصف قطر عند كل طرف، ومزيد على طول الحافة إن أضفته.\nمتصل الانحناء: مقطع بلا انكسار في الانعكاسات حيث يلتقي بالوجوه المستوية (G2).\nمنحنى: يتبع نصف القطر على طول الحافة أنصاف أقطار تحكّم تحدّدها."
|
||||
},
|
||||
"de": {
|
||||
"ui.copy_details": "Details kopieren",
|
||||
"ui.copy_details_tip": "Die vollständige Meldung kopieren, um sie nachzuschlagen oder zu melden.",
|
||||
"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_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_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_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."
|
||||
},
|
||||
"he": {
|
||||
"ui.copy_details": "העתקת פרטים",
|
||||
"ui.copy_details_tip": "העתקת ההודעה המלאה, כדי לחפש אותה או לדווח עליה.",
|
||||
"ui.fillet_error_corner": "לא ניתן היה לבנות כאן את הפינה החלקה. נסו נסיגה אחרת, או פינות משיקות.",
|
||||
"ui.fillet_error_failed": "לא ניתן היה לבנות את העיגול הזה על הקצוות האלה. נסו רדיוס אחר, או עגלו פחות קצוות בבת אחת.",
|
||||
"ui.fillet_error_too_large": "הרדיוס גדול מדי עבור הפאות שליד הקצוות האלה. נסו רדיוס קטן יותר, או בחרו ב„גבולות הפאות” את „הרדיוס הגדול ביותר”.",
|
||||
"ui.fillet_fit": "גבולות הפאות",
|
||||
"ui.fillet_fit_exact": "רדיוס מדויק",
|
||||
"ui.fillet_fit_reduce": "הרדיוס הגדול ביותר",
|
||||
"ui.fillet_fit_taper": "רדיוס מוצר",
|
||||
"ui.fillet_fit_tip": "כשהרדיוס גדול ממה שהפאות שליד הקצה מאפשרות.\nרדיוס מדויק: הרדיוס כפי שניתן, או ללא עיגול והודעה שמסבירה למה.\nהרדיוס הגדול ביותר: כל קצה מעוגל ברדיוס הגדול ביותר שנכנס.\nרדיוס מוצר: הרדיוס נשמר, והעיגול מוצר רק היכן שהפאות נגמרות.",
|
||||
"ui.fillet_options_tip": "שרשרת משיקה: לעגל גם את הקצוות שממשיכים בחלקות מהקצוות שנבחרו.\nתצוגה מקדימה: להציג את התוצאה בתצוגה בזמן העריכה.",
|
||||
"ui.fillet_type_choices_tip": "רדיוס קבוע: רדיוס אחד לכל אורך הקצה.\nאורך מיתר: רוחב העיגול נשאר זהה, בכל זווית שבה הפאות נפגשות.\nרדיוס משתנה: רדיוס בכל קצה, ועוד לאורך הקצה אם מוסיפים.\nרציף בעקמומיות: חתך בלי שבר בהשתקפויות היכן שהוא פוגש פאות מישוריות (G2).\nספליין: הרדיוס לאורך הקצה עוקב אחרי רדיוסי בקרה שאתם קובעים."
|
||||
},
|
||||
"ja": {
|
||||
"ui.copy_details": "詳細をコピー",
|
||||
"ui.copy_details_tip": "調べたり報告したりできるよう、メッセージ全文をコピーします。",
|
||||
"ui.fillet_error_corner": "ここではなめらかなコーナーを作れませんでした。別のセットバックか、接線のコーナーを試してください。",
|
||||
"ui.fillet_error_failed": "このフィレットをこれらのエッジに作れませんでした。別の半径を試すか、一度に丸めるエッジを減らしてください。",
|
||||
"ui.fillet_error_too_large": "これらのエッジの隣の面に対して半径が大きすぎます。小さい半径を試すか、「面の境界」を「最大半径」にしてください。",
|
||||
"ui.fillet_fit": "面の境界",
|
||||
"ui.fillet_fit_exact": "正確な半径",
|
||||
"ui.fillet_fit_reduce": "最大半径",
|
||||
"ui.fillet_fit_taper": "狭めた半径",
|
||||
"ui.fillet_fit_tip": "半径がエッジの隣の面で収まる大きさを超えるとき。\n正確な半径: 指定どおりの半径。収まらなければ丸めずに理由を知らせます。\n最大半径: 各エッジを収まる最大の半径で丸めます。\n狭めた半径: 半径を保ち、面が足りない所だけフィレットを細くします。",
|
||||
"ui.fillet_options_tip": "正接チェーン: 選んだエッジからなめらかに続くエッジも丸めます。\nプレビュー: 編集中に結果をビューに表示します。",
|
||||
"ui.fillet_type_choices_tip": "一定の半径: エッジ全体で一つの半径。\n弦長: 面がどんな角度で交わっても、フィレットの幅が同じ。\n可変半径: 両端の半径と、追加すればエッジ沿いの半径。\n曲率連続: 平面と接する所で映り込みが折れない断面 (G2)。\nスプライン: エッジ沿いの半径が、設定した制御半径に従います。"
|
||||
},
|
||||
"ko": {
|
||||
"ui.copy_details": "세부 정보 복사",
|
||||
"ui.copy_details_tip": "찾아보거나 보고할 수 있도록 전체 메시지를 복사합니다.",
|
||||
"ui.fillet_error_corner": "여기에서는 부드러운 코너를 만들 수 없었습니다. 다른 셋백이나 접선 코너를 시도해 보세요.",
|
||||
"ui.fillet_error_failed": "이 모서리들에 이 필렛을 만들 수 없었습니다. 다른 반지름을 시도하거나 한 번에 둥글게 하는 모서리 수를 줄여 보세요.",
|
||||
"ui.fillet_error_too_large": "이 모서리 옆의 면에 비해 반지름이 너무 큽니다. 더 작은 반지름을 시도하거나 '면 경계'를 '최대 반지름'으로 설정하세요.",
|
||||
"ui.fillet_fit": "면 경계",
|
||||
"ui.fillet_fit_exact": "정확한 반지름",
|
||||
"ui.fillet_fit_reduce": "최대 반지름",
|
||||
"ui.fillet_fit_taper": "좁힌 반지름",
|
||||
"ui.fillet_fit_tip": "반지름이 모서리 옆 면이 허용하는 것보다 클 때.\n정확한 반지름: 지정한 반지름 그대로, 또는 둥글게 하지 않고 이유를 알리는 메시지.\n최대 반지름: 모든 모서리를 들어가는 가장 큰 반지름으로 둥글게 합니다.\n좁힌 반지름: 반지름을 유지하고 면이 모자란 곳에서만 라운드를 좁힙니다.",
|
||||
"ui.fillet_options_tip": "접선 체인: 선택한 모서리에서 부드럽게 이어지는 모서리도 둥글게 합니다.\n미리 보기: 편집하는 동안 결과를 뷰에 표시합니다.",
|
||||
"ui.fillet_type_choices_tip": "일정한 반지름: 모서리 전체에 하나의 반지름.\n현 길이: 면이 어떤 각도로 만나든 라운드의 폭이 같습니다.\n가변 반지름: 양 끝의 반지름과, 추가하면 모서리를 따라 더.\n곡률 연속: 평면과 만나는 곳에서 반사가 꺾이지 않는 단면(G2).\n스플라인: 모서리를 따라 반지름이 설정한 제어 반지름을 따릅니다."
|
||||
},
|
||||
"zh": {
|
||||
"ui.copy_details": "复制详细信息",
|
||||
"ui.copy_details_tip": "复制完整消息,以便查找或报告。",
|
||||
"ui.fillet_error_corner": "无法在此处构建平滑角点。请尝试其他后退距离,或相切角点。",
|
||||
"ui.fillet_error_failed": "无法在这些边上构建此圆角。请尝试其他半径,或一次少倒几条边。",
|
||||
"ui.fillet_error_too_large": "半径对这些边旁边的面来说太大。请尝试更小的半径,或将“面边界”设为“最大半径”。",
|
||||
"ui.fillet_fit": "面边界",
|
||||
"ui.fillet_fit_exact": "精确半径",
|
||||
"ui.fillet_fit_reduce": "最大半径",
|
||||
"ui.fillet_fit_taper": "收窄半径",
|
||||
"ui.fillet_fit_tip": "当半径大于边旁边的面所允许的大小时。\n精确半径:按给定的半径倒圆,否则不倒圆并说明原因。\n最大半径:用能容纳的最大半径倒每条边。\n收窄半径:保持半径,只在面不够的地方收窄圆角。",
|
||||
"ui.fillet_options_tip": "相切链:同时倒圆与所选边平滑相连的边。\n预览:编辑时在视图中显示结果。",
|
||||
"ui.fillet_type_choices_tip": "恒定半径:整条边一个半径。\n弦长:无论面以什么角度相交,圆角宽度都相同。\n可变半径:两端各一个半径,添加后沿边还有更多。\n曲率连续:与平面相接处反射没有折痕的截面 (G2)。\n样条:沿边的半径跟随您设置的控制半径。"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"ui.fillet_setback_least": "More than {least}"
|
||||
},
|
||||
"ar": {
|
||||
"ui.fillet_setback_least": "أكثر من {least}"
|
||||
},
|
||||
"de": {
|
||||
"ui.fillet_setback_least": "Mehr als {least}"
|
||||
},
|
||||
"he": {
|
||||
"ui.fillet_setback_least": "יותר מ-{least}"
|
||||
},
|
||||
"ja": {
|
||||
"ui.fillet_setback_least": "{least} より大きく"
|
||||
},
|
||||
"ko": {
|
||||
"ui.fillet_setback_least": "{least}보다 크게"
|
||||
},
|
||||
"zh": {
|
||||
"ui.fillet_setback_least": "须大于 {least}"
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"ar": {
|
||||
"help.version": "الإصدار {version}"
|
||||
},
|
||||
"en": {
|
||||
"help.version": "Version {version}"
|
||||
},
|
||||
"he": {
|
||||
"help.version": "גרסה {version}"
|
||||
},
|
||||
"ja": {
|
||||
"help.version": "バージョン {version}"
|
||||
},
|
||||
"zh": {
|
||||
"help.version": "版本 {version}"
|
||||
},
|
||||
"zh-TW": {
|
||||
"help.version": "版本 {version}"
|
||||
}
|
||||
}
|
||||
@@ -1,30 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"import_report.refused_one": "1 part couldn't be made into a solid and was not imported.",
|
||||
"import_report.refused_other": "{count} parts couldn't be made into solids and were not imported."
|
||||
},
|
||||
"ar": {
|
||||
"import_report.refused_one": "تعذّر تحويل جزء واحد إلى مجسم، فلم يُستورد.",
|
||||
"import_report.refused_other": "تعذّر تحويل {count} أجزاء إلى مجسمات، فلم تُستورد."
|
||||
},
|
||||
"de": {
|
||||
"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."
|
||||
},
|
||||
"he": {
|
||||
"import_report.refused_one": "לא ניתן היה להפוך חלק אחד לגוף מוצק והוא לא יובא.",
|
||||
"import_report.refused_other": "לא ניתן היה להפוך {count} חלקים לגופים מוצקים והם לא יובאו."
|
||||
},
|
||||
"ja": {
|
||||
"import_report.refused_one": "1 個のパーツをソリッドにできず、読み込みませんでした。",
|
||||
"import_report.refused_other": "{count} 個のパーツをソリッドにできず、読み込みませんでした。"
|
||||
},
|
||||
"ko": {
|
||||
"import_report.refused_one": "부품 1개를 솔리드로 만들지 못해 가져오지 않았습니다.",
|
||||
"import_report.refused_other": "부품 {count}개를 솔리드로 만들지 못해 가져오지 않았습니다."
|
||||
},
|
||||
"zh": {
|
||||
"import_report.refused_one": "1 个零件无法生成实体,未导入。",
|
||||
"import_report.refused_other": "{count} 个零件无法生成实体,未导入。"
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"import_report.details": "Details:",
|
||||
"import_report.file_one": "There was 1 other problem with the file.",
|
||||
"import_report.file_other": "There were {count} other problems with the file.",
|
||||
"import_report.hint": "Exporting these parts again from the program that made them may fix this. Copy Warning Message has the details.",
|
||||
"import_report.left_out_one": "1 face couldn't be repaired and is missing.",
|
||||
"import_report.left_out_other": "{count} faces couldn't be repaired and are missing.",
|
||||
"import_report.not_imported_one": "1 part couldn't be imported.",
|
||||
"import_report.not_imported_other": "{count} parts couldn't be imported.",
|
||||
"import_report.not_shown_one": "1 face couldn't be drawn and is hidden.",
|
||||
"import_report.not_shown_other": "{count} faces couldn't be drawn and are hidden.",
|
||||
"import_report.open_one": "1 part has gaps between its faces and came in as an open body.",
|
||||
"import_report.open_other": "{count} parts have gaps between their faces and came in as open bodies.",
|
||||
"import_report.repaired_one": "Small flaws were repaired in 1 part.",
|
||||
"import_report.repaired_other": "Small flaws were repaired in {count} parts.",
|
||||
"import_report.solids": "Imported {solids} of {parts} parts as solids.",
|
||||
"import_report.surfaces_one": "1 part couldn't be made into a solid and came in as surfaces.",
|
||||
"import_report.surfaces_other": "{count} parts couldn't be made into solids and came in as surfaces."
|
||||
},
|
||||
"ar": {
|
||||
"import_report.details": "التفاصيل:",
|
||||
"import_report.file_one": "كانت هناك مشكلة أخرى واحدة في الملف.",
|
||||
"import_report.file_other": "كانت هناك {count} مشكلات أخرى في الملف.",
|
||||
"import_report.hint": "قد يحل ذلك تصدير هذه الأجزاء مرة أخرى من البرنامج الذي أنشأها. يحتوي «نسخ رسالة التحذير» على التفاصيل.",
|
||||
"import_report.left_out_one": "تعذّر إصلاح وجه واحد وهو مفقود.",
|
||||
"import_report.left_out_other": "تعذّر إصلاح {count} أوجه وهي مفقودة.",
|
||||
"import_report.not_imported_one": "تعذّر استيراد جزء واحد.",
|
||||
"import_report.not_imported_other": "تعذّر استيراد {count} أجزاء.",
|
||||
"import_report.not_shown_one": "تعذّر رسم وجه واحد وهو مخفي.",
|
||||
"import_report.not_shown_other": "تعذّر رسم {count} أوجه وهي مخفية.",
|
||||
"import_report.open_one": "في جزء واحد فجوات بين أوجهه، فاستُورد كجسم مفتوح.",
|
||||
"import_report.open_other": "في {count} أجزاء فجوات بين أوجهها، فاستُوردت كأجسام مفتوحة.",
|
||||
"import_report.repaired_one": "أُصلحت عيوب صغيرة في جزء واحد.",
|
||||
"import_report.repaired_other": "أُصلحت عيوب صغيرة في {count} أجزاء.",
|
||||
"import_report.solids": "استُورد {solids} من {parts} أجزاء كمجسمات.",
|
||||
"import_report.surfaces_one": "تعذّر تحويل جزء واحد إلى مجسم، فاستُورد كأسطح.",
|
||||
"import_report.surfaces_other": "تعذّر تحويل {count} أجزاء إلى مجسمات، فاستُوردت كأسطح."
|
||||
},
|
||||
"de": {
|
||||
"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.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.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."
|
||||
},
|
||||
"he": {
|
||||
"import_report.details": "פרטים:",
|
||||
"import_report.file_one": "הייתה בעיה נוספת אחת בקובץ.",
|
||||
"import_report.file_other": "היו {count} בעיות נוספות בקובץ.",
|
||||
"import_report.hint": "ייצוא מחדש של החלקים האלה מהתוכנה שיצרה אותם עשוי לפתור זאת. ב„העתקת הודעת האזהרה” יש את הפרטים.",
|
||||
"import_report.left_out_one": "לא ניתן היה לתקן פאה אחת והיא חסרה.",
|
||||
"import_report.left_out_other": "לא ניתן היה לתקן {count} פאות והן חסרות.",
|
||||
"import_report.not_imported_one": "לא ניתן היה לייבא חלק אחד.",
|
||||
"import_report.not_imported_other": "לא ניתן היה לייבא {count} חלקים.",
|
||||
"import_report.not_shown_one": "לא ניתן היה לצייר פאה אחת והיא מוסתרת.",
|
||||
"import_report.not_shown_other": "לא ניתן היה לצייר {count} פאות והן מוסתרות.",
|
||||
"import_report.open_one": "בחלק אחד יש רווחים בין הפאות והוא יובא כגוף פתוח.",
|
||||
"import_report.open_other": "ב-{count} חלקים יש רווחים בין הפאות והם יובאו כגופים פתוחים.",
|
||||
"import_report.repaired_one": "תוקנו פגמים קטנים בחלק אחד.",
|
||||
"import_report.repaired_other": "תוקנו פגמים קטנים ב-{count} חלקים.",
|
||||
"import_report.solids": "{solids} מתוך {parts} חלקים יובאו כגופים מוצקים.",
|
||||
"import_report.surfaces_one": "לא ניתן היה להפוך חלק אחד לגוף מוצק והוא יובא כמשטחים.",
|
||||
"import_report.surfaces_other": "לא ניתן היה להפוך {count} חלקים לגופים מוצקים והם יובאו כמשטחים."
|
||||
},
|
||||
"ja": {
|
||||
"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.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.repaired_one": "1 個のパーツの小さな不具合を修復しました。",
|
||||
"import_report.repaired_other": "{count} 個のパーツの小さな不具合を修復しました。",
|
||||
"import_report.solids": "{parts} 個のパーツのうち {solids} 個をソリッドとして読み込みました。",
|
||||
"import_report.surfaces_one": "1 個のパーツをソリッドにできず、サーフェスとして読み込みました。",
|
||||
"import_report.surfaces_other": "{count} 個のパーツをソリッドにできず、サーフェスとして読み込みました。"
|
||||
},
|
||||
"ko": {
|
||||
"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.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.repaired_one": "부품 1개의 작은 결함을 복구했습니다.",
|
||||
"import_report.repaired_other": "부품 {count}개의 작은 결함을 복구했습니다.",
|
||||
"import_report.solids": "부품 {parts}개 중 {solids}개를 솔리드로 가져왔습니다.",
|
||||
"import_report.surfaces_one": "부품 1개를 솔리드로 만들지 못해 서피스로 들어왔습니다.",
|
||||
"import_report.surfaces_other": "부품 {count}개를 솔리드로 만들지 못해 서피스로 들어왔습니다."
|
||||
},
|
||||
"zh": {
|
||||
"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.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.repaired_one": "已修复 1 个零件中的小缺陷。",
|
||||
"import_report.repaired_other": "已修复 {count} 个零件中的小缺陷。",
|
||||
"import_report.solids": "{parts} 个零件中有 {solids} 个作为实体导入。",
|
||||
"import_report.surfaces_one": "1 个零件无法生成实体,作为曲面导入。",
|
||||
"import_report.surfaces_other": "{count} 个零件无法生成实体,作为曲面导入。"
|
||||
}
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"file_load.step_build": "Building the model",
|
||||
"file_load.step_feature": "Feature {done} of {total}",
|
||||
"file_load.step_part": "Part {done} of {total}",
|
||||
"file_load.step_percent": "{percent}%",
|
||||
"file_load.step_read": "Reading the file",
|
||||
"file_load.step_rebuild": "Rebuilding the features",
|
||||
"file_load.step_repair": "Checking and repairing the parts",
|
||||
"file_load.step_show": "Preparing the view"
|
||||
},
|
||||
"ar": {
|
||||
"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": "جارٍ تجهيز العرض"
|
||||
},
|
||||
"de": {
|
||||
"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"
|
||||
},
|
||||
"he": {
|
||||
"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": "מכין את התצוגה"
|
||||
},
|
||||
"ja": {
|
||||
"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": "ビューを準備中"
|
||||
},
|
||||
"ko": {
|
||||
"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": "뷰 준비 중"
|
||||
},
|
||||
"zh": {
|
||||
"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": "正在准备视图"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"ui.patch_continuity_corner_tip": "The patch meets the faces round it at the chosen continuity along each side. At a corner where those faces meet each other with different curvatures, it stays tangent (G1) there."
|
||||
},
|
||||
"ar": {
|
||||
"ui.patch_continuity_corner_tip": "تلتقي الرقعة بالأوجه المحيطة بها بالاستمرارية المختارة على طول كل جانب. وعند زاوية تلتقي فيها تلك الأوجه بعضها ببعض بانحناءات مختلفة، تبقى مماسّة (G1) هناك."
|
||||
},
|
||||
"de": {
|
||||
"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)."
|
||||
},
|
||||
"he": {
|
||||
"ui.patch_continuity_corner_tip": "הטלאי פוגש את הפאות שסביבו ברציפות שנבחרה לאורך כל צלע. בפינה שבה הפאות האלה נפגשות זו עם זו בעקמומיות שונה, הוא נשאר משיק (G1) שם."
|
||||
},
|
||||
"ja": {
|
||||
"ui.patch_continuity_corner_tip": "パッチは各辺に沿って、周囲の面と選んだ連続性でつながります。周囲の面どうしが異なる曲率で接するコーナーでは、そこで接線 (G1) にとどまります。"
|
||||
},
|
||||
"ko": {
|
||||
"ui.patch_continuity_corner_tip": "패치는 각 변을 따라 주변 면과 선택한 연속성으로 이어집니다. 주변 면끼리 서로 다른 곡률로 만나는 모서리에서는 그곳에서 접선(G1)으로 유지됩니다."
|
||||
},
|
||||
"zh": {
|
||||
"ui.patch_continuity_corner_tip": "补面沿每条边以所选的连续性与周围的面相接。在周围的面彼此以不同曲率相接的角上,补面在该处保持相切 (G1)。"
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"thread.form_bspp": "Pipe G (ISO 228-1)",
|
||||
"thread.form_bspt": "Pipe taper R, Rc (ISO 7-1)",
|
||||
"thread.form_npt": "Pipe taper NPT (ASME B1.20.1)",
|
||||
"thread.form_rp": "Pipe Rp (ISO 7-1)"
|
||||
},
|
||||
"ar": {
|
||||
"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)"
|
||||
},
|
||||
"he": {
|
||||
"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)"
|
||||
},
|
||||
"ja": {
|
||||
"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)"
|
||||
},
|
||||
"zh": {
|
||||
"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)"
|
||||
},
|
||||
"de": {
|
||||
"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)"
|
||||
},
|
||||
"ko": {
|
||||
"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)"
|
||||
}
|
||||
}
|
||||
@@ -1,37 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"cmd.diagnostics_profile": "Profile",
|
||||
"prefs.profile_operations": "Profile operations",
|
||||
"prefs.profile_operations_note": "Records how long SMLib calls, kernel operations, features, commands and imports take. Agents read it with diagnostics.profile."
|
||||
},
|
||||
"ar": {
|
||||
"cmd.diagnostics_profile": "التحليل الزمني",
|
||||
"prefs.profile_operations": "تحليل زمن العمليات",
|
||||
"prefs.profile_operations_note": "يسجّل المدة التي تستغرقها استدعاءات SMLib وعمليات النواة والميزات والأوامر وعمليات الاستيراد. يقرؤه الوكلاء عبر diagnostics.profile."
|
||||
},
|
||||
"de": {
|
||||
"cmd.diagnostics_profile": "Profil",
|
||||
"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."
|
||||
},
|
||||
"he": {
|
||||
"cmd.diagnostics_profile": "פרופיל ביצועים",
|
||||
"prefs.profile_operations": "מדידת זמני פעולות",
|
||||
"prefs.profile_operations_note": "מתעד כמה זמן לוקחות קריאות SMLib, פעולות ליבה, תכונות, פקודות וייבוא. סוכנים קוראים זאת עם diagnostics.profile."
|
||||
},
|
||||
"ja": {
|
||||
"cmd.diagnostics_profile": "プロファイル",
|
||||
"prefs.profile_operations": "処理をプロファイル",
|
||||
"prefs.profile_operations_note": "SMLib の呼び出し、カーネル処理、フィーチャー、コマンド、インポートにかかる時間を記録します。エージェントは diagnostics.profile で読み取ります。"
|
||||
},
|
||||
"ko": {
|
||||
"cmd.diagnostics_profile": "프로파일",
|
||||
"prefs.profile_operations": "작업 프로파일링",
|
||||
"prefs.profile_operations_note": "SMLib 호출, 커널 작업, 피처, 명령, 가져오기에 걸리는 시간을 기록합니다. 에이전트는 diagnostics.profile로 읽습니다."
|
||||
},
|
||||
"zh": {
|
||||
"cmd.diagnostics_profile": "性能分析",
|
||||
"prefs.profile_operations": "分析操作耗时",
|
||||
"prefs.profile_operations_note": "记录 SMLib 调用、内核操作、特征、命令和导入所用的时间。智能体通过 diagnostics.profile 读取。"
|
||||
}
|
||||
}
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"ar": {
|
||||
"diagnostic.reselect_reference": "{feature}: المرجع `{reference}` لم يعد موجودًا كما اختير؛ أعد اختياره"
|
||||
},
|
||||
"de": {
|
||||
"diagnostic.reselect_reference": "{feature}: Die Referenz `{reference}` existiert nicht mehr so, wie sie ausgewählt wurde; wählen Sie sie erneut aus"
|
||||
},
|
||||
"en": {
|
||||
"diagnostic.reselect_reference": "{feature}: the reference `{reference}` no longer exists as it was picked; reselect it"
|
||||
},
|
||||
"he": {
|
||||
"diagnostic.reselect_reference": "{feature}: ההפניה `{reference}` כבר אינה קיימת כפי שנבחרה; יש לבחור אותה מחדש"
|
||||
},
|
||||
"ja": {
|
||||
"diagnostic.reselect_reference": "{feature}: 参照 `{reference}` は選択したときのままでは存在しません。選択し直してください"
|
||||
},
|
||||
"ko": {
|
||||
"diagnostic.reselect_reference": "{feature}: 참조 `{reference}`이(가) 선택했을 때의 모습으로 더 이상 존재하지 않습니다. 다시 선택하세요"
|
||||
},
|
||||
"zh": {
|
||||
"diagnostic.reselect_reference": "{feature}:引用 `{reference}` 已不再是选择时的样子;请重新选择"
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
{
|
||||
"en": {
|
||||
"Repair Part": "Repair Part",
|
||||
"browser.needs_repair": "{name}: needs repair",
|
||||
"cmd.body_repair": "Repair Part",
|
||||
"import_report.needs_repair_one": "1 part needs repair before it can be modelled on.",
|
||||
"import_report.needs_repair_other": "{count} parts need repair before they can be modelled on.",
|
||||
"refused.change": "Couldn't change {body}.",
|
||||
"refused.cut": "Couldn't cut {body}.",
|
||||
"refused.face_curved": "One of its curved faces can't be used for modelling.",
|
||||
"refused.face_flat": "One of its flat faces can't be used for modelling.",
|
||||
"refused.face_freeform": "One of its freeform faces can't be used for modelling.",
|
||||
"refused.face_rounded": "One of its rounded faces can't be used for modelling.",
|
||||
"refused.faces_apart": "Its faces don't meet closely enough to be used for modelling.",
|
||||
"refused.intersect": "Couldn't intersect with {body}.",
|
||||
"refused.join": "Couldn't join to {body}.",
|
||||
"refused.repair": "Repair part",
|
||||
"refused.show_face": "Show face"
|
||||
},
|
||||
"ar": {
|
||||
"Repair Part": "إصلاح الجزء",
|
||||
"browser.needs_repair": "{name}: يحتاج إلى إصلاح",
|
||||
"cmd.body_repair": "إصلاح الجزء",
|
||||
"import_report.needs_repair_one": "يحتاج جزء واحد إلى إصلاح قبل النمذجة عليه.",
|
||||
"import_report.needs_repair_other": "تحتاج {count} أجزاء إلى إصلاح قبل النمذجة عليها.",
|
||||
"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": "إظهار الوجه"
|
||||
},
|
||||
"de": {
|
||||
"Repair Part": "Teil reparieren",
|
||||
"browser.needs_repair": "{name}: muss repariert werden",
|
||||
"cmd.body_repair": "Teil reparieren",
|
||||
"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.",
|
||||
"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"
|
||||
},
|
||||
"he": {
|
||||
"Repair Part": "תיקון חלק",
|
||||
"browser.needs_repair": "{name}: דרוש תיקון",
|
||||
"cmd.body_repair": "תיקון חלק",
|
||||
"import_report.needs_repair_one": "חלק אחד דורש תיקון לפני שאפשר למדל עליו.",
|
||||
"import_report.needs_repair_other": "{count} חלקים דורשים תיקון לפני שאפשר למדל עליהם.",
|
||||
"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": "הצגת הפאה"
|
||||
},
|
||||
"ja": {
|
||||
"Repair Part": "パーツを修復",
|
||||
"browser.needs_repair": "{name}: 修復が必要",
|
||||
"cmd.body_repair": "パーツを修復",
|
||||
"import_report.needs_repair_one": "1 個のパーツはモデリングの前に修復が必要です。",
|
||||
"import_report.needs_repair_other": "{count} 個のパーツはモデリングの前に修復が必要です。",
|
||||
"refused.change": "{body} を変更できませんでした。",
|
||||
"refused.cut": "{body} をカットできませんでした。",
|
||||
"refused.face_curved": "曲面の 1 つがモデリングに使えません。",
|
||||
"refused.face_flat": "平らな面の 1 つがモデリングに使えません。",
|
||||
"refused.face_freeform": "自由曲面の 1 つがモデリングに使えません。",
|
||||
"refused.face_rounded": "丸みのある面の 1 つがモデリングに使えません。",
|
||||
"refused.faces_apart": "面どうしが十分に合っておらず、モデリングに使えません。",
|
||||
"refused.intersect": "{body} と交差できませんでした。",
|
||||
"refused.join": "{body} に結合できませんでした。",
|
||||
"refused.repair": "パーツを修復",
|
||||
"refused.show_face": "面を表示"
|
||||
},
|
||||
"ko": {
|
||||
"Repair Part": "부품 복구",
|
||||
"browser.needs_repair": "{name}: 복구 필요",
|
||||
"cmd.body_repair": "부품 복구",
|
||||
"import_report.needs_repair_one": "부품 1개는 모델링하기 전에 복구해야 합니다.",
|
||||
"import_report.needs_repair_other": "부품 {count}개는 모델링하기 전에 복구해야 합니다.",
|
||||
"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": "면 보기"
|
||||
},
|
||||
"zh": {
|
||||
"Repair Part": "修复零件",
|
||||
"browser.needs_repair": "{name}:需要修复",
|
||||
"cmd.body_repair": "修复零件",
|
||||
"import_report.needs_repair_one": "1 个零件需要修复后才能建模。",
|
||||
"import_report.needs_repair_other": "{count} 个零件需要修复后才能建模。",
|
||||
"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": "显示面"
|
||||
}
|
||||
}
|
||||
@@ -1,27 +0,0 @@
|
||||
{
|
||||
"ar": {
|
||||
"prefs.section_hatch": "تهشير المقطع",
|
||||
"prefs.section_hatch_by_part": "حسب الجزء",
|
||||
"prefs.section_hatch_one": "نمط واحد"
|
||||
},
|
||||
"en": {
|
||||
"prefs.section_hatch": "Section hatch",
|
||||
"prefs.section_hatch_by_part": "By part",
|
||||
"prefs.section_hatch_one": "One style"
|
||||
},
|
||||
"he": {
|
||||
"prefs.section_hatch": "קווקוו חתך",
|
||||
"prefs.section_hatch_by_part": "לפי חלק",
|
||||
"prefs.section_hatch_one": "סגנון אחד"
|
||||
},
|
||||
"ja": {
|
||||
"prefs.section_hatch": "断面ハッチング",
|
||||
"prefs.section_hatch_by_part": "部品ごと",
|
||||
"prefs.section_hatch_one": "単一スタイル"
|
||||
},
|
||||
"zh": {
|
||||
"prefs.section_hatch": "剖面线",
|
||||
"prefs.section_hatch_by_part": "按零件",
|
||||
"prefs.section_hatch_one": "单一样式"
|
||||
}
|
||||
}
|
||||