diff --git a/.config/nextest.toml b/.config/nextest.toml index ec67145f..e1070ba4 100644 --- a/.config/nextest.toml +++ b/.config/nextest.toml @@ -7,15 +7,15 @@ # Keep this list in sync with docs/book/src/dev/testing.md and CONTRIBUTING.md. default-filter = """ not ( - (package(solvecraft-engine) & ( + (package(solidcraft-engine) & ( test(=tests::hostile_params_never_panic) | test(/^tests::robustness::/) | 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) )) - | (package(solvecraft-kernel) & test(=tests::boolean_identity_on_tangent_primitives)) - | (package(solvecraft-ui-egui) & binary(scenarios) & test(=acceptance_sensor_enclosure)) + | (package(solidcraft-kernel) & test(=tests::boolean_identity_on_tangent_primitives)) + | (package(solidcraft-ui-egui) & binary(scenarios) & test(=acceptance_sensor_enclosure)) ) """ fail-fast = false diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c9008447..2e364137 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,6 +1,6 @@ --- name: Bug report -about: Help reproduce and fix a SolveCraft problem +about: Help reproduce and fix a SolidCraft problem title: '' labels: '' assignees: '' @@ -12,7 +12,7 @@ What did you expect, and what happened instead? ## Reproduction -Include steps, SolveCraft version/commit and relevant environment details. Prefer a small example without private design data. +Include steps, SolidCraft version/commit and relevant environment details. Prefer a small example without private design data. ## Screenshots of the behavior (when observable) diff --git a/.github/release-notes.md b/.github/release-notes.md index 05f01db5..f323c731 100644 --- a/.github/release-notes.md +++ b/.github/release-notes.md @@ -5,15 +5,26 @@ parameter to watch the timeline rebuild. [Documentation](https://bherbruck.githu [issues](https://github.com/bherbruck/solidcraft/issues). AI agents can model with it over MCP: `solidcraft-cli mcp` ([how](https://bherbruck.github.io/solidcraft/guide/mcp.html)). -**Renamed to SolidCraft.** Downloads, installers and programs now carry the SolidCraft name: -the desktop app is `solidcraft`, the command line `solidcraft-cli`. `solvecraft-cli` is still -installed as a deprecated alias that forwards to `solidcraft-cli` (it prints a one-line note on -stderr), so scripts and MCP setups such as `claude mcp add solvecraft -- solvecraft-cli mcp` -keep working; new setups use `claude mcp add solidcraft -- solidcraft-cli mcp`. Existing designs -open as before. Upgrades: the `.msi` upgrades an earlier install in place (it now installs to -`Program Files\SolidCraft`); the `solidcraft` `.deb`/`.rpm` replaces the old `solvecraft` -package; the Flatpak app ID `ai.storyteller.solvecraft` is unchanged, so Flatpak updates -continue; earlier AppImages still find updates. +**Renamed to SolidCraft.** Everything now uses the new name: the `solidcraft` and +`solidcraft-cli` programs, `.solidcraft` designs, `SOLIDCRAFT_*` environment variables, +settings folders, package names and the `solidcraft` MCP server. Upgrading from SolveCraft: +- `.solvecraft` designs still open (double-click, Open, drag-drop, the CLI and MCP); new saves + are `.solidcraft`. Builds older than this one can't open files saved by it. +- Settings, preferences and recovery files are copied to the new folders on first launch; the + old folders are left in place. `SOLVECRAFT_*` environment variables are still read. +- `solvecraft-cli` remains as a deprecated alias for one release; it forwards to + `solidcraft-cli` and prints a note. Agents: `claude mcp add solidcraft -- solidcraft-cli mcp`; + an existing `claude mcp add solvecraft -- solvecraft-cli mcp` setup keeps working. +- Windows: the `.msi` upgrades the existing installation in place (now in + `Program Files\SolidCraft`). Linux: the `solidcraft` `.deb`/`.rpm` replaces the `solvecraft` + package. The Flatpak app ID stays `ai.storyteller.solvecraft`, so `flatpak update` just works, + and existing AppImages find the update. +- macOS: quit the app, move the old `SolveCraft.app` to the Trash (your designs and settings + stay), then drag `SolidCraft.app` from the DMG onto Applications and pin it in the Dock. +- Details: [compatibility notes](https://github.com/bherbruck/solidcraft/blob/main/docs/compatibility.md). + +Documentation, the browser app and the Flatpak feed use the SolidCraft project Pages URLs; +old-name Pages addresses are not permanent aliases. **Which file?** - **Windows:** `SolidCraft-{version}-windows-x64.exe` runs as is. Or install the `.msi`, or unzip the @@ -26,12 +37,6 @@ continue; earlier AppImages still find updates. - **Web:** `solidcraft-web-{version}.zip` is a static site to host yourself. - `SHA256SUMS.txt`: `sha256sum -c SHA256SUMS.txt --ignore-missing`. -**Updating macOS from SolveCraft.app:** quit the app, rename the existing app in -Applications to `SolidCraft.app`, then drag the new app from the DMG onto Applications -and choose **Replace**. If both names are already installed, move the old `SolveCraft.app` -to the Trash. Remove any Dock shortcut with the old name and pin the new app. Designs -and settings are unchanged. The DMG includes these instructions. - For Flatpak feed installation and updates, add Flathub for the required runtime before installing SolidCraft; keep both remotes in the same user installation: diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml index 74dae6b5..e67bb549 100644 --- a/.github/workflows/pages.yml +++ b/.github/workflows/pages.yml @@ -48,7 +48,7 @@ jobs: - name: Build the book run: cargo xtask book - name: Build the web app - working-directory: apps/solvecraft-web + working-directory: apps/solidcraft-web run: trunk build --release --public-url /solidcraft/app/ - name: Assemble the site run: | diff --git a/AGENTS.md b/AGENTS.md index cc43f360..fb6123da 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -6,13 +6,13 @@ kernel, and a desktop UI that agents can drive. Projects published by [storytold](https://github.com/storytold) can serve as references for implementation conventions. Any code adapted from other projects (MIT OR Apache-2.0) is noted in ATTRIBUTION.md. -## Product name and compatibility -SolidCraft is the displayed product name; `solidcraft` is the repository name. Keep existing -`solvecraft` crate/binary names, `.solvecraft` files, format markers, environment variables, -settings/recovery paths and package IDs compatible. Published SolveCraft release artifacts -and attribution records keep their historical names. Public docs/web/Flatpak deployments -use project Pages `/solidcraft/`; do not add permanent old-name Pages aliases. See README's -compatibility notes. +## Product name +SolidCraft everywhere: crates `solidcraft-*`, binaries `solidcraft` and `solidcraft-cli`, +`.solidcraft` designs, the `solidcraft/N` format tag, `SOLIDCRAFT_*` environment variables, +settings and recovery folders, the MCP server name, docs, CI and packaging. The old name +survives only in the explicit compatibility shims listed in `docs/compatibility.md`; don't add +new uses of it anywhere else. Public docs/web/Flatpak deployments use project Pages +`/solidcraft/`; do not add permanent old-name Pages aliases. ## Start every session here 1. Read `plan/STATUS.md` (current milestone, next task), then the task in `plan/execution-plan.md` @@ -33,15 +33,15 @@ People trust SolidCraft with their designs; a crash loses their work. **This out - **Input-derived numbers are hostile** (command parameters, design files, control-channel input): `get()` not `[i]`, finite checks, caps on sizes and loop counts. - **Third-party kernel calls are guarded:** everything that calls `truck` goes through - `solvecraft_kernel::guard`, which turns a panic into `KernelError::Internal`. + `solidcraft_kernel::guard`, which turns a panic into `KernelError::Internal`. - **Last-resort guard:** `Session::execute` runs every command under `catch_unwind`; an escaped panic restores the design and reports `EngineError::Internal`. - **Crash reports:** the desktop app writes every panic and every unclean exit to a local crash folder, - with the design and the commands that reproduce it (`solvecraft_engine::crash`, docs/crash-reports.md). + with the design and the commands that reproduce it (`solidcraft_engine::crash`, docs/crash-reports.md). When someone says the app crashed, look there first: `solidcraft-cli crashes`, `crashes show `, `crashes replay ` (or the `diagnostics.crash_reports` command / MCP `crash_reports` tool). - **Prove it:** every crash fix lands with a regression test (see `hostile_params_never_panic`); a - crash report's `design.solvecraft` + `replay.json` is the test's starting point. + crash report's `design.solidcraft` + `replay.json` is the test's starting point. - Every production crate root carries `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]`. @@ -127,9 +127,9 @@ before the draft is verified. - `{"id":2,"method":"ui.start","params":{"command":"sketch.create"}}` then `ui.click {x, y}` - `{"id":3,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}` then read the PNG. - **For UI work, look at the result** (screenshot, read the PNG). Without a window use - `ui.render` or `solvecraft-cli snapshot`. -- Headless: `solvecraft-cli run script.json --out part.step`, `solvecraft-cli eval design.solvecraft`, - `solvecraft-cli snapshot script.json --out shot.png`, `solvecraft-cli commands`. + `ui.render` or `solidcraft-cli snapshot`. +- Headless: `solidcraft-cli run script.json --out part.step`, `solidcraft-cli eval design.solidcraft`, + `solidcraft-cli snapshot script.json --out shot.png`, `solidcraft-cli commands`. - Reference tests: `cargo xtask oracle` replays `tests/oracle/*/recipe.json` and compares with each case's `expected.json` (analytic or FreeCAD values; writes `docs/oracle.md`). Expected values never come from a commercial CAD product. diff --git a/ATTRIBUTION.md b/ATTRIBUTION.md index 3e5b4451..30e18bb3 100644 --- a/ATTRIBUTION.md +++ b/ATTRIBUTION.md @@ -4,8 +4,8 @@ Every non-code asset in this repository (images, icons, fonts, example parts) is its author, source and licence. `cargo xtask assets` (part of `cargo xtask ci`) fails if an asset file is missing from this table. -**Policy (mandatory):** SolveCraft contains **no Autodesk iconography, images, artwork, fonts, -templates, materials or help text.** Every asset is original work by SolveCraft contributors or +**Policy (mandatory):** SolidCraft contains **no Autodesk iconography, images, artwork, fonts, +templates, materials or help text.** Every asset is original work by SolidCraft contributors or third-party material under an open licence. Screenshots of Autodesk software are never committed. Generated-in-code assets are original and have no file to list: @@ -36,16 +36,16 @@ Generated-in-code assets are original and have no file to list: | assets/icon.iconset/icon_256x256@2x.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 | | assets/icon.iconset/icon_512x512.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 | | assets/icon.iconset/icon_512x512@2x.png | SolidCraft contributors | native sips export from assets/icon.png | MIT OR Apache-2.0 | -| docs/screenshots/sample-plate.png | SolveCraft contributors | screenshot of SolveCraft itself (`solvecraft --sample`) | MIT OR Apache-2.0 | -| docs/screenshots/enclosure-demo-0.2.1.gif | SolveCraft contributors | original electronics enclosure screen recording of the actual SolveCraft 0.2.1 English desktop UI, generated with examples/demo/record_ui.sh and its original animated pointer overlay (4× playback) | MIT OR Apache-2.0 | -| docs/screenshots/enclosure-demo-0.2.1.mp4 | SolveCraft contributors | original electronics enclosure screen recording of the actual SolveCraft 0.2.1 English desktop UI, generated with examples/demo/record_ui.sh and its original animated pointer overlay (4× playback) | MIT OR Apache-2.0 | -| docs/book/src/generated/sample-plate.png | SolveCraft contributors | copy of docs/screenshots/sample-plate.png made by `cargo xtask book` (gitignored) | MIT OR Apache-2.0 | -| docs/book/src/generated/bracket.png | SolveCraft contributors | rendered by `cargo xtask book` (`solvecraft-cli snapshot examples/bracket.json`; gitignored) | MIT OR Apache-2.0 | -| docs/book/src/generated/enclosure.png | SolveCraft contributors | rendered by `cargo xtask book` (`solvecraft-cli snapshot docs/book/scripts/enclosure.json`; gitignored) | MIT OR Apache-2.0 | -| docs/book/src/generated/revolve.png | SolveCraft contributors | rendered by `cargo xtask book` (`solvecraft-cli snapshot docs/book/scripts/revolve.json`; gitignored) | MIT OR Apache-2.0 | -| docs/book/src/generated/sheet-metal.png | SolveCraft contributors | rendered by `cargo xtask book` (`solvecraft-cli snapshot docs/book/scripts/sheet-metal.json`; gitignored) | MIT OR Apache-2.0 | +| docs/screenshots/sample-plate.png | SolidCraft contributors | screenshot of SolidCraft itself (`solidcraft --sample`) | MIT OR Apache-2.0 | +| docs/screenshots/enclosure-demo-0.2.1.gif | SolidCraft contributors | original electronics enclosure screen recording of the actual SolidCraft 0.2.1 English desktop UI, generated with examples/demo/record_ui.sh and its original animated pointer overlay (4× playback) | MIT OR Apache-2.0 | +| docs/screenshots/enclosure-demo-0.2.1.mp4 | SolidCraft contributors | original electronics enclosure screen recording of the actual SolidCraft 0.2.1 English desktop UI, generated with examples/demo/record_ui.sh and its original animated pointer overlay (4× playback) | MIT OR Apache-2.0 | +| docs/book/src/generated/sample-plate.png | SolidCraft contributors | copy of docs/screenshots/sample-plate.png made by `cargo xtask book` (gitignored) | MIT OR Apache-2.0 | +| docs/book/src/generated/bracket.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot examples/bracket.json`; gitignored) | MIT OR Apache-2.0 | +| docs/book/src/generated/enclosure.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/enclosure.json`; gitignored) | MIT OR Apache-2.0 | +| docs/book/src/generated/revolve.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/revolve.json`; gitignored) | MIT OR Apache-2.0 | +| docs/book/src/generated/sheet-metal.png | SolidCraft contributors | rendered by `cargo xtask book` (`solidcraft-cli snapshot docs/book/scripts/sheet-metal.json`; gitignored) | MIT OR Apache-2.0 | | crates/sketch/fonts/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in crates/sketch/fonts/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 | -| apps/solvecraft-web/fonts/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in apps/solvecraft-web/fonts/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 | +| apps/solidcraft-web/fonts/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in apps/solidcraft-web/fonts/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 | | tests/oracle/17-loft/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/17-loft/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 | | tests/oracle/23-bracket-multifeature/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/23-bracket-multifeature/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 | | tests/oracle/28-blind-pocket-fillets/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/28-blind-pocket-fillets/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 | @@ -69,25 +69,25 @@ Generated-in-code assets are original and have no file to list: ## Code adapted from sibling projects -| SolveCraft file | Adapted from | Licence | +| SolidCraft file | Adapted from | Licence | |---|---|---| | xtask/src/layers.rs | CADCraft `xtask/src/layers.rs` (dependency layering rules and tests) | MIT OR Apache-2.0 | -| apps/solvecraft/src/control_server.rs | CADCraft `apps/cadcraft/src/control_server.rs` (loopback JSON-lines server) | MIT OR Apache-2.0 | +| apps/solidcraft/src/control_server.rs | CADCraft `apps/cadcraft/src/control_server.rs` (loopback JSON-lines server) | MIT OR Apache-2.0 | | crates/ui-egui/src/lib.rs (screenshot queue, synthetic input) | CADCraft `crates/ui-egui/src/lib.rs` | MIT OR Apache-2.0 | | crates/ui-egui/src/control.rs (method dispatch and screenshot saving) | CADCraft `crates/ui-egui/src/control.rs` | MIT OR Apache-2.0 | | crates/ui-egui/src/gpu.rs (paint-callback structure) | CADCraft `crates/ui-egui/src/gpu.rs` | MIT OR Apache-2.0 | | crates/mcp/src/server.rs, crates/mcp/src/backend.rs (JSON-RPC framing, MCP lifecycle, control-port bridge) | CADCraft `crates/mcp/src/{server,backend}.rs` | MIT OR Apache-2.0 | | crates/mcp/src/tools.rs (tool schema helpers and argument checking) | CADCraft `crates/mcp/src/tools.rs`, GridCraft `crates/mcp/src/tools.rs` | MIT OR Apache-2.0 | | crates/io/src/threemf.rs (bounded zip part reading and writing) | GridCraft `crates/xlsx/src/package.rs` | MIT OR Apache-2.0 | -| crates/ui-egui/src/titlebar.rs (title bar drag, caption buttons, resize zones, their tests) and the window setup in apps/solvecraft/src/main.rs | VectorCraft `crates/ui-egui/src/titlebar.rs`, `apps/vectorcraft/src/main.rs` | MIT OR Apache-2.0 | +| crates/ui-egui/src/titlebar.rs (title bar drag, caption buttons, resize zones, their tests) and the window setup in apps/solidcraft/src/main.rs | VectorCraft `crates/ui-egui/src/titlebar.rs`, `apps/vectorcraft/src/main.rs` | MIT OR Apache-2.0 | | LICENSE-MIT, LICENSE-APACHE (licence text) | CADCraft | — | | .github/workflows/release.yml, .github/workflows/packaging-lint.yml and packaging/ (release pipeline: Linux AppImage/deb/rpm/Flatpak/tar.gz, FreeBSD, macOS dmg, Windows MSI/portable zip, web zip; AppRun, nfpm, Flatpak, WiX and Info.plist templates) | PhotoCraft (https://github.com/storytold/photocraft) `.github/workflows/{release,packaging-lint}.yml`, `packaging/` | Apache-2.0 | ## Recovered contributions -| SolveCraft file | Original contribution | Licence | +| SolidCraft file | Original contribution | Licence | |---|---|---| -| crates/ui-egui/src/viewport.rs (`nearest_quarter_turn`, `axis_view_orbit`) | N3MI-DG's [PR #101](https://github.com/bherbruck/solvecraft/pull/101): [minimal TOP/BOTTOM turns](https://github.com/bherbruck/solvecraft/commit/c28e780f52490784ef5886f094b9d3f6d10c3e3f) and [drag-to-orbit](https://github.com/bherbruck/solvecraft/commit/f30b30be6ecc4e2e6544a851243137c01a354934), adapted to the axis gizmo | MIT OR Apache-2.0 | +| crates/ui-egui/src/viewport.rs (`nearest_quarter_turn`, `axis_view_orbit`) | N3MI-DG's [PR #101](https://github.com/bherbruck/solidcraft/pull/101): [minimal TOP/BOTTOM turns](https://github.com/bherbruck/solidcraft/commit/c28e780f52490784ef5886f094b9d3f6d10c3e3f) and [drag-to-orbit](https://github.com/bherbruck/solidcraft/commit/f30b30be6ecc4e2e6544a851243137c01a354934), adapted to the axis gizmo | MIT OR Apache-2.0 | ## Vendored third-party code diff --git a/CLAUDE.md b/CLAUDE.md index 2e3c6308..b2003eea 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -6,13 +6,13 @@ kernel, and a desktop UI that agents can drive. Projects published by [storytold](https://github.com/storytold) can serve as references for implementation conventions. Any code adapted from other projects (MIT OR Apache-2.0) is noted in ATTRIBUTION.md. -## Product name and compatibility -SolidCraft is the displayed product name; `solidcraft` is the repository name. Keep existing -`solvecraft` crate/binary names, `.solvecraft` files, format markers, environment variables, -settings/recovery paths and package IDs compatible. Published SolveCraft release artifacts -and attribution records keep their historical names. Public docs/web/Flatpak deployments -use project Pages `/solidcraft/`; do not add permanent old-name Pages aliases. See README's -compatibility notes. +## Product name +SolidCraft everywhere: crates `solidcraft-*`, binaries `solidcraft` and `solidcraft-cli`, +`.solidcraft` designs, the `solidcraft/N` format tag, `SOLIDCRAFT_*` environment variables, +settings and recovery folders, the MCP server name, docs, CI and packaging. The old name +survives only in the explicit compatibility shims listed in `docs/compatibility.md`; don't add +new uses of it anywhere else. Public docs/web/Flatpak deployments use project Pages +`/solidcraft/`; do not add permanent old-name Pages aliases. ## Start every session here 1. Read `plan/STATUS.md` (current milestone, next task), then the task in `plan/execution-plan.md` @@ -33,15 +33,15 @@ People trust SolidCraft with their designs; a crash loses their work. **This out - **Input-derived numbers are hostile** (command parameters, design files, control-channel input): `get()` not `[i]`, finite checks, caps on sizes and loop counts. - **Third-party kernel calls are guarded:** everything that calls `truck` goes through - `solvecraft_kernel::guard`, which turns a panic into `KernelError::Internal`. + `solidcraft_kernel::guard`, which turns a panic into `KernelError::Internal`. - **Last-resort guard:** `Session::execute` runs every command under `catch_unwind`; an escaped panic restores the design and reports `EngineError::Internal`. - **Crash reports:** the desktop app writes every panic and every unclean exit to a local crash folder, - with the design and the commands that reproduce it (`solvecraft_engine::crash`, docs/crash-reports.md). + with the design and the commands that reproduce it (`solidcraft_engine::crash`, docs/crash-reports.md). When someone says the app crashed, look there first: `solidcraft-cli crashes`, `crashes show `, `crashes replay ` (or the `diagnostics.crash_reports` command / MCP `crash_reports` tool). - **Prove it:** every crash fix lands with a regression test (see `hostile_params_never_panic`); a - crash report's `design.solvecraft` + `replay.json` is the test's starting point. + crash report's `design.solidcraft` + `replay.json` is the test's starting point. - Every production crate root carries `#![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)]`. @@ -104,9 +104,9 @@ before the draft is verified. - `{"id":2,"method":"ui.start","params":{"command":"sketch.create"}}` then `ui.click {x, y}` - `{"id":3,"method":"ui.screenshot","params":{"path":"/tmp/shot.png"}}` then read the PNG. - **For UI work, look at the result** (screenshot, read the PNG). Without a window use - `ui.render` or `solvecraft-cli snapshot`. -- Headless: `solvecraft-cli run script.json --out part.step`, `solvecraft-cli eval design.solvecraft`, - `solvecraft-cli snapshot script.json --out shot.png`, `solvecraft-cli commands`. + `ui.render` or `solidcraft-cli snapshot`. +- Headless: `solidcraft-cli run script.json --out part.step`, `solidcraft-cli eval design.solidcraft`, + `solidcraft-cli snapshot script.json --out shot.png`, `solidcraft-cli commands`. - Reference tests: `cargo xtask oracle` replays `tests/oracle/*/recipe.json` and compares with each case's `expected.json` (analytic or FreeCAD values; writes `docs/oracle.md`). Expected values never come from a commercial CAD product. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c345d5a5..6208a4a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,14 +22,14 @@ cargo xtask test # unit, engine and UI-scenari cargo xtask ci # everything a PR must pass (see below) ``` -SolidCraft is the product and repository name. Cargo packages and executables retain -their `solvecraft` names for compatibility with existing scripts and MCP clients. +SolidCraft is the product, repository, Cargo package and executable name. Build the desktop app with `cargo build --release -p solidcraft`, or the CLI with `cargo build --release -p solidcraft-cli`. Run the CLI with `cargo run --release -p solidcraft-cli -- commands` (or `target/release/solidcraft-cli commands` -after building). There is no Cargo package named `solidcraft`. +after building). The old name appears only in the compatibility shims listed in +[docs/compatibility.md](docs/compatibility.md); don't add new uses of it. -Web: `cd apps/solvecraft-web && trunk serve --release`, then open the printed URL (`?sample` +Web: `cd apps/solidcraft-web && trunk serve --release`, then open the printed URL (`?sample` opens the sample part, `?webgl` forces WebGL2). Saving and opening files in the browser needs HTTPS or localhost, because it uses the Origin Private File System. @@ -43,7 +43,7 @@ loader wgpu renders through, and libdbus-1, which rfd's file dialogs reach xdg-d through (a dialog then never appears and the pick returns no path): ``` -ERROR solvecraft: winit EventLoopError: ... The wayland library could not be loaded +ERROR solidcraft: winit EventLoopError: ... The wayland library could not be loaded ERROR eframe::native::run: Exiting because of error: WGPU error: Failed to create surface for any enabled backend: {} ERROR rfd::backend::xdg_desktop_portal::portal::libdbus: Can't connect to a portal: libdbus-1.so not found ``` @@ -59,8 +59,8 @@ The default development shell retains the Rust/C toolchain already on `PATH`; To launch, use `nix run .#solidcraft -- --sample`. To install the app's terminal commands, desktop entry and generated icons, use `nix profile install .#solidcraft`. `nix build` alone creates a build result; it does not install a launcher in your -desktop menu. The installed `solidcraft` command and compatible `solvecraft` -command run the same wrapped app; `solvecraft-cli` is included as well. See +desktop menu. The installed `solidcraft` command runs the wrapped app; `solidcraft-cli` is included as +well. See [Nix installation and troubleshooting](docs/nix.md) for remote-flake commands, desktop integration and how to distinguish an old executable on `PATH`. @@ -82,7 +82,7 @@ The [README](README.md#architecture) has the layer table. The short version: commands. - `crates/ui-egui`: the desktop/web front end (egui + wgpu). It's swappable; nothing below it depends on egui. -- `crates/mcp`, `apps/solvecraft`, `apps/solvecraft-cli`, `apps/solvecraft-web`. +- `crates/mcp`, `apps/solidcraft`, `apps/solidcraft-cli`, `apps/solidcraft-web`. `cargo xtask layers` fails the build if a lower layer depends on a higher one. @@ -107,7 +107,7 @@ The [README](README.md#architecture) has the layer table. The short version: - Non-test code never uses `unwrap`, `expect`, `panic!` or indexing on input-derived data. There's no `unsafe`, and clippy denies it. - An unsupported case returns an error ("not supported yet: …"); it never panics. - - Every call into truck goes through `solvecraft_kernel::guard`. + - Every call into truck goes through `solidcraft_kernel::guard`. - Every crash fix comes with a regression test. 5. **Everything is a command, with a test.** A new feature means a `CommandSpec` in `crates/engine/src/cmd/`, engine tests that measure the result (volume, area, @@ -159,12 +159,12 @@ Pull requests skip a few heavy tests (one to four minutes each), which run on ev `main` and nightly; check the `main` run after merging. They are listed in `.config/nextest.toml` and the [testing page](docs/book/src/dev/testing.md): -- `solvecraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`, +- `solidcraft-engine`: `tests::hostile_params_never_panic`, everything in `tests::robustness`, `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` -- `solvecraft-kernel`: `tests::boolean_identity_on_tangent_primitives` -- `solvecraft-ui-egui` scenarios: `acceptance_sensor_enclosure` +- `solidcraft-kernel`: `tests::boolean_identity_on_tangent_primitives` +- `solidcraft-ui-egui` scenarios: `acceptance_sensor_enclosure` **The reference tests** are 76 parts in [`tests/oracle/`](tests/oracle/), each a `recipe.json` in our own format with an `expected.json` beside it. SolidCraft rebuilds the recipe and compares diff --git a/Cargo.lock b/Cargo.lock index bca12999..705c9c00 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3300,8 +3300,8 @@ dependencies = [ "quick-xml 0.38.4", "rfd", "serde_json", - "solvecraft-engine", - "solvecraft-ui-egui", + "solidcraft-engine", + "solidcraft-ui-egui", "winit", ] @@ -3310,43 +3310,43 @@ name = "solidcraft-cli" version = "0.5.0" dependencies = [ "serde_json", - "solvecraft-engine", - "solvecraft-mcp", + "solidcraft-engine", + "solidcraft-mcp", ] [[package]] -name = "solvecraft-doc" +name = "solidcraft-doc" version = "0.5.0" dependencies = [ "log", "serde", "serde_json", "sha2", - "solvecraft-geom", - "solvecraft-kernel", - "solvecraft-sketch", + "solidcraft-geom", + "solidcraft-kernel", + "solidcraft-sketch", "thiserror 2.0.21", "web-time", ] [[package]] -name = "solvecraft-engine" +name = "solidcraft-engine" version = "0.5.0" dependencies = [ "log", "serde", "serde_json", - "solvecraft-doc", - "solvecraft-geom", - "solvecraft-io", - "solvecraft-kernel", - "solvecraft-render", - "solvecraft-sketch", + "solidcraft-doc", + "solidcraft-geom", + "solidcraft-io", + "solidcraft-kernel", + "solidcraft-render", + "solidcraft-sketch", "thiserror 2.0.21", ] [[package]] -name = "solvecraft-geom" +name = "solidcraft-geom" version = "0.5.0" dependencies = [ "serde", @@ -3355,24 +3355,24 @@ dependencies = [ ] [[package]] -name = "solvecraft-io" +name = "solidcraft-io" version = "0.5.0" dependencies = [ "quick-xml 0.38.4", - "solvecraft-doc", - "solvecraft-geom", - "solvecraft-kernel", + "solidcraft-doc", + "solidcraft-geom", + "solidcraft-kernel", "thiserror 2.0.21", "zip", ] [[package]] -name = "solvecraft-kernel" +name = "solidcraft-kernel" version = "0.5.0" dependencies = [ "log", "serde", - "solvecraft-geom", + "solidcraft-geom", "thiserror 2.0.21", "truck-meshalgo", "truck-modeling", @@ -3382,36 +3382,36 @@ dependencies = [ ] [[package]] -name = "solvecraft-mcp" +name = "solidcraft-mcp" version = "0.5.0" dependencies = [ "serde_json", - "solvecraft-engine", + "solidcraft-engine", ] [[package]] -name = "solvecraft-render" +name = "solidcraft-render" version = "0.5.0" dependencies = [ "image", "serde", "serde_json", - "solvecraft-geom", + "solidcraft-geom", ] [[package]] -name = "solvecraft-sketch" +name = "solidcraft-sketch" version = "0.5.0" dependencies = [ "serde", "serde_json", - "solvecraft-geom", + "solidcraft-geom", "thiserror 2.0.21", "ttf-parser", ] [[package]] -name = "solvecraft-ui-egui" +name = "solidcraft-ui-egui" version = "0.5.0" dependencies = [ "egui", @@ -3420,13 +3420,13 @@ dependencies = [ "log", "serde", "serde_json", - "solvecraft-engine", + "solidcraft-engine", "ttf-parser", "web-time", ] [[package]] -name = "solvecraft-web" +name = "solidcraft-web" version = "0.5.0" dependencies = [ "eframe", @@ -3434,8 +3434,8 @@ dependencies = [ "js-sys", "log", "serde_json", - "solvecraft-engine", - "solvecraft-ui-egui", + "solidcraft-engine", + "solidcraft-ui-egui", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", diff --git a/Cargo.toml b/Cargo.toml index 3b7ea3c1..ccc24d0f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,15 +14,15 @@ repository = "https://github.com/bherbruck/solidcraft" [workspace.dependencies] # internal -solvecraft-geom = { path = "crates/geom" } -solvecraft-sketch = { path = "crates/sketch" } -solvecraft-kernel = { path = "crates/kernel" } -solvecraft-render = { path = "crates/render" } -solvecraft-doc = { path = "crates/doc" } -solvecraft-io = { path = "crates/io" } -solvecraft-engine = { path = "crates/engine" } -solvecraft-ui-egui = { path = "crates/ui-egui" } -solvecraft-mcp = { path = "crates/mcp" } +solidcraft-geom = { path = "crates/geom" } +solidcraft-sketch = { path = "crates/sketch" } +solidcraft-kernel = { path = "crates/kernel" } +solidcraft-render = { path = "crates/render" } +solidcraft-doc = { path = "crates/doc" } +solidcraft-io = { path = "crates/io" } +solidcraft-engine = { path = "crates/engine" } +solidcraft-ui-egui = { path = "crates/ui-egui" } +solidcraft-mcp = { path = "crates/mcp" } # foundation thiserror = "2" @@ -96,7 +96,7 @@ codegen-units = 16 # binary). See docs/crash-reports.md. debug = "line-tables-only" -# truck-shapeops with SolveCraft fixes (Apache-2.0, see vendor/truck-shapeops and ATTRIBUTION.md). +# truck-shapeops with SolidCraft fixes (Apache-2.0, see vendor/truck-shapeops and ATTRIBUTION.md). [patch.crates-io] truck-shapeops = { path = "vendor/truck-shapeops" } truck-stepio = { path = "vendor/truck-stepio" } diff --git a/LICENSE-MIT b/LICENSE-MIT index d19e8efe..bd0e0f26 100644 --- a/LICENSE-MIT +++ b/LICENSE-MIT @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2026 SolveCraft contributors +Copyright (c) 2026 SolidCraft contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NOTICE b/NOTICE index a3067f13..af0676c1 100644 --- a/NOTICE +++ b/NOTICE @@ -1,5 +1,5 @@ -SolveCraft -Copyright (c) 2026 SolveCraft contributors. +SolidCraft +Copyright (c) 2026 SolidCraft contributors. Licensed under the MIT License or the Apache License, Version 2.0, at your option (LICENSE-MIT, LICENSE-APACHE). @@ -7,10 +7,10 @@ Third-party material Software dependencies are listed in Cargo.lock; their licences are in THIRD-PARTY-LICENSES.txt (`cargo xtask licences`), which the programs also carry (Help > About > Licences, -`solvecraft-cli licences`). The geometry kernel builds on the truck crates +`solidcraft-cli licences`). The geometry kernel builds on the truck crates (truck-modeling, truck-shapeops, truck-meshalgo, truck-stepio, truck-topology and their dependencies, https://github.com/ricosjp/truck), licensed under the Apache License 2.0 and used -as libraries. truck-shapeops 0.4.0 is vendored in vendor/truck-shapeops with SolveCraft +as libraries. truck-shapeops 0.4.0 is vendored in vendor/truck-shapeops with SolidCraft modifications (listed in vendor/README.md and marked in the source); its licence is in vendor/truck-shapeops/LICENSE. Copyright (c) RICOS Co. Ltd. @@ -23,6 +23,6 @@ Most bundled assets are drawn in code or contributed originally; third-party fon for sketch text, BIZ UDPGothic for web Japanese display) are licensed under SIL OFL 1.1. The per-file list is in ATTRIBUTION.md. -SolveCraft is an independent project and is not affiliated with, sponsored by or endorsed by +SolidCraft is an independent project and is not affiliated with, sponsored by or endorsed by Autodesk, Inc. Autodesk and Fusion are trademarks of Autodesk, Inc.; they are used here only to describe compatibility goals. diff --git a/README.md b/README.md index 316e8669..46d6be2b 100644 --- a/README.md +++ b/README.md @@ -27,14 +27,12 @@ and help is welcome: see [CONTRIBUTING.md](CONTRIBUTING.md). ## Name and compatibility -SolidCraft was previously called SolveCraft. The product display name and repository are -changing; existing identifiers remain compatible: `.solvecraft` designs, `solvecraft-cli`, -Cargo package names, settings/recovery locations and package IDs keep their existing names. -Published 0.2.0 and 0.2.1 packages and demo recordings retain their original SolveCraft branding -and filenames. - -On macOS, the DMG contains `SolidCraft.app`. To replace an older `SolveCraft.app` -without installing a second copy, follow the [macOS upgrade instructions](https://bherbruck.github.io/solidcraft/guide/getting-started.html#macos). +SolidCraft was renamed from an earlier working name. Since 0.5.0 everything uses the new +name: the `solidcraft` and `solidcraft-cli` programs, `.solidcraft` designs, `SOLIDCRAFT_*` +environment variables, settings folders and the `solidcraft` MCP server. Upgrading keeps +working: designs, settings, recovery files, environment variables and CLI/MCP setups from +earlier builds carry over. Every compatibility shim is listed in +[docs/compatibility.md](docs/compatibility.md). Use the SolidCraft documentation and web-app URLs above. GitHub redirects links to the renamed repository; old-name Pages and Flatpak URLs are not maintained as permanent aliases. @@ -77,7 +75,7 @@ renamed repository; old-name Pages and Flatpak URLs are not maintained as perman retained shallow prismatic pockets, exact line/arc/circle DXF outlines and bend tables; plastic enclosure features and rules. Pocket depth and side survive unfold/refold and appear separately from through-hole outlines in the flat preview and DXF. -- **Files**: designs as JSON (`.solvecraft`), export to STEP (AP242 with body names, colours and +- **Files**: designs as JSON (`.solidcraft`), export to STEP (AP242 with body names, colours and components as assemblies; revolved faces as native planes, cylinders, cones, spheres and tori), IGES 5.3 (manifold solid B-reps with names and colours), 3MF, STL (binary/ASCII) and OBJ; 3MF, STL and OBJ import as mesh bodies (they render, measure, move and export; solid features @@ -92,9 +90,9 @@ renamed repository; old-name Pages and Flatpak URLs are not maintained as perman control channel so agents and tests can drive it. Feature errors and Help can open an editable **Create Issue** draft with relevant context and an optional reviewed screenshot; opening GitHub does not submit the report. -- **Headless CLI** `solvecraft-cli`: run command scripts, measure (volume, area, centre of mass, +- **Headless CLI** `solidcraft-cli`: run command scripts, measure (volume, area, centre of mass, face/edge/vertex counts), export, render PNG snapshots and replay reference recipes. -- **MCP server** `solvecraft-cli mcp`: AI agents model, measure and look at parts headless or +- **MCP server** `solidcraft-cli mcp`: AI agents model, measure and look at parts headless or in the running app (see below). ## Build and run @@ -130,7 +128,7 @@ sandbox does not expose hidraw devices, so the 3D mouse only works with native b Devices that send all six axes in HID report 1 (some homemade ones do) are recognised from their report descriptor, so they work as well. -In the browser: `cd apps/solvecraft-web && trunk build --release --public-url ./` (WebGPU, or +In the browser: `cd apps/solidcraft-web && trunk build --release --public-url ./` (WebGPU, or WebGL2 with `?webgl`; `?sample` opens the sample part). ## Scripts @@ -148,12 +146,12 @@ A script is a JSON list of commands, the same ones the UI runs: ## AI agents (MCP) -`solvecraft-cli mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server on +`solidcraft-cli mcp` is a [Model Context Protocol](https://modelcontextprotocol.io) server on stdio. Headless it runs its own design session; with `--connect 127.0.0.1:PORT` it drives the app -started with `solvecraft --control PORT`, so you can watch the agent model. Tools: +started with `solidcraft --control PORT`, so you can watch the agent model. Tools: `list_commands`, `execute`, `batch`, `inspect_design`, `measure`, `body_topology`, `set_parameter`, `screenshot` (returns the image), `export`, `undo`, `redo`, `new_design`, `open`, -`save`. Claude Code: `claude mcp add solvecraft -- solvecraft-cli mcp`. Details, client +`save`. Claude Code: `claude mcp add solidcraft -- solidcraft-cli mcp`. Details, client configuration and an example session: [docs/mcp.md](docs/mcp.md). ## Architecture @@ -167,7 +165,7 @@ configuration and an example session: [docs/mcp.md](docs/mcp.md). | L3 | `io` | design files, STL/OBJ/STEP/3MF export, STEP and 3MF/STL import features | | L4 | `engine` | the session and the command registry (everything is a command) | | L5 | `ui-egui`, `mcp` | the swappable desktop front end; the MCP server (headless or bridged to the app) | -| apps | `solvecraft`, `solvecraft-cli` | desktop app, headless CLI | +| apps | `solidcraft`, `solidcraft-cli` | desktop app, headless CLI | ## Contributing diff --git a/ROADMAP.md b/ROADMAP.md index dfc15f91..fe544244 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -57,7 +57,7 @@ PCB / Package, Render, Animation, Simulation, Manufacture/CAM, Drawings. | M11 | 2D drawings | deferred | — | | M12 | Release: cross-platform packages, verification and deliberate publication implemented; original current-UI demo refreshed. Trusted distribution signing and broader performance work remain | in progress | 16 | | M14 | Configurations: a table of rows (parameter expressions and feature suppressions per row) switched with config.activate (undoable, all or nothing), kept through parameter renames and feature deletes (config.configure, config.table, config.column/row/activate). Per-row body materials and appearances (library looks, custom colour/opacity and clearing overrides), undoable activation and save/reopen: done. Ordered typed configuration rules, atomic validation and structured rule editing: done. Component appearance and certified semantic face style columns, atomic preview/apply and independent-copy remapping: done. Exact constructed sketch-side and immutable imported-face lineage now preserve legacy names and reject retargeting. Left: ambiguous shifted/two-sided caps | in progress | 4 | -| M13 | Never lose work: atomic saves (temporary file, flush, rename; a crash at any moment leaves the old file or the new one) with the replaced version kept as `.bak`; autosave of unsaved changes to the recovery folder every 5 minutes (preference) and after big operations, only when something changed; "Recover unsaved design?" on the next launch (`doc.recovery_list`, `doc.recover`, `doc.recovery_discard`); a kill-mid-save test (process killed while saving a 0.5 MB design 25 times). File format: `"format": "solvecraft/12"`, older formats upgraded step by step (`doc::format`), newer ones refused with a message, out-of-range numbers and broken placements refused; a corpus of designs saved by M0, components-era, joints-era and current builds (apps/solvecraft-cli/tests/corpus) that must open and evaluate as saved; damaged and hostile variants (truncation, bit flips, hostile values, removed keys, repeated features, deep nesting) never panic | done | 6 | +| M13 | Never lose work: atomic saves (temporary file, flush, rename; a crash at any moment leaves the old file or the new one) with the replaced version kept as `.bak`; autosave of unsaved changes to the recovery folder every 5 minutes (preference) and after big operations, only when something changed; "Recover unsaved design?" on the next launch (`doc.recovery_list`, `doc.recover`, `doc.recovery_discard`); a kill-mid-save test (process killed while saving a 0.5 MB design 25 times). File format: `"format": "solidcraft/12"`, older formats upgraded step by step (`doc::format`), newer ones refused with a message, out-of-range numbers and broken placements refused; a corpus of designs saved by M0, components-era, joints-era and current builds (apps/solidcraft-cli/tests/corpus) that must open and evaluate as saved; damaged and hostile variants (truncation, bit flips, hostile values, removed keys, repeated features, deep nesting) never panic | done | 6 | ## Dimensioning and error reporting diff --git a/THIRD-PARTY-LICENSES.txt b/THIRD-PARTY-LICENSES.txt index 001e8289..b6a2e4c5 100644 --- a/THIRD-PARTY-LICENSES.txt +++ b/THIRD-PARTY-LICENSES.txt @@ -1,6 +1,6 @@ Third-party software in SolidCraft -SolidCraft (the desktop app, solvecraft-cli and the web app) is built from the 454 crates below. +SolidCraft (the desktop app, solidcraft-cli and the web app) is built from the 454 crates below. Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files. SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE. @@ -13354,7 +13354,7 @@ Bundled fonts (SIL Open Font License, Version 1.1) Copyright 2012 Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) Source: https://github.com/liberationfonts -- BIZUDPGothic-Regular.ttf (apps/solvecraft-web/fonts/BIZUDPGothic-Regular.ttf) +- BIZUDPGothic-Regular.ttf (apps/solidcraft-web/fonts/BIZUDPGothic-Regular.ttf) Copyright 2022 The BIZ UDGothic Project Authors (https://github.com/googlefonts/morisawa-biz-ud-mincho) Source: https://github.com/googlefonts/morisawa-biz-ud-gothic diff --git a/apps/solvecraft-cli/Cargo.toml b/apps/solidcraft-cli/Cargo.toml similarity index 88% rename from apps/solvecraft-cli/Cargo.toml rename to apps/solidcraft-cli/Cargo.toml index aacadb60..3e4f01e1 100644 --- a/apps/solvecraft-cli/Cargo.toml +++ b/apps/solidcraft-cli/Cargo.toml @@ -20,6 +20,6 @@ name = "solvecraft-cli" path = "src/alias.rs" [dependencies] -solvecraft-engine = { workspace = true } -solvecraft-mcp = { workspace = true } +solidcraft-engine = { workspace = true } +solidcraft-mcp = { workspace = true } serde_json = { workspace = true } diff --git a/apps/solvecraft-cli/src/alias.rs b/apps/solidcraft-cli/src/alias.rs similarity index 100% rename from apps/solvecraft-cli/src/alias.rs rename to apps/solidcraft-cli/src/alias.rs diff --git a/apps/solvecraft-cli/src/crashes.rs b/apps/solidcraft-cli/src/crashes.rs similarity index 98% rename from apps/solvecraft-cli/src/crashes.rs rename to apps/solidcraft-cli/src/crashes.rs index bae7f60c..01288fbf 100644 --- a/apps/solvecraft-cli/src/crashes.rs +++ b/apps/solidcraft-cli/src/crashes.rs @@ -4,7 +4,7 @@ use std::sync::Mutex; use serde_json::{Value, json}; -use solvecraft_engine::crash; +use solidcraft_engine::crash; const USAGE: &str = "usage: solidcraft-cli crashes [list [--json] | show | replay | dir | clear []]"; @@ -58,7 +58,7 @@ fn replay(report: &Value) -> Result<(), String> { let commands = script["commands"].as_array().cloned().unwrap_or_default(); let mut s = match report["design_path"].as_str() { Some(d) => super::load(d)?, - None => solvecraft_engine::Session::default(), + None => solidcraft_engine::Session::default(), }; std::panic::set_hook(Box::new(|info| { let loc = info.location().map_or_else(|| "(unknown)".to_string(), |l| format!("{}:{}", l.file(), l.line())); diff --git a/apps/solvecraft-cli/src/fuzz.rs b/apps/solidcraft-cli/src/fuzz.rs similarity index 98% rename from apps/solvecraft-cli/src/fuzz.rs rename to apps/solidcraft-cli/src/fuzz.rs index 6c76fede..fcd5756e 100644 --- a/apps/solvecraft-cli/src/fuzz.rs +++ b/apps/solidcraft-cli/src/fuzz.rs @@ -9,8 +9,8 @@ use std::process::{Command, Stdio}; use std::time::{Duration, Instant}; use serde_json::{Value, json}; -use solvecraft_engine::fuzz; -use solvecraft_engine::fuzz::boolean_case; +use solidcraft_engine::fuzz; +use solidcraft_engine::fuzz::boolean_case; fn flag(args: &[String], name: &str, default: T) -> T { args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).and_then(|v| v.parse().ok()).unwrap_or(default) @@ -45,7 +45,7 @@ pub fn one(args: &[String]) -> Result<(), String> { design["minimal"] = json!({"outcome": m.outcome, "steps": m.steps.iter().map(|s| s.kind).collect::>(), "script": m.script()}); } } - let (a, b) = solvecraft_engine::fuzz::boolean_shapes(seed); + let (a, b) = solidcraft_engine::fuzz::boolean_shapes(seed); eprintln!("boolean {seed} {} {}", serde_json::to_string(&a).unwrap_or_default(), serde_json::to_string(&b).unwrap_or_default()); let b = boolean_case(seed); let bp = PANICS.swap(0, std::sync::atomic::Ordering::SeqCst); diff --git a/apps/solvecraft-cli/src/main.rs b/apps/solidcraft-cli/src/main.rs similarity index 87% rename from apps/solvecraft-cli/src/main.rs rename to apps/solidcraft-cli/src/main.rs index 82c9ca61..a067afb9 100644 --- a/apps/solvecraft-cli/src/main.rs +++ b/apps/solidcraft-cli/src/main.rs @@ -1,16 +1,16 @@ -//! SolveCraft headless command line. +//! SolidCraft headless command line. //! //! ```text -//! solidcraft-cli run [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] -//! solidcraft-cli eval (alias: inspect) measurements as JSON -//! solidcraft-cli bench-open [--parse-only] loading timings as JSON lines +//! solidcraft-cli run [--in design|part.step|mesh.3mf] [--out FILE]... [--save FILE] [--quiet] +//! solidcraft-cli eval (alias: inspect) measurements as JSON +//! solidcraft-cli bench-open [--parse-only] loading timings as JSON lines //! solidcraft-cli snapshot --out shot.png [--width W] [--height H] [--view iso|front|top|…] //! solidcraft-cli exec [json-params] run one command on an empty design //! solidcraft-cli commands the command registry as JSON //! solidcraft-cli recipe translate an oracle recipe to a script //! solidcraft-cli oracle ... replay recipes and compare with expected.json //! solidcraft-cli mcp [--in design|script] [--connect HOST:PORT] MCP server on stdio (docs/mcp.md) -//! solidcraft-cli licences SolveCraft's licence and the third-party licences +//! solidcraft-cli licences SolidCraft's licence and the third-party licences //! solidcraft-cli icon --out F.png|F.ico [--size N] the app icon (packaging; drawn, not a file) //! ``` #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] @@ -28,21 +28,21 @@ use serde_json::{Value, json}; #[path = "../../app_icon.rs"] mod app_icon; -use solvecraft_engine::Session; -use solvecraft_engine::render::{StandardView, render_png}; +use solidcraft_engine::Session; +use solidcraft_engine::render::{StandardView, render_png}; const USAGE: &str = "usage: - solidcraft-cli run [--in design.solvecraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] - solidcraft-cli run --in [--out FILE]... - solidcraft-cli eval (alias: inspect; or --in FILE) - solidcraft-cli bench-open [--parse-only] + solidcraft-cli run [--in design.solidcraft|part.step] [--out FILE]... [--save FILE] [--quiet] [--results] + solidcraft-cli run --in [--out FILE]... + solidcraft-cli eval (alias: inspect; or --in FILE) + solidcraft-cli bench-open [--parse-only] solidcraft-cli snapshot --out shot.png [--width W] [--height H] [--view iso|front|back|top|bottom|left|right] solidcraft-cli exec [json-params] solidcraft-cli commands solidcraft-cli recipe solidcraft-cli oracle ... [--json] solidcraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE] - solidcraft-cli mcp [--in design.solvecraft|script.json] [--connect 127.0.0.1:PORT] + solidcraft-cli mcp [--in design.solidcraft|script.json] [--connect 127.0.0.1:PORT] solidcraft-cli crashes [list [--json] | show | replay | dir | clear []] solidcraft-cli licences solidcraft-cli icon --out icon.png [--size 256] | --out icon.ico @@ -81,7 +81,7 @@ fn main() -> ExitCode { Some("licences" | "licenses") => { // Written, not print!ed: a closed pipe (`| head`) is not a crash. use std::io::Write as _; - let _ = std::io::stdout().lock().write_all(solvecraft_engine::licences::all().as_bytes()); + let _ = std::io::stdout().lock().write_all(solidcraft_engine::licences::all().as_bytes()); Ok(()) } // Hidden, for the kill-mid-save test: save a large design over and over. @@ -111,19 +111,19 @@ fn pretty(v: &Value) -> String { pub fn read_json(path: &str) -> Result { // Through the engine's file access, which refuses anything that is not a regular file and // reads no more than the cap: a character device or a FIFO would otherwise never end. - let s = solvecraft_engine::io::vfs::read_to_string(path, solvecraft_engine::io::MAX_DESIGN_BYTES).map_err(|e| format!("{path}: {e}"))?; + let s = solidcraft_engine::io::vfs::read_to_string(path, solidcraft_engine::io::MAX_DESIGN_BYTES).map_err(|e| format!("{path}: {e}"))?; serde_json::from_str(&s).map_err(|e| format!("{path}: {e}")) } /// A session from a design file, a STEP/3MF/STL file, a command script, or an oracle recipe. pub fn load(path: &str) -> Result { let mut s = Session::default(); - if solvecraft_engine::io::is_step_path(path) || solvecraft_engine::io::is_mesh_path(path) { + 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); } let v = read_json(path)?; - if v.get("format").and_then(Value::as_str).is_some_and(|f| f.starts_with("solvecraft")) { + if v.get("format").and_then(Value::as_str).is_some_and(|f| solidcraft_engine::doc::format::version_of(f).is_some()) { s.execute("doc.open", &json!({"path": path})).map_err(|e| e.to_string())?; } else if v.get("features").is_some() && v.get("commands").is_none() { let script = recipe::to_script(&v)?; @@ -220,11 +220,11 @@ fn cmd_snapshot(args: &[String]) -> Result<(), String> { let w = flag(args, "--width").and_then(|x| x.parse().ok()).unwrap_or(1280usize); let h = flag(args, "--height").and_then(|x| x.parse().ok()).unwrap_or(800usize); let s = load(path)?; - let mut cam = solvecraft_engine::view::home_camera(&s); + let mut cam = solidcraft_engine::view::home_camera(&s); if let Some(v) = flag(args, "--view") { cam.set_view(StandardView::parse(v).ok_or_else(|| format!("unknown view `{v}`"))?); } - let scene = solvecraft_engine::view::scene(&s, &cam); + let scene = solidcraft_engine::view::scene(&s, &cam); let png = render_png(&scene, &cam, w, h).ok_or("render failed")?; std::fs::write(out, &png).map_err(|e| format!("{out}: {e}"))?; println!("{}", pretty(&json!({"path": out, "width": w, "height": h, "bytes": png.len()}))); @@ -249,7 +249,7 @@ fn cmd_icon(args: &[String]) -> Result<(), String> { } fn icon_png(size: usize) -> Result, String> { - solvecraft_engine::render::png(size, size, app_icon::rgba(size)?).ok_or_else(|| "icon: PNG encoding failed".to_string()) + solidcraft_engine::render::png(size, size, app_icon::rgba(size)?).ok_or_else(|| "icon: PNG encoding failed".to_string()) } /// A Windows icon file holding PNG images (Vista and later read these). @@ -287,21 +287,21 @@ fn cmd_exec(args: &[String]) -> Result<(), String> { /// MCP server on stdio: headless (optionally starting from a design or script) or bridged to /// a running app's control port. stdout carries only protocol messages. fn cmd_mcp(args: &[String]) -> Result<(), String> { - let backend: Box = match (flag(args, "--connect"), flag(args, "--in")) { + let backend: Box = match (flag(args, "--connect"), flag(args, "--in")) { (Some(_), Some(_)) => return Err("mcp: use either --connect or --in, not both".into()), - (Some(addr), None) => Box::new(solvecraft_mcp::Remote::connect(addr).map_err(|e| { + (Some(addr), None) => Box::new(solidcraft_mcp::Remote::connect(addr).map_err(|e| { let port = addr.rsplit(':').next().filter(|p| p.parse::().is_ok()).unwrap_or("PORT"); format!( "cannot connect to {addr}: {e}\nno SolidCraft app is listening there; start it with \ - `solvecraft --control {port}` (or SOLVECRAFT_CONTROL_PORT={port}) and retry" + `solidcraft --control {port}` (or SOLIDCRAFT_CONTROL_PORT={port}) and retry" ) })?), - (None, Some(path)) => Box::new(solvecraft_mcp::Headless::new(load(path)?)), - (None, None) => Box::new(solvecraft_mcp::Headless::default()), + (None, Some(path)) => Box::new(solidcraft_mcp::Headless::new(load(path)?)), + (None, None) => Box::new(solidcraft_mcp::Headless::default()), }; let stdin = std::io::stdin(); let stdout = std::io::stdout(); - solvecraft_mcp::Server::new(backend).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string()) + solidcraft_mcp::Server::new(backend).serve(stdin.lock(), stdout.lock()).map_err(|e| e.to_string()) } /// `save-stress `: save a design of a few hundred kilobytes `rounds` times, its @@ -311,7 +311,7 @@ fn cmd_save_stress(args: &[String]) -> Result<(), String> { return Err("usage: solidcraft-cli save-stress ".into()); }; let mut s = Session::default(); - let filler = (0..3000).map(|i| solvecraft_engine::doc::Param { + let filler = (0..3000).map(|i| solidcraft_engine::doc::Param { name: format!("p{i}"), expr: format!("{i} mm + 0.5 mm"), unit: "mm".into(), @@ -358,21 +358,21 @@ fn cmd_bench_edit(args: &[String]) -> Result<(), String> { /// Separate file reading, JSON decoding/repair, timeline evaluation and display meshing. fn cmd_bench_open(args: &[String]) -> Result<(), String> { - let path = args.first().ok_or("usage: solidcraft-cli bench-open [--parse-only]")?; + let path = args.first().ok_or("usage: solidcraft-cli bench-open [--parse-only]")?; let t = std::time::Instant::now(); - let step = solvecraft_engine::io::is_step_path(path); - let limit = if step { solvecraft_engine::io::MAX_STEP_BYTES } else { solvecraft_engine::io::MAX_DESIGN_BYTES }; - let bytes = solvecraft_engine::io::vfs::read(path, limit).map_err(|e| format!("{path}: {e}"))?; + let step = solidcraft_engine::io::is_step_path(path); + let limit = if step { solidcraft_engine::io::MAX_STEP_BYTES } else { solidcraft_engine::io::MAX_DESIGN_BYTES }; + let bytes = solidcraft_engine::io::vfs::read(path, limit).map_err(|e| format!("{path}: {e}"))?; let read_ms = t.elapsed().as_secs_f64() * 1000.0; println!("{}", json!({"step": "read", "ms": read_ms, "bytes": bytes.len()})); let t = std::time::Instant::now(); let doc = if step { - let imported = solvecraft_engine::io::step_import_feature(&bytes, path).map_err(|e| e.to_string())?; - let mut doc = solvecraft_engine::doc::Document::new(&imported.name); + let imported = solidcraft_engine::io::step_import_feature(&bytes, path).map_err(|e| e.to_string())?; + let mut doc = solidcraft_engine::doc::Document::new(&imported.name); doc.add_feature(imported.kind, Some(&imported.name)).map_err(|e| e.to_string())?; doc } else { - solvecraft_engine::io::read_design(&bytes).map_err(|e| e.to_string())? + solidcraft_engine::io::read_design(&bytes).map_err(|e| e.to_string())? }; let parse_ms = t.elapsed().as_secs_f64() * 1000.0; println!("{}", json!({"step": "parse", "ms": parse_ms, "features": doc.features.len()})); diff --git a/apps/solvecraft-cli/src/oracle.rs b/apps/solidcraft-cli/src/oracle.rs similarity index 94% rename from apps/solvecraft-cli/src/oracle.rs rename to apps/solidcraft-cli/src/oracle.rs index 2d1b1ec8..37e2853e 100644 --- a/apps/solvecraft-cli/src/oracle.rs +++ b/apps/solidcraft-cli/src/oracle.rs @@ -5,7 +5,7 @@ #![allow(clippy::needless_range_loop)] use serde_json::{Value, json}; -use solvecraft_engine::geom::Vec3; +use solidcraft_engine::geom::Vec3; use crate::spatial::{self, Tri}; use crate::{read_json, recipe}; @@ -88,7 +88,7 @@ fn check_case(dir: &str) -> Value { Ok(s) => s, Err(e) => return json!({"case": name, "pass": false, "skipped": true, "error": e}), }; - let mut s = solvecraft_engine::Session::default(); + let mut s = solidcraft_engine::Session::default(); if let Err(e) = s.run_script(&script) { return json!({"case": name, "pass": false, "error": e.to_string(), "source": want.as_ref().map(|w| w["source"].clone())}); } @@ -148,27 +148,27 @@ fn check_case(dir: &str) -> Value { } /// Export the session's bodies as STEP, validate the file and import it again. -fn step_export_check(s: &solvecraft_engine::Session, volume: f64) -> (bool, String) { +fn step_export_check(s: &solidcraft_engine::Session, volume: f64) -> (bool, String) { let st = s.model.state(); - let bytes = match solvecraft_engine::io::export(&st, &[], solvecraft_engine::io::Format::Step, "oracle") { + let bytes = match solidcraft_engine::io::export(&st, &[], solidcraft_engine::io::Format::Step, "oracle") { Ok(b) => b, Err(e) => return (false, format!("export: {e}")), }; let text = String::from_utf8_lossy(&bytes); - if let Err(e) = solvecraft_engine::kernel::step_validate(&text) { + if let Err(e) = solidcraft_engine::kernel::step_validate(&text) { return (false, format!("invalid: {e}")); } - match solvecraft_engine::kernel::step_orientation_errors(&text) { + match solidcraft_engine::kernel::step_orientation_errors(&text) { Ok(errs) if !errs.is_empty() => return (false, format!("faces inside out: {}", errs.join("; "))), Err(e) => return (false, format!("orientation check: {e}")), Ok(_) => {} } - match solvecraft_engine::kernel::step_import(&text) { + match solidcraft_engine::kernel::step_import(&text) { Ok(imp) if !imp.warnings.is_empty() => (false, format!("warnings: {}", imp.warnings.join("; "))), Ok(imp) if imp.bodies.len() != st.bodies.len() => (false, format!("{} bodies back of {}", imp.bodies.len(), st.bodies.len())), Ok(imp) => { - let m: Vec<_> = imp.bodies.iter().filter_map(|b| solvecraft_engine::kernel::measure(&b.body).ok()).collect(); - let m0: Vec<_> = st.bodies.iter().filter_map(|b| solvecraft_engine::kernel::measure(&b.body).ok()).collect(); + let m: Vec<_> = imp.bodies.iter().filter_map(|b| solidcraft_engine::kernel::measure(&b.body).ok()).collect(); + let m0: Vec<_> = st.bodies.iter().filter_map(|b| solidcraft_engine::kernel::measure(&b.body).ok()).collect(); let v: f64 = m.iter().map(|x| x.volume).sum(); let (a, a0): (f64, f64) = (m.iter().map(|x| x.area).sum(), m0.iter().map(|x| x.area).sum()); let faces: usize = imp.bodies.iter().map(|b| b.file_faces).sum(); @@ -184,7 +184,7 @@ fn step_export_check(s: &solvecraft_engine::Session, volume: f64) -> (bool, Stri } /// Our part as a triangle soup (curved faces refined onto the surface). -fn our_mesh(s: &solvecraft_engine::Session) -> Result, String> { +fn our_mesh(s: &solidcraft_engine::Session) -> Result, String> { let mut ours: Vec = Vec::new(); for b in &s.world_state().bodies { let m = b.body.tessellate_with(MESH_TOL, true).map_err(|e| e.to_string())?; @@ -201,7 +201,7 @@ fn our_mesh(s: &solvecraft_engine::Session) -> Result, String> { /// Compare our part with the reference triangulation: the symmetric surface distance, and the /// principal second moments of volume of both meshes (the shape's inertia, independent of how it /// sits in space). -fn reference_checks(s: &solvecraft_engine::Session, stl: &str, tol: f64, c: &mut Checks) { +fn reference_checks(s: &solidcraft_engine::Session, stl: &str, tol: f64, c: &mut Checks) { let (ours, reference) = match (our_mesh(s), spatial::read_stl(stl)) { (Ok(o), Ok(r)) if !r.is_empty() => (o, r), (Err(e), _) | (_, Err(e)) => return c.push("surface_distance_mm", json!(e), json!(format!("<= {tol}")), false), diff --git a/apps/solvecraft-cli/src/recipe.rs b/apps/solidcraft-cli/src/recipe.rs similarity index 100% rename from apps/solvecraft-cli/src/recipe.rs rename to apps/solidcraft-cli/src/recipe.rs diff --git a/apps/solvecraft-cli/src/spatial.rs b/apps/solidcraft-cli/src/spatial.rs similarity index 100% rename from apps/solvecraft-cli/src/spatial.rs rename to apps/solidcraft-cli/src/spatial.rs diff --git a/apps/solvecraft-cli/tests/commands_output.rs b/apps/solidcraft-cli/tests/commands_output.rs similarity index 100% rename from apps/solvecraft-cli/tests/commands_output.rs rename to apps/solidcraft-cli/tests/commands_output.rs diff --git a/apps/solvecraft-cli/tests/corpus/README.md b/apps/solidcraft-cli/tests/corpus/README.md similarity index 91% rename from apps/solvecraft-cli/tests/corpus/README.md rename to apps/solidcraft-cli/tests/corpus/README.md index 7e26db03..8fa26209 100644 --- a/apps/solvecraft-cli/tests/corpus/README.md +++ b/apps/solidcraft-cli/tests/corpus/README.md @@ -1,8 +1,8 @@ # Design file corpus -Designs saved by older SolveCraft builds. `tests/design_corpus.rs` opens each one with the +Designs saved by older SolidCraft builds. `tests/design_corpus.rs` opens each one with the current build and checks that it evaluates as it did when saved (the `.expected.json` next to -it is `solvecraft-cli eval` of that file by the build that saved it: bodies, volumes, areas, +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. diff --git a/apps/solvecraft-cli/tests/corpus/components-arm.expected.json b/apps/solidcraft-cli/tests/corpus/components-arm.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/components-arm.expected.json rename to apps/solidcraft-cli/tests/corpus/components-arm.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/components-arm.solvecraft b/apps/solidcraft-cli/tests/corpus/components-arm.solidcraft similarity index 99% rename from apps/solvecraft-cli/tests/corpus/components-arm.solvecraft rename to apps/solidcraft-cli/tests/corpus/components-arm.solidcraft index 8fb36b01..0132dd69 100644 --- a/apps/solvecraft-cli/tests/corpus/components-arm.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/components-arm.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/1", + "format": "solidcraft/1", "name": "Untitled", "units": "mm", "params": [], diff --git a/apps/solvecraft-cli/tests/corpus/joints-hinge.expected.json b/apps/solidcraft-cli/tests/corpus/joints-hinge.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/joints-hinge.expected.json rename to apps/solidcraft-cli/tests/corpus/joints-hinge.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/joints-hinge.solvecraft b/apps/solidcraft-cli/tests/corpus/joints-hinge.solidcraft similarity index 99% rename from apps/solvecraft-cli/tests/corpus/joints-hinge.solvecraft rename to apps/solidcraft-cli/tests/corpus/joints-hinge.solidcraft index d83f1767..03c705a4 100644 --- a/apps/solvecraft-cli/tests/corpus/joints-hinge.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/joints-hinge.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/1", + "format": "solidcraft/1", "name": "Untitled", "units": "mm", "params": [ diff --git a/apps/solvecraft-cli/tests/corpus/m0-box-fillet-hole.expected.json b/apps/solidcraft-cli/tests/corpus/m0-box-fillet-hole.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/m0-box-fillet-hole.expected.json rename to apps/solidcraft-cli/tests/corpus/m0-box-fillet-hole.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/m0-box-fillet-hole.solvecraft b/apps/solidcraft-cli/tests/corpus/m0-box-fillet-hole.solidcraft similarity index 99% rename from apps/solvecraft-cli/tests/corpus/m0-box-fillet-hole.solvecraft rename to apps/solidcraft-cli/tests/corpus/m0-box-fillet-hole.solidcraft index e917f445..f49b1817 100644 --- a/apps/solvecraft-cli/tests/corpus/m0-box-fillet-hole.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/m0-box-fillet-hole.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/1", + "format": "solidcraft/1", "name": "Untitled", "units": "mm", "params": [ diff --git a/apps/solvecraft-cli/tests/corpus/m0-revolve-primitives.expected.json b/apps/solidcraft-cli/tests/corpus/m0-revolve-primitives.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/m0-revolve-primitives.expected.json rename to apps/solidcraft-cli/tests/corpus/m0-revolve-primitives.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/m0-revolve-primitives.solvecraft b/apps/solidcraft-cli/tests/corpus/m0-revolve-primitives.solidcraft similarity index 99% rename from apps/solvecraft-cli/tests/corpus/m0-revolve-primitives.solvecraft rename to apps/solidcraft-cli/tests/corpus/m0-revolve-primitives.solidcraft index bcfcc0b4..3e1f0c87 100644 --- a/apps/solvecraft-cli/tests/corpus/m0-revolve-primitives.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/m0-revolve-primitives.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/1", + "format": "solidcraft/1", "name": "Untitled", "units": "mm", "params": [], diff --git a/apps/solvecraft-cli/tests/corpus/modern-path-pattern.expected.json b/apps/solidcraft-cli/tests/corpus/modern-path-pattern.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/modern-path-pattern.expected.json rename to apps/solidcraft-cli/tests/corpus/modern-path-pattern.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/modern-path-pattern.solvecraft b/apps/solidcraft-cli/tests/corpus/modern-path-pattern.solidcraft similarity index 99% rename from apps/solvecraft-cli/tests/corpus/modern-path-pattern.solvecraft rename to apps/solidcraft-cli/tests/corpus/modern-path-pattern.solidcraft index f7e882f3..56decec8 100644 --- a/apps/solvecraft-cli/tests/corpus/modern-path-pattern.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/modern-path-pattern.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/1", + "format": "solidcraft/1", "name": "Untitled", "units": "mm", "params": [], diff --git a/apps/solvecraft-cli/tests/corpus/modern-plastic.expected.json b/apps/solidcraft-cli/tests/corpus/modern-plastic.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/modern-plastic.expected.json rename to apps/solidcraft-cli/tests/corpus/modern-plastic.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/modern-plastic.solvecraft b/apps/solidcraft-cli/tests/corpus/modern-plastic.solidcraft similarity index 98% rename from apps/solvecraft-cli/tests/corpus/modern-plastic.solvecraft rename to apps/solidcraft-cli/tests/corpus/modern-plastic.solidcraft index fc8a96c1..cca4a9fd 100644 --- a/apps/solvecraft-cli/tests/corpus/modern-plastic.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/modern-plastic.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/1", + "format": "solidcraft/1", "name": "Untitled", "units": "mm", "params": [ diff --git a/apps/solvecraft-cli/tests/corpus/modern-sheet-metal.expected.json b/apps/solidcraft-cli/tests/corpus/modern-sheet-metal.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/modern-sheet-metal.expected.json rename to apps/solidcraft-cli/tests/corpus/modern-sheet-metal.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/modern-sheet-metal.solvecraft b/apps/solidcraft-cli/tests/corpus/modern-sheet-metal.solidcraft similarity index 98% rename from apps/solvecraft-cli/tests/corpus/modern-sheet-metal.solvecraft rename to apps/solidcraft-cli/tests/corpus/modern-sheet-metal.solidcraft index b9611c82..556482f3 100644 --- a/apps/solvecraft-cli/tests/corpus/modern-sheet-metal.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/modern-sheet-metal.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/1", + "format": "solidcraft/1", "name": "Untitled", "units": "mm", "params": [], diff --git a/apps/solvecraft-cli/tests/corpus/naming-split-pieces.expected.json b/apps/solidcraft-cli/tests/corpus/naming-split-pieces.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/naming-split-pieces.expected.json rename to apps/solidcraft-cli/tests/corpus/naming-split-pieces.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/naming-split-pieces.solvecraft b/apps/solidcraft-cli/tests/corpus/naming-split-pieces.solidcraft similarity index 99% rename from apps/solvecraft-cli/tests/corpus/naming-split-pieces.solvecraft rename to apps/solidcraft-cli/tests/corpus/naming-split-pieces.solidcraft index 6cabf4a1..2880ccd8 100644 --- a/apps/solvecraft-cli/tests/corpus/naming-split-pieces.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/naming-split-pieces.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/13", + "format": "solidcraft/13", "name": "Untitled", "units": "mm", "params": [], diff --git a/apps/solvecraft-cli/tests/corpus/scripts/components-arm.json b/apps/solidcraft-cli/tests/corpus/scripts/components-arm.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/components-arm.json rename to apps/solidcraft-cli/tests/corpus/scripts/components-arm.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/joints-hinge.json b/apps/solidcraft-cli/tests/corpus/scripts/joints-hinge.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/joints-hinge.json rename to apps/solidcraft-cli/tests/corpus/scripts/joints-hinge.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/m0-box-fillet-hole.json b/apps/solidcraft-cli/tests/corpus/scripts/m0-box-fillet-hole.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/m0-box-fillet-hole.json rename to apps/solidcraft-cli/tests/corpus/scripts/m0-box-fillet-hole.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/m0-revolve-primitives.json b/apps/solidcraft-cli/tests/corpus/scripts/m0-revolve-primitives.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/m0-revolve-primitives.json rename to apps/solidcraft-cli/tests/corpus/scripts/m0-revolve-primitives.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/modern-path-pattern.json b/apps/solidcraft-cli/tests/corpus/scripts/modern-path-pattern.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/modern-path-pattern.json rename to apps/solidcraft-cli/tests/corpus/scripts/modern-path-pattern.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/modern-plastic.json b/apps/solidcraft-cli/tests/corpus/scripts/modern-plastic.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/modern-plastic.json rename to apps/solidcraft-cli/tests/corpus/scripts/modern-plastic.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/modern-sheet-metal.json b/apps/solidcraft-cli/tests/corpus/scripts/modern-sheet-metal.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/modern-sheet-metal.json rename to apps/solidcraft-cli/tests/corpus/scripts/modern-sheet-metal.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/naming-split-pieces.json b/apps/solidcraft-cli/tests/corpus/scripts/naming-split-pieces.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/naming-split-pieces.json rename to apps/solidcraft-cli/tests/corpus/scripts/naming-split-pieces.json diff --git a/apps/solvecraft-cli/tests/corpus/scripts/split-body-pieces.json b/apps/solidcraft-cli/tests/corpus/scripts/split-body-pieces.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/scripts/split-body-pieces.json rename to apps/solidcraft-cli/tests/corpus/scripts/split-body-pieces.json diff --git a/apps/solvecraft-cli/tests/corpus/split-body-pieces.expected.json b/apps/solidcraft-cli/tests/corpus/split-body-pieces.expected.json similarity index 100% rename from apps/solvecraft-cli/tests/corpus/split-body-pieces.expected.json rename to apps/solidcraft-cli/tests/corpus/split-body-pieces.expected.json diff --git a/apps/solvecraft-cli/tests/corpus/split-body-pieces.solvecraft b/apps/solidcraft-cli/tests/corpus/split-body-pieces.solidcraft similarity index 99% rename from apps/solvecraft-cli/tests/corpus/split-body-pieces.solvecraft rename to apps/solidcraft-cli/tests/corpus/split-body-pieces.solidcraft index ceb585c5..30d4dbf3 100644 --- a/apps/solvecraft-cli/tests/corpus/split-body-pieces.solvecraft +++ b/apps/solidcraft-cli/tests/corpus/split-body-pieces.solidcraft @@ -1,5 +1,5 @@ { - "format": "solvecraft/13", + "format": "solidcraft/13", "name": "Untitled", "units": "mm", "params": [], diff --git a/apps/solvecraft-cli/tests/design_corpus.rs b/apps/solidcraft-cli/tests/design_corpus.rs similarity index 94% rename from apps/solvecraft-cli/tests/design_corpus.rs rename to apps/solidcraft-cli/tests/design_corpus.rs index 06ab5abc..ea11cc7f 100644 --- a/apps/solvecraft-cli/tests/design_corpus.rs +++ b/apps/solidcraft-cli/tests/design_corpus.rs @@ -1,15 +1,15 @@ -//! Designs saved by older SolveCraft builds (tests/corpus, see its README) open and evaluate as +//! 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}; use serde_json::{Value, json}; -use solvecraft_engine::Session; +use solidcraft_engine::Session; fn corpus() -> Vec { let dir = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus"); let mut v: Vec = - std::fs::read_dir(&dir).unwrap().flatten().map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == "solvecraft")).collect(); + std::fs::read_dir(&dir).unwrap().flatten().map(|e| e.path()).filter(|p| p.extension().is_some_and(|x| x == "solidcraft")).collect(); v.sort(); assert!(v.len() >= 7, "corpus files in {}", dir.display()); v @@ -56,7 +56,7 @@ fn old_designs_open_and_evaluate_as_saved() { problems.push(format!("{name}: feature {}: {e}", r.id)); } } - assert_eq!(s.doc.format, solvecraft_engine::doc::format::format_string(solvecraft_engine::doc::format::FORMAT), "{name}: upgraded"); + 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"] != want["body_count"] && !known(&name, "body_count") { problems.push(format!("{name}: {} bodies, saved with {}", got["body_count"], want["body_count"])); @@ -84,7 +84,7 @@ fn old_designs_open_and_evaluate_as_saved() { } } // Saved again, it reads back the same. - let again = solvecraft_engine::doc::Document::from_json(&s.doc.to_json()).unwrap(); + let again = solidcraft_engine::doc::Document::from_json(&s.doc.to_json()).unwrap(); assert_eq!(again, *s.doc, "{name}: round trip"); } assert!(problems.is_empty(), "{}", problems.join("\n")); @@ -95,7 +95,7 @@ fn old_designs_open_and_evaluate_as_saved() { /// they are found as the build that saved them found them, without new notes. #[test] 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.solvecraft"); + 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()]); @@ -106,7 +106,7 @@ fn names_saved_before_exact_history_find_the_faces_they_named() { } // 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(solvecraft_engine::geom::Vec3::new(p[0], p[1], p[2]))); + 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])); } @@ -115,7 +115,7 @@ fn names_saved_before_exact_history_find_the_faces_they_named() { /// the far one stays `Body1` (chamfered), the near one `Body1 (1)` (filleted, coloured). #[test] fn body_names_saved_before_exact_history_keep_their_pieces() { - let path = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/corpus/split-body-pieces.solvecraft"); + 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 { @@ -123,7 +123,7 @@ fn body_names_saved_before_exact_history_keep_their_pieces() { } let state = s.model.state(); 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(solvecraft_engine::geom::Vec3::new(p[0], p[1], p[2])); + 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)")); } @@ -181,7 +181,7 @@ fn at_mut<'a>(v: &'a mut Value, path: &[Value]) -> Option<&'a mut Value> { /// Open a file's bytes as File → Open does and evaluate them; any panic is a failure. fn try_bytes(bytes: &[u8]) { - if let Ok(doc) = solvecraft_engine::io::read_design(bytes) { + if let Ok(doc) = solidcraft_engine::io::read_design(bytes) { let mut s = Session::new(doc); let _ = s.execute("inspect.measure", &json!({})); let _ = s.execute("document.inspect", &json!({})); @@ -197,7 +197,7 @@ fn generated() -> Vec<(String, Vec)> { "plane": {"type": "midplane", "a": side, "b": {"type": "plane", "plane": {"type": "origin", "name": "XZ"}}}}) }; let design = |name: &str, features: Value| { - json!({"format": "solvecraft/11", "name": name, "units": "mm", "params": [], "next_id": 9, "features": features}).to_string().into_bytes() + json!({"format": "solidcraft/11", "name": name, "units": "mm", "params": [], "next_id": 9, "features": features}).to_string().into_bytes() }; // A sketch past the caps interactive editing enforces: solving it would size dense matrices // by the point count and abort on the allocation. diff --git a/apps/solvecraft-cli/tests/fuzz_range.rs b/apps/solidcraft-cli/tests/fuzz_range.rs similarity index 100% rename from apps/solvecraft-cli/tests/fuzz_range.rs rename to apps/solidcraft-cli/tests/fuzz_range.rs diff --git a/apps/solvecraft-cli/tests/kill_mid_save.rs b/apps/solidcraft-cli/tests/kill_mid_save.rs similarity index 91% rename from apps/solvecraft-cli/tests/kill_mid_save.rs rename to apps/solidcraft-cli/tests/kill_mid_save.rs index e63952dc..78dd707b 100644 --- a/apps/solvecraft-cli/tests/kill_mid_save.rs +++ b/apps/solidcraft-cli/tests/kill_mid_save.rs @@ -6,17 +6,17 @@ use std::time::Duration; fn round_of(path: &std::path::Path) -> Option { let bytes = std::fs::read(path).ok()?; - let doc = solvecraft_engine::io::read_design(&bytes).ok()?; + let doc = solidcraft_engine::io::read_design(&bytes).ok()?; doc.params.iter().find(|p| p.name == "save_round").map(|p| p.expr.clone()) } #[test] fn killed_mid_save_the_old_file_survives() { let exe = env!("CARGO_BIN_EXE_solidcraft-cli"); - let dir = std::env::temp_dir().join(format!("solvecraft-kill-mid-save-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("solidcraft-kill-mid-save-{}", std::process::id())); let _ = std::fs::remove_dir_all(&dir); std::fs::create_dir_all(&dir).unwrap(); - let path = dir.join("design.solvecraft"); + let path = dir.join("design.solidcraft"); let ok = Command::new(exe).args(["save-stress", &path.to_string_lossy(), "1"]).status().unwrap(); assert!(ok.success()); assert_eq!(round_of(&path).as_deref(), Some("0")); @@ -44,7 +44,7 @@ fn killed_mid_save_the_old_file_survives() { let round = round_of(&path); assert!(round.is_some(), "after kill {i} the design file is torn or missing"); seen.insert(round); - let bak = solvecraft_engine::io::backup_path(&path); + let bak = solidcraft_engine::io::backup_path(&path); if bak.exists() { assert!(round_of(&bak).is_some(), "after kill {i} the backup is torn"); } diff --git a/apps/solvecraft-cli/tests/legacy_alias.rs b/apps/solidcraft-cli/tests/legacy_alias.rs similarity index 100% rename from apps/solvecraft-cli/tests/legacy_alias.rs rename to apps/solidcraft-cli/tests/legacy_alias.rs diff --git a/apps/solvecraft-cli/tests/mcp_connect.rs b/apps/solidcraft-cli/tests/mcp_connect.rs similarity index 100% rename from apps/solvecraft-cli/tests/mcp_connect.rs rename to apps/solidcraft-cli/tests/mcp_connect.rs diff --git a/apps/solvecraft-cli/tests/run_results.rs b/apps/solidcraft-cli/tests/run_results.rs similarity index 70% rename from apps/solvecraft-cli/tests/run_results.rs rename to apps/solidcraft-cli/tests/run_results.rs index 8962e11b..a74c924b 100644 --- a/apps/solvecraft-cli/tests/run_results.rs +++ b/apps/solidcraft-cli/tests/run_results.rs @@ -12,7 +12,7 @@ struct Scratch(PathBuf); impl Scratch { fn new() -> Self { - let dir = std::env::temp_dir().join(format!("solvecraft-run-results-{}-{}", std::process::id(), NEXT.fetch_add(1, Ordering::Relaxed))); + let dir = std::env::temp_dir().join(format!("solidcraft-run-results-{}-{}", std::process::id(), NEXT.fetch_add(1, Ordering::Relaxed))); std::fs::create_dir(&dir).unwrap(); Self(dir) } @@ -46,7 +46,7 @@ fn body_count(path: &Path) -> Value { #[test] fn results_still_save_and_export_the_script_once() { let dir = Scratch::new(); - let saved = dir.0.join("box.solvecraft"); + let saved = dir.0.join("box.solidcraft"); let stl = dir.0.join("box.stl"); let obj = dir.0.join("box.obj"); let result = json_output( @@ -64,8 +64,8 @@ fn results_still_save_and_export_the_script_once() { fn results_with_base_design_and_quiet_still_report_and_save() { let dir = Scratch::new(); let script = dir.script(); - let base = dir.0.join("base.solvecraft"); - let saved = dir.0.join("combined.solvecraft"); + let base = dir.0.join("base.solidcraft"); + let saved = dir.0.join("combined.solidcraft"); json_output(cli().arg("run").arg(&script).arg("--save").arg(&base).output().unwrap()); let result = json_output(cli().args(["run", "--results", "--quiet", "--in"]).arg(&base).arg(&script).arg("--save").arg(&saved).output().unwrap()); assert_eq!(result.as_array().unwrap().len(), 1); @@ -77,7 +77,7 @@ fn results_with_base_design_and_quiet_still_report_and_save() { fn results_propagate_output_errors() { let dir = Scratch::new(); for flag in ["--save", "--out"] { - let path = dir.0.join("missing").join(if flag == "--save" { "box.solvecraft" } else { "box.stl" }); + let path = dir.0.join("missing").join(if flag == "--save" { "box.solidcraft" } else { "box.stl" }); let output = cli().arg("run").arg(dir.script()).arg("--results").arg(flag).arg(&path).output().unwrap(); assert_eq!(output.status.code(), Some(1)); assert!(!output.stderr.is_empty()); @@ -89,9 +89,27 @@ fn results_propagate_output_errors() { #[test] fn results_without_script_still_fail() { let dir = Scratch::new(); - let base = dir.0.join("base.solvecraft"); + let base = dir.0.join("base.solidcraft"); json_output(cli().arg("run").arg(dir.script()).arg("--save").arg(&base).output().unwrap()); let output = cli().args(["run", "--results", "--in"]).arg(&base).output().unwrap(); assert_eq!(output.status.code(), Some(1)); assert!(String::from_utf8_lossy(&output.stderr).contains("--results needs a script")); } + +/// Compatibility shim (docs/compatibility.md): a design saved before the rename, with the +/// legacy extension and format tag, still opens; re-saving writes the current tag. +#[test] +fn legacy_extension_and_format_tag_still_open() { + let dir = Scratch::new(); + let saved = dir.0.join("box.solidcraft"); + json_output(cli().arg("run").arg(dir.script()).arg("--save").arg(&saved).output().unwrap()); + let text = std::fs::read_to_string(&saved).unwrap(); + assert!(text.contains("\"solidcraft/"), "new designs carry the current tag"); + let legacy = dir.0.join("old.solvecraft"); + std::fs::write(&legacy, text.replace("\"solidcraft/", "\"solvecraft/")).unwrap(); + assert_eq!(body_count(&legacy), 1); + let resaved = dir.0.join("resaved.solidcraft"); + json_output(cli().args(["run", "--in"]).arg(&legacy).arg(dir.script()).arg("--save").arg(&resaved).output().unwrap()); + assert_eq!(body_count(&resaved), 2); + assert!(std::fs::read_to_string(&resaved).unwrap().contains("\"solidcraft/")); +} diff --git a/apps/solvecraft-web/Cargo.toml b/apps/solidcraft-web/Cargo.toml similarity index 88% rename from apps/solvecraft-web/Cargo.toml rename to apps/solidcraft-web/Cargo.toml index 476758b4..db51d3dc 100644 --- a/apps/solvecraft-web/Cargo.toml +++ b/apps/solidcraft-web/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "solvecraft-web" +name = "solidcraft-web" description = "SolidCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)" version.workspace = true edition.workspace = true @@ -10,12 +10,12 @@ publish = false workspace = true [[bin]] -name = "solvecraft-web" +name = "solidcraft-web" path = "src/main.rs" [dependencies] -solvecraft-engine = { workspace = true } -solvecraft-ui-egui = { workspace = true } +solidcraft-engine = { workspace = true } +solidcraft-ui-egui = { workspace = true } egui = { workspace = true } log = { workspace = true } diff --git a/apps/solvecraft-web/Trunk.toml b/apps/solidcraft-web/Trunk.toml similarity index 68% rename from apps/solvecraft-web/Trunk.toml rename to apps/solidcraft-web/Trunk.toml index 4094dea2..e608607f 100644 --- a/apps/solvecraft-web/Trunk.toml +++ b/apps/solidcraft-web/Trunk.toml @@ -1,4 +1,4 @@ -# Build with: trunk build --release (from apps/solvecraft-web) +# Build with: trunk build --release (from apps/solidcraft-web) [build] target = "index.html" dist = "../../dist/web" diff --git a/apps/solvecraft-web/fonts/BIZUDPGothic-Regular.ttf b/apps/solidcraft-web/fonts/BIZUDPGothic-Regular.ttf similarity index 100% rename from apps/solvecraft-web/fonts/BIZUDPGothic-Regular.ttf rename to apps/solidcraft-web/fonts/BIZUDPGothic-Regular.ttf diff --git a/apps/solvecraft-web/fonts/OFL.txt b/apps/solidcraft-web/fonts/OFL.txt similarity index 100% rename from apps/solvecraft-web/fonts/OFL.txt rename to apps/solidcraft-web/fonts/OFL.txt diff --git a/apps/solvecraft-web/index.html b/apps/solidcraft-web/index.html similarity index 71% rename from apps/solvecraft-web/index.html rename to apps/solidcraft-web/index.html index eedf6dc0..42d371e8 100644 --- a/apps/solvecraft-web/index.html +++ b/apps/solidcraft-web/index.html @@ -4,18 +4,18 @@ SolidCraft - + - -
Loading SolidCraft…
+ +
Loading SolidCraft…
diff --git a/apps/solvecraft-web/src/files.rs b/apps/solidcraft-web/src/files.rs similarity index 92% rename from apps/solvecraft-web/src/files.rs rename to apps/solidcraft-web/src/files.rs index 41106b99..b384280c 100644 --- a/apps/solvecraft-web/src/files.rs +++ b/apps/solidcraft-web/src/files.rs @@ -1,5 +1,5 @@ //! Files in the browser. Designs are kept in the origin private file system (OPFS): read into -//! the engine's in-memory files (`solvecraft_engine::io::vfs`) at start, written back whenever a +//! the engine's in-memory files (`solidcraft_engine::io::vfs`) at start, written back whenever a //! command writes one. Files come in from the computer through a file picker (the File System //! Access API where the browser has it, an `` otherwise) or a drop on the page, //! and go out as downloads (exports, and "Download design"). An autosave of unsaved work is kept @@ -9,7 +9,7 @@ use std::cell::{Cell, RefCell}; use std::rc::Rc; use js_sys::{Array, Function, Object, Reflect, Uint8Array}; -use solvecraft_engine::io::vfs; +use solidcraft_engine::io::vfs; use wasm_bindgen::{JsCast as _, JsValue, closure::Closure}; use wasm_bindgen_futures::{JsFuture, spawn_local}; use web_sys::{FileSystemDirectoryHandle, FileSystemFileHandle, FileSystemGetFileOptions, FileSystemWritableFileStream}; @@ -20,8 +20,11 @@ pub const OPFS: &str = "/opfs/"; pub const DOWNLOADS: &str = "/downloads/"; /// Files brought in from the computer. pub const UPLOAD: &str = "/upload/"; -/// The autosave of unsaved work (in OPFS: `recovery.solvecraft` and `recovery.json`). -const RECOVERY: &str = "recovery.solvecraft"; +/// The autosave of unsaved work (in OPFS: `recovery.solidcraft` and `recovery.json`). +const RECOVERY: &str = "recovery.solidcraft"; +/// The autosave earlier releases kept (compatibility shim, docs/compatibility.md): recovered +/// when there is no current one. +const LEGACY_RECOVERY: &str = "recovery.solvecraft"; const RECOVERY_META: &str = "recovery.json"; /// What to do with a file brought in from the computer. @@ -87,7 +90,10 @@ thread_local! { } /// Local storage keys of stored files. -const LOCAL_PREFIX: &str = "solvecraft.file."; +const LOCAL_PREFIX: &str = "solidcraft.file."; +/// Local-storage files kept by earlier releases (compatibility shim, docs/compatibility.md): +/// still listed and opened; removing one removes either key. +const LEGACY_LOCAL_PREFIX: &str = "solvecraft.file."; fn local() -> Result { web_sys::window().and_then(|w| w.local_storage().ok().flatten()).ok_or_else(|| "this browser keeps no local storage".to_string()) @@ -177,7 +183,9 @@ async fn write_file(name: &str, bytes: &[u8]) -> Result<(), String> { async fn remove_file(name: &str) -> Result<(), String> { if local_only() { - return local()?.remove_item(&format!("{LOCAL_PREFIX}{name}")).map_err(err); + let st = local()?; + st.remove_item(&format!("{LEGACY_LOCAL_PREFIX}{name}")).map_err(err)?; + return st.remove_item(&format!("{LOCAL_PREFIX}{name}")).map_err(err); } let dir = root().await?; JsFuture::from(dir.remove_entry(name)).await.map_err(err)?; @@ -190,7 +198,7 @@ fn read_local() -> Result, f64)>, String> { let mut out = Vec::new(); for i in 0..st.length().unwrap_or(0).min(10_000) { let Some(k) = st.key(i).ok().flatten() else { continue }; - let Some(name) = k.strip_prefix(LOCAL_PREFIX) else { continue }; + let Some(name) = k.strip_prefix(LOCAL_PREFIX).or_else(|| k.strip_prefix(LEGACY_LOCAL_PREFIX)) else { continue }; if let Some(v) = st.get_item(&k).ok().flatten() { out.push((name.to_string(), decode(&v), 0.0)); } @@ -251,7 +259,14 @@ pub fn start(files: &Rc) { files.say("This page can't use the browser's file storage (it needs https or localhost): designs are kept in local storage, a few MB at most.", false); } let mut entries = Vec::new(); + let has_recovery = all.iter().any(|(n, _, _)| n == RECOVERY); for (name, bytes, modified) in all { + if name == LEGACY_RECOVERY { + if !has_recovery { + vfs::insert(&format!("{OPFS}{RECOVERY}"), bytes); + } + continue; + } if name == RECOVERY_META { let meta: serde_json::Value = serde_json::from_slice(&bytes).unwrap_or_default(); let n = meta["name"].as_str().unwrap_or("Untitled").to_string(); @@ -349,7 +364,7 @@ pub fn rename(files: &Rc, from: &str, to: &str) -> Result<(), String> { if files.entries.borrow().iter().any(|e| e.name == to) { return Err(format!("there is already a file named {to}")); } - let bytes = vfs::read(&format!("{OPFS}{from}"), solvecraft_engine::io::MAX_DESIGN_BYTES).map_err(|e| e.to_string())?; + let bytes = vfs::read(&format!("{OPFS}{from}"), solidcraft_engine::io::MAX_DESIGN_BYTES).map_err(|e| e.to_string())?; vfs::write(&format!("{OPFS}{to}"), &bytes).map_err(|e| e.to_string())?; delete(files, from); flush(files); @@ -357,11 +372,11 @@ pub fn rename(files: &Rc, from: &str, to: &str) -> Result<(), String> { } /// Keep (or drop) the autosave of the open design. -pub fn autosave(files: &Rc, doc: Option<(&solvecraft_engine::doc::Document, Option<&str>)>) { +pub fn autosave(files: &Rc, doc: Option<(&solidcraft_engine::doc::Document, Option<&str>)>) { let files = files.clone(); match doc { Some((d, path)) => { - let bytes = solvecraft_engine::io::write_design(d); + let bytes = solidcraft_engine::io::write_design(d); let meta = serde_json::json!({"name": d.name, "path": path, "saved_at": js_sys::Date::now()}).to_string(); spawn_local(async move { let r = async { @@ -375,6 +390,7 @@ pub fn autosave(files: &Rc, doc: Option<(&solvecraft_engine::doc::Documen } None => spawn_local(async move { let _ = remove_file(RECOVERY).await; + let _ = remove_file(LEGACY_RECOVERY).await; let _ = remove_file(RECOVERY_META).await; }), } @@ -436,7 +452,7 @@ fn input_picker(files: &Rc, intent: Intent) { let Some(doc) = web_sys::window().and_then(|w| w.document()) else { return }; let Ok(input) = doc.create_element("input").map(|e| e.unchecked_into::()) else { return }; input.set_type("file"); - input.set_accept(".solvecraft,.step,.stp,.3mf,.stl"); + input.set_accept(".solidcraft,.solvecraft,.step,.stp,.3mf,.stl"); // Attached (hidden) while it is open: some browsers ignore a click on a detached input. let _ = input.set_attribute("style", "display:none"); if let Some(body) = doc.body() { diff --git a/apps/solvecraft-web/src/main.rs b/apps/solidcraft-web/src/main.rs similarity index 93% rename from apps/solvecraft-web/src/main.rs rename to apps/solidcraft-web/src/main.rs index 20bee57f..b2b50c74 100644 --- a/apps/solvecraft-web/src/main.rs +++ b/apps/solidcraft-web/src/main.rs @@ -1,6 +1,6 @@ -//! SolveCraft in the browser. +//! SolidCraft in the browser. //! -//! Runs the same [`solvecraft_ui_egui::SolveApp`] as the desktop app through eframe's web runner +//! Runs the same [`solidcraft_ui_egui::SolveApp`] as the desktop app through eframe's web runner //! (wgpu: WebGPU where available, WebGL2 otherwise). Build with `trunk build --release` from this //! directory (output in `dist/web`). URL flags: `?webgl` forces WebGL2, `?sample` opens the //! sample part. @@ -13,15 +13,15 @@ mod files; mod web { use std::rc::Rc; - use solvecraft_engine::Session; - use solvecraft_engine::io::vfs; - use solvecraft_ui_egui::{Services, SolveApp}; + use solidcraft_engine::Session; + use solidcraft_engine::io::vfs; + use solidcraft_ui_egui::{Services, SolveApp}; use wasm_bindgen::JsCast as _; use crate::files::{self, Files, Intent}; - const CANVAS_ID: &str = "solvecraft_canvas"; - const LOADING_ID: &str = "solvecraft_loading"; + const CANVAS_ID: &str = "solidcraft_canvas"; + const LOADING_ID: &str = "solidcraft_loading"; const DEPTH_BITS: u8 = 24; /// Seconds between autosaves of unsaved work. @@ -63,7 +63,7 @@ mod web { /// The start page's recent designs were filled from the stored ones. recents_synced: bool, /// On-demand CJK font: fetched only when a CJK language is selected. - font: solvecraft_ui_egui::fonts::CjkFontLoader, + font: solidcraft_ui_egui::fonts::CjkFontLoader, font_result: FontLoadResult, } @@ -79,7 +79,7 @@ mod web { } let incoming: Vec<_> = self.files.incoming.borrow_mut().drain(..).collect(); for (name, bytes, intent) in incoming { - let design = name.ends_with(&format!(".{}", solvecraft_engine::io::DESIGN_EXT)); + let design = solidcraft_engine::io::is_design_path(&name); let path = if design && intent == Intent::Open { // A design from the computer is kept in the browser (a copy), so Save works. let path = files::free_name(&name); @@ -133,13 +133,13 @@ mod web { entries.sort_by(|a, b| b.modified.total_cmp(&a.modified)); let recent = &mut self.app.home.recent; recent.retain(|p| vfs::exists(p)); - for e in entries.iter().filter(|e| e.name.ends_with(&format!(".{}", solvecraft_engine::io::DESIGN_EXT))) { + for e in entries.iter().filter(|e| solidcraft_engine::io::is_design_path(&e.name)) { let p = format!("{}{}", files::OPFS, e.name); if !recent.contains(&p) { recent.push(p); } } - recent.truncate(solvecraft_ui_egui::home::MAX_RECENT); + recent.truncate(solidcraft_ui_egui::home::MAX_RECENT); } /// The stored designs: open, insert, rename, delete, download; open from the computer. @@ -206,7 +206,7 @@ mod web { if ui.small_button("Open").clicked() { act = Some(("open", e.name.clone())); } - let design = e.name.ends_with(&format!(".{}", solvecraft_engine::io::DESIGN_EXT)); + let design = solidcraft_engine::io::is_design_path(&e.name); if !design && ui.small_button("Insert").on_hover_text("Into the open design (STEP, 3MF, STL)").clicked() { act = Some(("insert", e.name.clone())); } @@ -241,7 +241,7 @@ mod web { self.app.insert_path(&path); self.files.panel.set(false); } - "download" => match vfs::read(&path, solvecraft_engine::io::MAX_DESIGN_BYTES) { + "download" => match vfs::read(&path, solidcraft_engine::io::MAX_DESIGN_BYTES) { Ok(b) => { if let Err(e) = files::download(&name, &b) { self.app.set_status(e, true); @@ -251,8 +251,8 @@ mod web { }, "download_open" => { let d = &self.app.session.doc; - let bytes = solvecraft_engine::io::write_design(d); - if let Err(e) = files::download(&format!("{}.{}", d.name, solvecraft_engine::io::DESIGN_EXT), &bytes) { + let bytes = solidcraft_engine::io::write_design(d); + if let Err(e) = files::download(&format!("{}.{}", d.name, solidcraft_engine::io::DESIGN_EXT), &bytes) { self.app.set_status(e, true); } } @@ -318,7 +318,7 @@ mod web { let done = self.font_result.borrow_mut().take(); if let Some(res) = done { let installed = match res { - Ok(bytes) => solvecraft_ui_egui::fonts::install_cjk_font(ctx, bytes), + Ok(bytes) => solidcraft_ui_egui::fonts::install_cjk_font(ctx, bytes), Err(err) => { log::warn!("failed to fetch CJK font from {JAPANESE_FONT_PATH}: {err}"); false @@ -349,7 +349,7 @@ mod web { self.tick_font(ctx); self.app.logic(ctx); self.files_tick(ctx); - UNSAVED.with(|u| u.set(solvecraft_ui_egui::documents::any_dirty(&self.app))); + UNSAVED.with(|u| u.set(solidcraft_ui_egui::documents::any_dirty(&self.app))); } fn raw_input_hook(&mut self, _ctx: &egui::Context, raw: &mut egui::RawInput) { files::take_drops(&self.files, raw); @@ -365,14 +365,11 @@ mod web { // Persist the user's own language, not a session-only English fallback. let saved = self.font.persisted_language(&self.app.preferences.language).to_owned(); let live = std::mem::replace(&mut self.app.preferences.language, saved); - storage.set_string(PREFS_KEY, self.app.prefs()); + storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, self.app.prefs()); self.app.preferences.language = live; } } - /// Where the preferences (theme) are kept (the browser's local storage). - const PREFS_KEY: &str = "solvecraft.prefs"; - fn query() -> String { web_sys::window().and_then(|w| w.location().search().ok()).unwrap_or_default() } @@ -411,7 +408,7 @@ mod web { canvas, options, Box::new(move |cc| { - solvecraft_ui_egui::fonts::setup_fonts(&cc.egui_ctx); + solidcraft_ui_egui::fonts::setup_fonts(&cc.egui_ctx); let files = Rc::new(Files::default()); files::start(&files); let panel = files.clone(); @@ -423,13 +420,13 @@ mod web { })), // Designs are kept in the browser; everything else is downloaded. pick_save: Some(Box::new(|name: &str, exts: &[&str]| { - let design = exts.contains(&solvecraft_engine::io::DESIGN_EXT); + let design = exts.contains(&solidcraft_engine::io::DESIGN_EXT); Some(format!("{}{name}", if design { files::OPFS } else { files::DOWNLOADS })) })), ..Default::default() }; let mut app = SolveApp::new(Session::default(), services); - if let Some(p) = cc.storage.and_then(|s| s.get_string(PREFS_KEY)) { + if let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))) { app.load_prefs(&p); } if let Some(rs) = &cc.wgpu_render_state { @@ -437,7 +434,7 @@ mod web { app.set_wgpu(rs, DEPTH_BITS, 0, 1); } if query().contains("sample") { - if let Err(e) = app.session.run_script(&solvecraft_engine::sample::script()) { + if let Err(e) = app.session.run_script(&solidcraft_engine::sample::script()) { app.set_status(format!("sample: {e}"), true); } app.session.doc_mut().name = "Sample Plate".into(); @@ -480,5 +477,5 @@ fn main() { #[cfg(not(target_arch = "wasm32"))] fn main() { - eprintln!("solvecraft-web only runs in the browser: build it with `trunk build --release` in apps/solvecraft-web"); + eprintln!("solidcraft-web only runs in the browser: build it with `trunk build --release` in apps/solidcraft-web"); } diff --git a/apps/solvecraft/Cargo.toml b/apps/solidcraft/Cargo.toml similarity index 91% rename from apps/solvecraft/Cargo.toml rename to apps/solidcraft/Cargo.toml index 8ccf63b0..01cbcc33 100644 --- a/apps/solvecraft/Cargo.toml +++ b/apps/solidcraft/Cargo.toml @@ -11,8 +11,8 @@ default-run = "solidcraft" workspace = true [dependencies] -solvecraft-engine = { workspace = true } -solvecraft-ui-egui = { workspace = true } +solidcraft-engine = { workspace = true } +solidcraft-ui-egui = { workspace = true } eframe = { workspace = true, features = ["default_fonts", "wgpu", "accesskit", "wayland", "x11", "links"] } egui = { workspace = true } serde_json = { workspace = true } diff --git a/apps/solvecraft/build.rs b/apps/solidcraft/build.rs similarity index 100% rename from apps/solvecraft/build.rs rename to apps/solidcraft/build.rs diff --git a/apps/solvecraft/src/control_server.rs b/apps/solidcraft/src/control_server.rs similarity index 96% rename from apps/solvecraft/src/control_server.rs rename to apps/solidcraft/src/control_server.rs index 834818d3..792b10b3 100644 --- a/apps/solvecraft/src/control_server.rs +++ b/apps/solidcraft/src/control_server.rs @@ -8,8 +8,8 @@ use std::sync::mpsc::{Receiver, Sender, channel}; use std::time::Duration; use serde_json::{Value, json}; -use solvecraft_engine::MAX_LINE; -use solvecraft_ui_egui::ControlRequest; +use solidcraft_engine::MAX_LINE; +use solidcraft_ui_egui::ControlRequest; /// Connections served at once. The channel has no authentication beyond being bound to /// loopback, so a flood must not be able to spawn threads without end. @@ -24,11 +24,11 @@ pub fn start(port: u16, ctx: egui::Context) -> Receiver { let listener = match TcpListener::bind(("127.0.0.1", port)) { Ok(l) => l, Err(e) => { - eprintln!("solvecraft: control server failed to bind 127.0.0.1:{port}: {e}"); + eprintln!("solidcraft: control server failed to bind 127.0.0.1:{port}: {e}"); return rx; } }; - eprintln!("solvecraft: control server listening on 127.0.0.1:{port}"); + eprintln!("solidcraft: control server listening on 127.0.0.1:{port}"); std::thread::spawn(move || { for stream in listener.incoming().flatten() { if OPEN.fetch_add(1, Ordering::Relaxed) >= MAX_CONNECTIONS { diff --git a/apps/solvecraft/src/main.rs b/apps/solidcraft/src/main.rs similarity index 68% rename from apps/solvecraft/src/main.rs rename to apps/solidcraft/src/main.rs index 0f7266c7..bb3f7986 100644 --- a/apps/solvecraft/src/main.rs +++ b/apps/solidcraft/src/main.rs @@ -1,13 +1,13 @@ -//! SolveCraft desktop app. +//! SolidCraft desktop app. //! -//! Usage: `solvecraft [--control ] [--sample] [--crash-log] [design.solvecraft | part.step]` +//! Usage: `solidcraft [--control ] [--sample] [--crash-log] [design.solidcraft | part.step]` //! //! A `.step`/`.stp` file opens as a new design holding the file's bodies (an Import feature); a //! `.3mf`/`.stl` file as a new design holding mesh bodies. //! -//! `--control ` (or `SOLVECRAFT_CONTROL_PORT`) starts a localhost JSON-lines control +//! `--control ` (or `SOLIDCRAFT_CONTROL_PORT`) starts a localhost JSON-lines control //! server: `{"id":1,"method":"engine.execute","params":{"command":"solid.box","params":{…}}}`. -//! See `solvecraft_ui_egui::control` and docs/control-protocol.md. +//! See `solidcraft_ui_egui::control` and docs/control-protocol.md. #![cfg_attr(all(target_os = "windows", not(debug_assertions)), windows_subsystem = "windows")] #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic, clippy::unimplemented, clippy::todo, clippy::unreachable)] #![forbid(unsafe_code)] @@ -19,8 +19,8 @@ mod control_server; #[cfg(any(target_os = "windows", target_os = "linux"))] mod space_mouse; -use solvecraft_engine::Session; -use solvecraft_ui_egui::{Services, SolveApp}; +use solidcraft_engine::Session; +use solidcraft_ui_egui::{Services, SolveApp}; struct App { solve: SolveApp, @@ -31,12 +31,12 @@ struct App { impl eframe::App for App { fn logic(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // The design on screen is the one whose commands a crash report replays. - self.solve.session.crash_journal = solvecraft_engine::crash::enabled(); + self.solve.session.crash_journal = solidcraft_engine::crash::enabled(); // The OS closing the window (Alt+F4, Cmd+Q, the window manager): ask about unsaved // designs first; the window closes once they are answered (`quit_requested`). if ctx.input(|i| i.viewport().close_requested()) && !self.solve.quit_requested { ctx.send_viewport_cmd(egui::ViewportCommand::CancelClose); - solvecraft_ui_egui::documents::request_quit(&mut self.solve); + solidcraft_ui_egui::documents::request_quit(&mut self.solve); } self.solve.logic(ctx); #[cfg(any(target_os = "windows", target_os = "linux"))] @@ -52,26 +52,24 @@ impl eframe::App for App { self.solve.ui(ui); } fn save(&mut self, storage: &mut dyn eframe::Storage) { - storage.set_string(PREFS_KEY, self.solve.prefs()); + storage.set_string(solidcraft_ui_egui::prefs::STORAGE_KEY, self.solve.prefs()); } fn on_exit(&mut self) { - solvecraft_ui_egui::recovery_ui::close(&mut self.solve); - solvecraft_engine::crash::end_session(); + solidcraft_ui_egui::recovery_ui::close(&mut self.solve); + solidcraft_engine::crash::end_session(); } } -/// Where the preferences (theme) are kept in eframe's storage. -const PREFS_KEY: &str = "solvecraft.prefs"; - fn services() -> Services { Services { pick_open: Some(Box::new(|| { rfd::FileDialog::new() .add_filter( "Designs, STEP, IGES and meshes", - &["solvecraft", "step", "stp", "STEP", "STP", "igs", "iges", "IGS", "IGES", "3mf", "3MF", "stl", "STL"], + &["solidcraft", "solvecraft", "step", "stp", "STEP", "STP", "igs", "iges", "IGS", "IGES", "3mf", "3MF", "stl", "STL"], ) - .add_filter("SolidCraft design", &["solvecraft"]) + // The legacy extension is a compatibility shim (docs/compatibility.md). + .add_filter("SolidCraft design", &solidcraft_engine::io::DESIGN_EXTS) .add_filter("STEP", &["step", "stp", "STEP", "STP"]) .add_filter("IGES", &["igs", "iges", "IGS", "IGES"]) .add_filter("Mesh (3MF, STL)", &["3mf", "3MF", "stl", "STL"]) @@ -104,10 +102,40 @@ fn services() -> Services { } } +/// The per-user settings folder: eframe's storage (`app.ron`) and the anti-aliasing preference. +fn settings_dir() -> Option { + eframe::storage_dir("SolidCraft") +} + /// The anti-aliasing preference, kept next to eframe's storage (it is needed before the /// window opens). fn graphics_path() -> Option { - eframe::storage_dir("SolveCraft").map(|d| d.join("graphics.json")) + settings_dir().map(|d| d.join("graphics.json")) +} + +/// Compatibility shim (docs/compatibility.md): on the first launch after the rename, copy the +/// settings of earlier releases into the new folder. They kept `graphics.json` (and, on Linux +/// and macOS, the recovery folder) under the old product name, and eframe's `app.ron` under the +/// window's app id. The old folders are never changed. +fn adopt_legacy_settings() { + let (Some(current), Some(legacy)) = (settings_dir(), eframe::storage_dir("SolveCraft")) else { return }; + let legacy_eframe = eframe::storage_dir(APP_ID); + if let Err(e) = adopt_settings(¤t, &legacy, legacy_eframe.as_deref()) { + log::warn!("settings: could not copy the previous release's settings into {}: {e}", current.display()); + } +} + +fn adopt_settings(current: &std::path::Path, legacy: &std::path::Path, legacy_eframe: Option<&std::path::Path>) -> std::io::Result<()> { + let fresh = !current.exists(); + solidcraft_engine::io::adopt_legacy_dir(current, legacy)?; + if let Some(old) = legacy_eframe.map(|d| d.join("app.ron")).filter(|p| fresh && p.is_file()) { + std::fs::create_dir_all(current)?; + let new = current.join("app.ron"); + if !new.exists() { + std::fs::copy(old, new)?; + } + } + Ok(()) } /// Anti-aliasing samples from Preferences (4 when unset). @@ -122,7 +150,7 @@ fn msaa() -> u16 { } /// Windows and Linux: no OS title bar; the application bar is the title bar -/// (`solvecraft_ui_egui::titlebar`). macOS keeps its traffic lights over the integrated bar. +/// (`solidcraft_ui_egui::titlebar`). macOS keeps its traffic lights over the integrated bar. const CUSTOM_TITLEBAR: bool = !cfg!(target_os = "macos"); const DEPTH_BITS: u8 = 24; @@ -147,12 +175,17 @@ fn app_viewport() -> Result { .with_fullsize_content_view(true) .with_titlebar_shown(false) .with_title_shown(false) - .with_app_id("ai.storyteller.solvecraft")) + .with_app_id(APP_ID)) } +/// The window's app id: it names the desktop entry, so it stays the packages' reverse-DNS id, +/// which is kept from before the rename (compatibility shim, docs/compatibility.md). +const APP_ID: &str = "ai.storyteller.solvecraft"; + fn main() -> eframe::Result { + adopt_legacy_settings(); let samples = msaa(); - let mut control_port: Option = std::env::var("SOLVECRAFT_CONTROL_PORT").ok().and_then(|p| p.parse().ok()); + let mut control_port: Option = solidcraft_engine::geom::env::var("CONTROL_PORT").and_then(|p| p.parse().ok()); let mut files = Vec::new(); let mut sample = false; let mut args = std::env::args().skip(1); @@ -165,10 +198,10 @@ fn main() -> eframe::Result { return Ok(()); } "--crash-log" => { - match solvecraft_engine::crash::default_dir() { + match solidcraft_engine::crash::default_dir() { Some(dir) => { println!("crash reports: {}", dir.display()); - for r in solvecraft_engine::crash::list(&dir).iter().take(5) { + for r in solidcraft_engine::crash::list(&dir).iter().take(5) { println!(" {} {} {}", r.id, r.kind, r.message); } println!("more: solidcraft-cli crashes"); @@ -178,21 +211,21 @@ fn main() -> eframe::Result { return Ok(()); } "--help" | "-h" => { - println!("usage: solidcraft [--control PORT] [--sample] [--crash-log] [design.solvecraft | part.step]"); + println!("usage: solidcraft [--control PORT] [--sample] [--crash-log] [design.solidcraft | part.step]"); return Ok(()); } _ => files.push(a), } } - // Crashes leave a report in the crash folder (solvecraft_engine::crash, docs/crash-reports.md). - let crashed = solvecraft_engine::crash::default_dir() + // Crashes leave a report in the crash folder (solidcraft_engine::crash, docs/crash-reports.md). + let crashed = solidcraft_engine::crash::default_dir() .map(|dir| { - let build = solvecraft_engine::crash::Build { - version: solvecraft_ui_egui::help::VERSION, - commit: solvecraft_ui_egui::help::COMMIT, - built: solvecraft_ui_egui::help::BUILD_DATE, + let build = solidcraft_engine::crash::Build { + version: solidcraft_ui_egui::help::VERSION, + commit: solidcraft_ui_egui::help::COMMIT, + built: solidcraft_ui_egui::help::BUILD_DATE, }; - solvecraft_engine::crash::install(dir, build) + solidcraft_engine::crash::install(dir, build) }) .unwrap_or_default(); let options = eframe::NativeOptions { @@ -200,6 +233,8 @@ fn main() -> eframe::Result { depth_buffer: DEPTH_BITS, stencil_buffer: STENCIL_BITS, multisampling: samples, + // Kept with the other settings, not in a folder named after the app id. + persistence_path: settings_dir().map(|d| d.join("app.ron")), ..Default::default() }; #[cfg(all(unix, not(target_os = "macos")))] @@ -209,7 +244,7 @@ fn main() -> eframe::Result { std::env::var_os("DISPLAY").is_some(), std::env::var_os("WAYLAND_DISPLAY").is_some(), std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref(), - std::env::var("SOLVECRAFT_WAYLAND").ok().as_deref(), + solidcraft_engine::geom::env::var("WAYLAND").as_deref(), ) { options.event_loop_builder = Some(Box::new(|b| { use winit::platform::x11::EventLoopBuilderExtX11; @@ -220,20 +255,20 @@ fn main() -> eframe::Result { "SolidCraft", options, Box::new(move |cc| { - solvecraft_ui_egui::fonts::setup_fonts(&cc.egui_ctx); + solidcraft_ui_egui::fonts::setup_fonts(&cc.egui_ctx); let mut app = SolveApp::new(Session::default(), services()); app.custom_titlebar = CUSTOM_TITLEBAR; app.integrated_titlebar = cfg!(target_os = "macos"); - if let Some(p) = cc.storage.and_then(|s| s.get_string(PREFS_KEY)) { + if let Some(p) = cc.storage.and_then(|s| solidcraft_ui_egui::prefs::stored(|k| s.get_string(k))) { app.load_prefs(&p); } if let Some(rs) = &cc.wgpu_render_state { eprintln!("SolidCraft graphics: {:?}, MSAA {}x (requested {}x)", rs.adapter.get_info().backend, rs.msaa_samples, samples); app.set_wgpu(rs, DEPTH_BITS, STENCIL_BITS, rs.msaa_samples); // A saved anti-aliasing the device can't do: back to the default, with a notice. - solvecraft_ui_egui::prefs::check_msaa(&mut app, u32::from(samples)); + solidcraft_ui_egui::prefs::check_msaa(&mut app, u32::from(samples)); let info = rs.adapter.get_info(); - solvecraft_engine::crash::set_environment(&format!( + solidcraft_engine::crash::set_environment(&format!( "Renderer: {:?} {} ({}), MSAA {}x", info.backend, info.name, info.driver, rs.msaa_samples )); @@ -243,7 +278,7 @@ fn main() -> eframe::Result { app = app.with_control(rx); } if sample { - if let Err(e) = app.session.run_script(&solvecraft_engine::sample::script()) { + if let Err(e) = app.session.run_script(&solidcraft_engine::sample::script()) { app.set_status(format!("sample: {e}"), true); } app.session.doc_mut().name = "Sample Plate".into(); @@ -260,12 +295,12 @@ fn main() -> eframe::Result { app.home.show(rev); } // Autosave, and offer what crashed sessions left behind. - if let Some(dir) = solvecraft_engine::recovery::default_dir() { + if let Some(dir) = solidcraft_engine::recovery::default_dir() { let every = if app.autosave_minutes > 0.0 { app.autosave_minutes * 60.0 } else { f64::MAX / 4.0 }; - solvecraft_ui_egui::recovery_ui::start(&mut app, &dir, std::time::Duration::from_secs_f64(every.min(1e9))); + solidcraft_ui_egui::recovery_ui::start(&mut app, &dir, std::time::Duration::from_secs_f64(every.min(1e9))); } // Tell the user about crashes since the last start (after the recovery offer is set up). - solvecraft_ui_egui::crash_ui::announce(&mut app, &crashed); + solidcraft_ui_egui::crash_ui::announce(&mut app, &crashed); #[cfg(any(target_os = "windows", target_os = "linux"))] let space_mouse = space_mouse::SpaceMouse::start(&cc.egui_ctx); Ok(Box::new(App { @@ -281,7 +316,7 @@ fn main() -> eframe::Result { /// on Wayland (only on X11), so dropping files from the file manager showed a "no" cursor: use /// XWayland when it's there. Except on niri, whose XWayland (xwayland-satellite) breaks window /// management (maximize/restore), so it stays on native Wayland and gives up file drops. -/// `SOLVECRAFT_WAYLAND=1` keeps native Wayland, `SOLVECRAFT_WAYLAND=0` forces XWayland. +/// `SOLIDCRAFT_WAYLAND=1` keeps native Wayland, `SOLIDCRAFT_WAYLAND=0` forces XWayland. #[cfg(all(unix, not(target_os = "macos")))] fn prefer_x11(display: bool, wayland_display: bool, desktop: Option<&str>, wayland_override: Option<&str>) -> bool { match wayland_override.map(str::trim) { @@ -308,7 +343,7 @@ mod backend_tests { assert!(!prefer_x11(true, true, Some("niri:GNOME"), None)); // niri without a Wayland socket (nested X session) still uses X11. assert!(prefer_x11(true, false, Some("niri"), None)); - // SOLVECRAFT_WAYLAND: anything but 0 keeps Wayland, 0 forces XWayland (when present). + // SOLIDCRAFT_WAYLAND: anything but 0 keeps Wayland, 0 forces XWayland (when present). assert!(!prefer_x11(true, true, Some("GNOME"), Some("1"))); assert!(!prefer_x11(true, true, Some("GNOME"), Some(""))); assert!(prefer_x11(true, true, Some("niri"), Some("0"))); @@ -323,7 +358,7 @@ mod icon_tests { fn macos_preserves_the_bundle_icon_instead_of_setting_a_runtime_bitmap() { let viewport = super::app_viewport().expect("viewport"); assert_eq!(viewport.icon.as_deref(), Some(&eframe::egui::IconData::default())); - assert_eq!(viewport.app_id.as_deref(), Some("ai.storyteller.solvecraft")); + assert_eq!(viewport.app_id.as_deref(), Some(super::APP_ID)); } #[cfg(not(target_os = "macos"))] @@ -333,12 +368,34 @@ mod icon_tests { let icon = viewport.icon.expect("running window must have an icon"); assert_eq!((icon.width, icon.height), (64, 64)); assert_eq!(icon.rgba, super::app_icon::rgba(64).expect("original mark")); - assert_eq!(viewport.app_id.as_deref(), Some("ai.storyteller.solvecraft")); + assert_eq!(viewport.app_id.as_deref(), Some(super::APP_ID)); } } #[cfg(test)] mod msaa_tests { + #[test] + fn first_launch_adopts_the_previous_release_settings() { + let d = std::env::temp_dir().join(format!("solidcraft-adopt-settings-{}", std::process::id())); + let _ = std::fs::remove_dir_all(&d); + let (current, legacy, legacy_eframe) = (d.join("solidcraft"), d.join("solvecraft"), d.join("ai.storyteller.solvecraft")); + std::fs::create_dir_all(&legacy).unwrap(); + std::fs::create_dir_all(&legacy_eframe).unwrap(); + std::fs::write(legacy.join("graphics.json"), r#"{"msaa":8}"#).unwrap(); + std::fs::write(legacy_eframe.join("app.ron"), "(prefs)").unwrap(); + super::adopt_settings(¤t, &legacy, Some(&legacy_eframe)).unwrap(); + assert_eq!(std::fs::read_to_string(current.join("graphics.json")).unwrap(), r#"{"msaa":8}"#); + assert_eq!(std::fs::read_to_string(current.join("app.ron")).unwrap(), "(prefs)"); + assert!(legacy.join("graphics.json").is_file() && legacy_eframe.join("app.ron").is_file()); + // Later launches leave the current settings alone. + std::fs::write(current.join("app.ron"), "(mine)").unwrap(); + std::fs::remove_file(current.join("graphics.json")).unwrap(); + super::adopt_settings(¤t, &legacy, Some(&legacy_eframe)).unwrap(); + assert_eq!(std::fs::read_to_string(current.join("app.ron")).unwrap(), "(mine)"); + assert!(!current.join("graphics.json").exists()); + let _ = std::fs::remove_dir_all(&d); + } + #[test] fn saved_multisampling_uses_the_selected_backend_policy() { use eframe::wgpu::Backend; diff --git a/apps/solvecraft/src/space_mouse.rs b/apps/solidcraft/src/space_mouse.rs similarity index 97% rename from apps/solvecraft/src/space_mouse.rs rename to apps/solidcraft/src/space_mouse.rs index 6698453f..0880577f 100644 --- a/apps/solvecraft/src/space_mouse.rs +++ b/apps/solidcraft/src/space_mouse.rs @@ -15,8 +15,8 @@ use hid_descriptor::Layout; use hidapi::{DeviceInfo, HidApi}; use quick_xml::Reader; use quick_xml::events::Event; -use solvecraft_engine::render::Camera; -use solvecraft_ui_egui::SolveApp; +use solidcraft_engine::render::Camera; +use solidcraft_ui_egui::SolveApp; const STALE_AFTER: Duration = Duration::from_millis(150); const RETRY_AFTER: Duration = Duration::from_secs(2); @@ -185,7 +185,10 @@ fn parse_driver_mapping(xml: &[u8]) -> Option { } fn load_driver_mapping() -> Option { - let profile = std::path::PathBuf::from(std::env::var_os("APPDATA")?).join("3Dconnexion").join("3DxWare").join("Cfg").join("solvecraft-KMJ.xml"); + let cfg = std::path::PathBuf::from(std::env::var_os("APPDATA")?).join("3Dconnexion").join("3DxWare").join("Cfg"); + // The driver names the profile after the executable; one made for the pre-rename executable + // still applies until the driver writes a new one (compatibility shim). + let profile = ["solidcraft-KMJ.xml", "solvecraft-KMJ.xml"].into_iter().map(|n| cfg.join(n)).find(|p| p.is_file())?; if std::fs::metadata(&profile).ok()?.len() > MAX_PROFILE_BYTES { log::warn!("3D mouse: ignoring oversized 3DxWare profile {}", profile.display()); return None; @@ -371,7 +374,7 @@ impl SpaceMouse { let shared = Arc::new(Mutex::new(Reading::new(now))); let worker_state = Arc::clone(&shared); let worker_ctx = ctx.clone(); - if let Err(error) = std::thread::Builder::new().name("solvecraft-3d-mouse".into()).spawn(move || reader(worker_ctx, worker_state)) { + if let Err(error) = std::thread::Builder::new().name("solidcraft-3d-mouse".into()).spawn(move || reader(worker_ctx, worker_state)) { log::warn!("3D mouse: reader could not start: {error}"); } Self { shared, last_frame: now, driver_mapping: load_driver_mapping().unwrap_or_default(), mapping_checked_at: now } diff --git a/apps/solvecraft/src/space_mouse/hid_descriptor.rs b/apps/solidcraft/src/space_mouse/hid_descriptor.rs similarity index 100% rename from apps/solvecraft/src/space_mouse/hid_descriptor.rs rename to apps/solidcraft/src/space_mouse/hid_descriptor.rs diff --git a/apps/solvecraft/src/space_mouse/hidraw.rs b/apps/solidcraft/src/space_mouse/hidraw.rs similarity index 100% rename from apps/solvecraft/src/space_mouse/hidraw.rs rename to apps/solidcraft/src/space_mouse/hidraw.rs diff --git a/crates/doc/Cargo.toml b/crates/doc/Cargo.toml index 3be05de4..30240420 100644 --- a/crates/doc/Cargo.toml +++ b/crates/doc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "solvecraft-doc" +name = "solidcraft-doc" description = "SolidCraft document model: parameters and expressions, sketches and features on a parametric timeline, incremental evaluation" version.workspace = true edition.workspace = true @@ -9,9 +9,9 @@ license.workspace = true workspace = true [dependencies] -solvecraft-geom = { workspace = true } -solvecraft-sketch = { workspace = true } -solvecraft-kernel = { workspace = true } +solidcraft-geom = { workspace = true } +solidcraft-sketch = { workspace = true } +solidcraft-kernel = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/doc/src/anchor_frame.rs b/crates/doc/src/anchor_frame.rs index 1759bf89..9b7e7b68 100644 --- a/crates/doc/src/anchor_frame.rs +++ b/crates/doc/src/anchor_frame.rs @@ -7,7 +7,7 @@ pub fn state_in_face_anchor_frame<'a>(doc: &Document, state: &'a ModelState, anc let raw = state.body(&anchor.target.body).ok_or_else(|| DocError::Unknown(format!("anchored body `{}`", anchor.target.body)))?; let relative = anchor.source_to_author(doc, author, raw)?; let mut output = crate::state_in_frame_at(doc, state, author, frame).into_owned(); - let shape = if crate::is_identity(&relative) { raw.body.clone() } else { solvecraft_kernel::transform_matrix(&raw.body, relative)? }; + let shape = if crate::is_identity(&relative) { raw.body.clone() } else { solidcraft_kernel::transform_matrix(&raw.body, relative)? }; let mut body = ModelBody::new(raw.name.clone(), shape, raw.feature); body.component = raw.component; body.transferred = raw.transferred; diff --git a/crates/doc/src/appearance.rs b/crates/doc/src/appearance.rs index 08ba9f6c..cfb4eb10 100644 --- a/crates/doc/src/appearance.rs +++ b/crates/doc/src/appearance.rs @@ -6,7 +6,7 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use crate::Document; diff --git a/crates/doc/src/assembly.rs b/crates/doc/src/assembly.rs index b8924d45..0422e883 100644 --- a/crates/doc/src/assembly.rs +++ b/crates/doc/src/assembly.rs @@ -9,7 +9,7 @@ //! and the engine maps a new feature's geometry into the active component's frame. use serde::{Deserialize, Serialize}; -use solvecraft_geom::{Plane, Vec3}; +use solidcraft_geom::{Plane, Vec3}; use crate::{AxisRef, DocError, Document, FeatureKind, PatternKind, PlaneRef, Result}; diff --git a/crates/doc/src/body_transfer.rs b/crates/doc/src/body_transfer.rs index bc629ba5..07fa5071 100644 --- a/crates/doc/src/body_transfer.rs +++ b/crates/doc/src/body_transfer.rs @@ -1,6 +1,6 @@ //! Recorded ownership changes are evaluated at their timeline position. use crate::{DocError, Document, Mat, ModelBody, ModelState, Result}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use std::collections::BTreeSet; pub fn valid_transfer_matrix(m: &Mat) -> bool { @@ -39,7 +39,7 @@ pub(crate) fn evaluate(doc: &Document, st: &mut ModelState, bodies: &[String], f if doc.model_body_component(old) != from { return Err(DocError::Invalid(format!("body `{name}` no longer belongs to transfer source component {from}"))); } - let shape = if crate::is_identity(&transform) { old.body.clone() } else { solvecraft_kernel::transform_matrix(&old.body, transform)? }; + let shape = if crate::is_identity(&transform) { old.body.clone() } else { solidcraft_kernel::transform_matrix(&old.body, transform)? }; let mut body = ModelBody::new(old.name.clone(), shape, old.feature); body.component = to; body.transferred = true; diff --git a/crates/doc/src/body_transfer_sheet.rs b/crates/doc/src/body_transfer_sheet.rs index 10f9297e..be971d30 100644 --- a/crates/doc/src/body_transfer_sheet.rs +++ b/crates/doc/src/body_transfer_sheet.rs @@ -1,5 +1,5 @@ //! World-frame metadata at a timeline body ownership boundary. -use solvecraft_geom::Plane; +use solidcraft_geom::Plane; use crate::{DocError, Mat, ModelState, Result, apply_point, apply_vector}; diff --git a/crates/doc/src/canvas.rs b/crates/doc/src/canvas.rs index 2c7cb9cc..6955a01f 100644 --- a/crates/doc/src/canvas.rs +++ b/crates/doc/src/canvas.rs @@ -2,7 +2,7 @@ //! The image bytes travel inside the design file (base64), so it reopens without the original. use serde::{Deserialize, Serialize}; -use solvecraft_geom::{Plane, Vec2, Vec3}; +use solidcraft_geom::{Plane, Vec2, Vec3}; use crate::PlaneRef; diff --git a/crates/doc/src/clipboard.rs b/crates/doc/src/clipboard.rs index c4ad4a35..63d4e1a9 100644 --- a/crates/doc/src/clipboard.rs +++ b/crates/doc/src/clipboard.rs @@ -202,13 +202,13 @@ fn remap_plane( } fn remap_link( - source: &mut solvecraft_sketch::LinkSource, + source: &mut solidcraft_sketch::LinkSource, ids: &BTreeMap, names: &BTreeMap, bodies: &BTreeMap, depth: usize, ) { - use solvecraft_sketch::LinkSource; + use solidcraft_sketch::LinkSource; if depth > 64 { return; } @@ -441,11 +441,11 @@ fn checked_plane(p: &PlaneRef, depth: usize) -> crate::Result<()> { } Ok(()) } -fn checked_link(source: &solvecraft_sketch::LinkSource, depth: usize) -> crate::Result<()> { +fn checked_link(source: &solidcraft_sketch::LinkSource, depth: usize) -> crate::Result<()> { if depth > 64 { return Err(crate::DocError::Invalid("copy link nesting exceeds 64".into())); } - if let solvecraft_sketch::LinkSource::Intersection { a, b } = source { + if let solidcraft_sketch::LinkSource::Intersection { a, b } = source { checked_link(a, depth + 1)?; checked_link(b, depth + 1)?; } diff --git a/crates/doc/src/config_certificates.rs b/crates/doc/src/config_certificates.rs index a44d2234..66cc709b 100644 --- a/crates/doc/src/config_certificates.rs +++ b/crates/doc/src/config_certificates.rs @@ -1,7 +1,7 @@ //! Certificates are producer identities, never a nearest-surface selection. use crate::{DocError, Document, FeatureKind, ModelBody, Result, SolvedSketch}; use serde::{Deserialize, Serialize}; -use solvecraft_geom::Seg2; +use solidcraft_geom::Seg2; fn valid_signature(outer: &[String], holes: &[Vec]) -> bool { let valid = |ids: &[String]| { @@ -68,11 +68,11 @@ pub(crate) fn cap_key(outer: &[String], holes: &[Vec]) -> String { } format!("{:x}", hash.finalize()) } -fn exact_region_signature(curves: &ExactSketchCurves, region: &solvecraft_geom::Region2) -> Option { +fn exact_region_signature(curves: &ExactSketchCurves, region: &solidcraft_geom::Region2) -> Option { if region.outer.segs.len() + region.holes.iter().map(|hole| hole.segs.len()).sum::() > 4096 || region.holes.len() > 128 { return None; } - let signature = |outline: &solvecraft_geom::Loop2| { + let signature = |outline: &solidcraft_geom::Loop2| { let mut ids = outline.segs.iter().map(|segment| curves.find(segment)).collect::>>()?; ids.sort(); ids.dedup(); @@ -85,9 +85,9 @@ fn exact_region_signature(curves: &ExactSketchCurves, region: &solvecraft_geom:: } /// Charge all source-curve comparisons before matching; over-budget tools stay usable but uncertified. -type CapBindings = Vec>>; -pub(crate) fn extrude_cap_bindings(sketch: &SolvedSketch, bodies: &[solvecraft_kernel::Body], budget: usize) -> Option { - use solvecraft_kernel::FaceSource; +type CapBindings = Vec>>; +pub(crate) fn extrude_cap_bindings(sketch: &SolvedSketch, bodies: &[solidcraft_kernel::Body], budget: usize) -> Option { + use solidcraft_kernel::FaceSource; if sketch.sketch.curves.len() > 2000 { return None; } @@ -116,7 +116,7 @@ pub(crate) fn extrude_cap_bindings(sketch: &SolvedSketch, bodies: &[solvecraft_k let mut counts = std::collections::BTreeMap::new(); let mut bindings = Vec::with_capacity(bodies.len()); for body in bodies { - let mut cached: Option<(&solvecraft_geom::Region2, Option)> = None; + let mut cached: Option<(&solidcraft_geom::Region2, Option)> = None; let mut values = Vec::new(); for source in body.face_sources().into_iter().flatten() { let signature = if let Some(FaceSource::ExtrudeCap { region, high, .. }) = source { @@ -260,8 +260,8 @@ pub(crate) fn shell_certificate_role(source: &FaceCertificate, inner: bool) -> R Ok(format!("shell:{}:{role}", if inner { "inner" } else { "outer" })) } -pub(crate) fn flat_source_certificate(source: &solvecraft_kernel::FaceSource) -> Option<(u64, FaceCertificate)> { - use solvecraft_kernel::FaceSource; +pub(crate) fn flat_source_certificate(source: &solidcraft_kernel::FaceSource) -> Option<(u64, FaceCertificate)> { + use solidcraft_kernel::FaceSource; match source { FaceSource::Box { producer: Some(producer), axis, high, .. } => Some((*producer, FaceCertificate::Box { axis: *axis, high: *high })), FaceSource::CertifiedExtrudeSide { producer, sketch, curve, .. } => { @@ -277,8 +277,8 @@ pub(crate) fn flat_source_certificate(source: &solvecraft_kernel::FaceSource) -> } } -pub(crate) fn flat_source_matches(certificate: &FaceCertificate, source: &solvecraft_kernel::FaceSource, producer: u64) -> bool { - use solvecraft_kernel::FaceSource; +pub(crate) fn flat_source_matches(certificate: &FaceCertificate, source: &solidcraft_kernel::FaceSource, producer: u64) -> bool { + use solidcraft_kernel::FaceSource; match (certificate, source) { (FaceCertificate::Box { axis, high }, FaceSource::Box { producer: Some(current), axis: current_axis, high: current_high, .. }) => { producer == *current && axis == current_axis && high == current_high @@ -324,14 +324,14 @@ impl<'a> ExactSketchCurves<'a> { if curve.construction || !matches!( curve.kind, - solvecraft_sketch::CurveKind::Line { .. } - | solvecraft_sketch::CurveKind::Arc { .. } - | solvecraft_sketch::CurveKind::Circle { .. } + solidcraft_sketch::CurveKind::Line { .. } + | solidcraft_sketch::CurveKind::Arc { .. } + | solidcraft_sketch::CurveKind::Circle { .. } ) { continue; } - let originals = if let solvecraft_sketch::CurveKind::Circle { c, r } = curve.kind { + let originals = if let solidcraft_sketch::CurveKind::Circle { c, r } = curve.kind { sketch .sketch .point(c) @@ -364,7 +364,7 @@ fn exact_segment_contains(original: &Seg2, generated: &Seg2) -> bool { (Seg2::Line { a, b }, Seg2::Line { a: c, b: d }) => { let direction = *b - *a; let scale = direction.len().max((*d - *c).len()).max(1.); - let interval = |point: solvecraft_geom::Vec2| { + let interval = |point: solidcraft_geom::Vec2| { let t = (point - *a).dot(direction) / direction.len2(); (-1e-10..=1. + 1e-10).contains(&t) }; @@ -400,18 +400,18 @@ fn exact_segment_contains(original: &Seg2, generated: &Seg2) -> bool { } impl Document { - pub fn configuration_face_target(&self, body: &ModelBody, index: usize, point: solvecraft_geom::Vec3) -> Result { + pub fn configuration_face_target(&self, body: &ModelBody, index: usize, point: solidcraft_geom::Vec3) -> Result { let names = crate::naming::face_names(body); let mut name = names .get(index) .map(|name| crate::naming::strip_piece(name).to_string()) .ok_or_else(|| DocError::Unknown("face source name missing".into()))?; let origin = match body.body.face_sources().and_then(|sources| sources.get(index)).and_then(Option::as_ref) { - Some(solvecraft_kernel::FaceSource::Imported { producer: Some(producer), .. }) - | Some(solvecraft_kernel::FaceSource::CertifiedExtrudeSide { producer, .. }) - | Some(solvecraft_kernel::FaceSource::CertifiedExtrudeCap { producer, .. }) - | Some(solvecraft_kernel::FaceSource::ShellPlanar { producer: Some(producer), .. }) - | Some(solvecraft_kernel::FaceSource::Box { producer: Some(producer), .. }) => *producer, + Some(solidcraft_kernel::FaceSource::Imported { producer: Some(producer), .. }) + | Some(solidcraft_kernel::FaceSource::CertifiedExtrudeSide { producer, .. }) + | Some(solidcraft_kernel::FaceSource::CertifiedExtrudeCap { producer, .. }) + | Some(solidcraft_kernel::FaceSource::ShellPlanar { producer: Some(producer), .. }) + | Some(solidcraft_kernel::FaceSource::Box { producer: Some(producer), .. }) => *producer, _ => name .strip_prefix('F') .and_then(|name| name.split_once(':')) @@ -439,7 +439,7 @@ impl Document { let Some(source) = body.body.face_sources().and_then(|sources| sources.get(index)).and_then(Option::as_ref) else { return Ok(None); }; - if let solvecraft_kernel::FaceSource::ShellPlanar { producer: Some(producer), source, inner, .. } = source { + if let solidcraft_kernel::FaceSource::ShellPlanar { producer: Some(producer), source, inner, .. } = source { if !self.feature(*producer).is_some_and(|feature| matches!(feature.kind, FeatureKind::Shell { .. })) { return Err(DocError::Invalid("shell source producer missing".into())); } @@ -452,7 +452,7 @@ impl Document { certificate.validate()?; return Ok(Some(certificate)); } - if let solvecraft_kernel::FaceSource::Imported { item, face, payload_sha256: Some(digest), producer: Some(producer) } = source { + if let solidcraft_kernel::FaceSource::Imported { item, face, payload_sha256: Some(digest), producer: Some(producer) } = source { if !self.feature(*producer).is_some_and(|feature| matches!(feature.kind, FeatureKind::Import { .. })) { return Err(DocError::Invalid("import source producer missing".into())); } @@ -461,7 +461,7 @@ impl Document { return Ok(Some(certificate)); } let certificate = match source { - solvecraft_kernel::FaceSource::CertifiedExtrudeSide { producer, sketch, curve, .. } => { + solidcraft_kernel::FaceSource::CertifiedExtrudeSide { producer, sketch, curve, .. } => { if !self.feature(*producer).is_some_and(|feature| matches!(feature.kind, FeatureKind::Extrude { .. })) { return Err(DocError::Invalid("side source producer missing".into())); } @@ -472,16 +472,16 @@ impl Document { certificate.validate()?; Ok(Some(certificate)) } - pub fn strong_face_target(&self, body: &ModelBody, index: usize, point: solvecraft_geom::Vec3) -> Result { + pub fn strong_face_target(&self, body: &ModelBody, index: usize, point: solidcraft_geom::Vec3) -> Result { let mut target = self.configuration_face_target(body, index, point)?; match body.body.face_sources().and_then(|sources| sources.get(index)).and_then(Option::as_ref) { - Some(solvecraft_kernel::FaceSource::Box { producer: Some(producer), axis, high, .. }) => { + Some(solidcraft_kernel::FaceSource::Box { producer: Some(producer), axis, high, .. }) => { target.origin_feature = *producer; let axis_name = ["x", "y", "z"].get(*axis as usize).ok_or_else(|| DocError::Invalid("invalid box face axis".into()))?; target.name = format!("F{producer}:{}{axis_name}", if *high { "+" } else { "-" }); target.certificate = Some(FaceCertificate::Box { axis: *axis, high: *high }); } - Some(solvecraft_kernel::FaceSource::CertifiedExtrudeCap { producer, sketch, high, outer, holes, .. }) => { + Some(solidcraft_kernel::FaceSource::CertifiedExtrudeCap { producer, sketch, high, outer, holes, .. }) => { target.origin_feature = *producer; target.name = format!("F{producer}:cap:{}:{}", if *high { "high" } else { "low" }, cap_key(outer, holes)); target.certificate = Some(FaceCertificate::ExtrudeCap { sketch: *sketch, high: *high, outer: outer.clone(), holes: holes.clone() }); @@ -507,15 +507,15 @@ mod tests { use super::*; use serde_json::json; - fn solved(mut sketch: solvecraft_sketch::Sketch) -> SolvedSketch { - let report = solvecraft_sketch::solve(&mut sketch); - SolvedSketch { feature: 1, name: "sources".into(), plane: solvecraft_geom::Plane::XY, sketch, report, profiles: Vec::new() } + fn solved(mut sketch: solidcraft_sketch::Sketch) -> SolvedSketch { + let report = solidcraft_sketch::solve(&mut sketch); + SolvedSketch { feature: 1, name: "sources".into(), plane: solidcraft_geom::Plane::XY, sketch, report, profiles: Vec::new() } } #[test] fn prepared_sources_keep_exact_identity_and_reject_ambiguity() { - use solvecraft_geom::Vec2; - let mut sketch = solvecraft_sketch::Sketch::new(); + use solidcraft_geom::Vec2; + let mut sketch = solidcraft_sketch::Sketch::new(); sketch.add_line(Vec2::ZERO, Vec2::new(10., 0.), None, None, Some("line")).unwrap(); sketch.add_circle(Vec2::new(20., 0.), 2., None, Some("circle")).unwrap(); let construction = sketch.add_line(Vec2::ZERO, Vec2::new(10., 0.), None, None, Some("construction")).unwrap(); @@ -532,7 +532,7 @@ mod tests { // A prepared pass borrows one immutable evaluated sketch, so an upstream edit // necessarily prepares fresh sources rather than retaining stale coordinates. let mut moved = sketch.clone(); - moved.sketch = solvecraft_sketch::Sketch::new(); + moved.sketch = solidcraft_sketch::Sketch::new(); moved.sketch.add_line(Vec2::new(0., 1.), Vec2::new(10., 1.), None, None, Some("line")).unwrap(); assert_eq!(ExactSketchCurves::new(&moved).find(&partial), None); } @@ -540,8 +540,8 @@ mod tests { #[test] #[ignore = "manual optimized certification microbenchmark; no timing assertion"] fn benchmark_prepared_extrude_sources() { - use solvecraft_geom::Vec2; - let mut sketch = solvecraft_sketch::Sketch::new(); + use solidcraft_geom::Vec2; + let mut sketch = solidcraft_sketch::Sketch::new(); for i in 0..1000 { let y = i as f64; sketch.add_line(Vec2::new(0., y), Vec2::new(10., y), None, None, None).unwrap(); @@ -593,7 +593,7 @@ mod tests { #[test] fn analytic_arc_interval_refuses_unsampled_gaps_and_accepts_wrap_reversal() { let radians = |degrees: f64| degrees.to_radians(); - let arc = |start, sweep| Seg2::Arc { center: solvecraft_geom::Vec2::default(), radius: 10., start: radians(start), sweep: radians(sweep) }; + let arc = |start, sweep| Seg2::Arc { center: solidcraft_geom::Vec2::default(), radius: 10., start: radians(start), sweep: radians(sweep) }; assert!(!exact_segment_contains(&arc(10., 350.), &arc(20., 360.)), "five point probes missed the missing ten-degree gap"); assert!(!exact_segment_contains(&arc(10., 350.), &arc(20., -360.))); assert!(exact_segment_contains(&arc(350., 40.), &arc(355., 20.))); diff --git a/crates/doc/src/config_clone.rs b/crates/doc/src/config_clone.rs index da1aac92..0821c538 100644 --- a/crates/doc/src/config_clone.rs +++ b/crates/doc/src/config_clone.rs @@ -142,7 +142,7 @@ mod tests { body: "Old".into(), origin_feature: 3, name: "F3:+z".into(), - point: solvecraft_geom::Vec3::new(1., 1., 2.), + point: solidcraft_geom::Vec3::new(1., 1., 2.), certificate: None, }), ], @@ -199,7 +199,7 @@ mod tests { body: "Foreign".into(), origin_feature: 3, name: "F3:+z".into(), - point: solvecraft_geom::Vec3::ZERO, + point: solidcraft_geom::Vec3::ZERO, certificate: None, }), ], diff --git a/crates/doc/src/config_shell_tests.rs b/crates/doc/src/config_shell_tests.rs index 4bb90342..65499c17 100644 --- a/crates/doc/src/config_shell_tests.rs +++ b/crates/doc/src/config_shell_tests.rs @@ -1,7 +1,7 @@ use super::*; use crate::{FeatureKind, Model, PlaneRef}; use serde_json::json; -use solvecraft_geom::{Vec2, Vec3}; +use solidcraft_geom::{Vec2, Vec3}; fn shell_document() -> (Document, u64, u64) { let mut doc = Document::new("certified shell"); @@ -35,23 +35,23 @@ fn face_index(body: &ModelBody, inner: bool) -> usize { .unwrap() .iter() .position(|source| { - matches!(source, Some(solvecraft_kernel::FaceSource::ShellPlanar { inner: current, source, .. }) - if *current == inner && matches!(source.as_ref(), solvecraft_kernel::FaceSource::Box { axis:0,high:true,.. })) + matches!(source, Some(solidcraft_kernel::FaceSource::ShellPlanar { inner: current, source, .. }) + if *current == inner && matches!(source.as_ref(), solidcraft_kernel::FaceSource::Box { axis:0,high:true,.. })) }) .unwrap() } #[test] fn shell_planar_raw_bounds_flat_identity_and_clone_remap() { - let plane = solvecraft_geom::Plane::XY; - let same = |planes: &[Option]| super::super::styles::shell_plane_family(planes.iter().copied()); + let plane = solidcraft_geom::Plane::XY; + let same = |planes: &[Option]| super::super::styles::shell_plane_family(planes.iter().copied()); assert!(same(&[Some(plane), Some(plane)])); assert!(!same(&[])); assert!(!same(&[Some(plane), None])); for different in [ - solvecraft_geom::Plane { origin: Vec3::X, ..plane }, - solvecraft_geom::Plane { x: -plane.x, y: -plane.y, ..plane }, - solvecraft_geom::Plane { y: -plane.y, ..plane }, + solidcraft_geom::Plane { origin: Vec3::X, ..plane }, + solidcraft_geom::Plane { x: -plane.x, y: -plane.y, ..plane }, + solidcraft_geom::Plane { y: -plane.y, ..plane }, ] { assert!(!same(&[Some(plane), Some(different)])); } @@ -82,15 +82,15 @@ fn shell_planar_anchor_rebuild_cold_geometry_and_missing_source_errors() { let body = state.body("Case").unwrap(); let outer = doc.strong_face_target(body, face_index(body, false), Vec3::default()).unwrap(); let outer_matches: Vec<_> = body.body.face_sources().unwrap().iter().enumerate().filter_map(|(index, source)| { - matches!(source, Some(solvecraft_kernel::FaceSource::ShellPlanar { inner: false, source, .. }) if matches!(source.as_ref(), solvecraft_kernel::FaceSource::Box { axis: 0, high: true, .. })).then_some((index, body.body.face_plane(index))) + matches!(source, Some(solidcraft_kernel::FaceSource::ShellPlanar { inner: false, source, .. }) if matches!(source.as_ref(), solidcraft_kernel::FaceSource::Box { axis: 0, high: true, .. })).then_some((index, body.body.face_plane(index))) }).collect(); assert_eq!(outer_matches.len(), 1, "outer support must be one physical face: {outer_matches:?}"); assert!(super::super::styles::shell_plane_family(outer_matches.iter().map(|(_, plane)| *plane))); let outer_plane = body.body.face_plane(face_index(body, false)).unwrap(); let outer_anchor = doc.capture_face_anchor(body, face_index(body, false), &outer_plane, &[], 0, &[]).unwrap(); - let projected = crate::project::resolve_anchored_face(&doc, &state, &outer_plane, solvecraft_sketch::LinkKind::Project, &outer_anchor).unwrap(); - let mut linked = solvecraft_sketch::Sketch::new(); - let link = linked.add_link(solvecraft_sketch::LinkKind::Project, projected.source, &projected.geom).unwrap(); + let projected = crate::project::resolve_anchored_face(&doc, &state, &outer_plane, solidcraft_sketch::LinkKind::Project, &outer_anchor).unwrap(); + let mut linked = solidcraft_sketch::Sketch::new(); + let link = linked.add_link(solidcraft_sketch::LinkKind::Project, projected.source, &projected.geom).unwrap(); assert!(!linked.update_link(&link, &projected.geom).unwrap()); let inner_index = face_index(body, true); let inner = doc.strong_face_target(body, inner_index, Vec3::default()).unwrap(); @@ -100,7 +100,7 @@ fn shell_planar_anchor_rebuild_cold_geometry_and_missing_source_errors() { let picked = body.body.face_plane(inner_index).unwrap(); assert!((picked.origin.x - 18.).abs() < 1e-8); let anchor = doc.capture_face_anchor(body, inner_index, &picked, &[], 0, &[]).unwrap(); - let mut sketch = solvecraft_sketch::Sketch::new(); + let mut sketch = solidcraft_sketch::Sketch::new(); let points: Vec<_> = [(2., 2.), (4., 2.), (4., 4.), (2., 4.)].into_iter().map(|(x, y)| sketch.add_point(Vec2::new(x, y), None).unwrap()).collect(); for edge in 0..4 { @@ -136,11 +136,11 @@ fn shell_planar_anchor_rebuild_cold_geometry_and_missing_source_errors() { let plane = doc.resolve_face_anchor(&anchor, 0, &changed).unwrap(); assert!((plane.origin.x - 27.).abs() < 1e-8); let after = changed.bodies.iter().find(|body| body.feature == boss).unwrap().mesh().bounds(); - assert!((solvecraft_kernel::measure(&changed.bodies.iter().find(|body| body.feature == boss).unwrap().body).unwrap().volume - 8.).abs() < 1e-8); + assert!((solidcraft_kernel::measure(&changed.bodies.iter().find(|body| body.feature == boss).unwrap().body).unwrap().volume - 8.).abs() < 1e-8); assert!((doc.resolve_face_anchor(&outer_anchor, 0, &changed).unwrap().origin.x - 30.).abs() < 1e-8); let changed_outer_plane = doc.resolve_face_anchor(&outer_anchor, 0, &changed).unwrap(); let refreshed = - crate::project::resolve_anchored_face(&doc, &changed, &changed_outer_plane, solvecraft_sketch::LinkKind::Project, &outer_anchor).unwrap(); + crate::project::resolve_anchored_face(&doc, &changed, &changed_outer_plane, solidcraft_sketch::LinkKind::Project, &outer_anchor).unwrap(); assert!(!linked.update_link(&link, &refreshed.geom).unwrap(), "same exterior boundary must retain projected IDs"); assert!((after.min.x - before.min.x - 9.).abs() < 1e-8, "{before:?} -> {after:?}"); assert!((after.max.x - before.max.x - 9.).abs() < 1e-8, "{before:?} -> {after:?}"); @@ -173,11 +173,11 @@ fn shell_planar_anchor_rebuild_cold_geometry_and_missing_source_errors() { fn shell_planar_format13_keeps_v12_documents_and_refuses_future() { let (doc, _, _) = shell_document(); let mut legacy: serde_json::Value = serde_json::from_str(&doc.to_json()).unwrap(); - legacy["format"] = json!("solvecraft/12"); + legacy["format"] = json!("solidcraft/12"); let loaded = crate::format::read(&legacy.to_string()).unwrap(); let mut current: serde_json::Value = serde_json::from_str(&loaded.to_json()).unwrap(); assert_eq!(current["format"], crate::format::format_string(crate::format::FORMAT)); - current["format"] = json!("solvecraft/12"); + current["format"] = json!("solidcraft/12"); crate::format::pieces_in_shell_order(&mut legacy["features"]); assert_eq!(legacy, current, "the migration preserves every prior field"); legacy["format"] = json!(crate::format::format_string(crate::format::FORMAT + 1)); @@ -199,7 +199,7 @@ fn regularized_shell_keeps_legacy_and_explicit_single_face_projections_closed() let body = state.body("Case").unwrap(); let index = face_index(body, false); let mesh = body.mesh(); - assert_eq!(body.body.face_sources().unwrap().iter().filter(|source|matches!(source,Some(solvecraft_kernel::FaceSource::ShellPlanar {inner:false,source,..}) if matches!(source.as_ref(),solvecraft_kernel::FaceSource::Box {axis:0,high:true,..}))).count(),1,"no physical outer-wall strip face may remain"); + assert_eq!(body.body.face_sources().unwrap().iter().filter(|source|matches!(source,Some(solidcraft_kernel::FaceSource::ShellPlanar {inner:false,source,..}) if matches!(source.as_ref(),solidcraft_kernel::FaceSource::Box {axis:0,high:true,..}))).count(),1,"no physical outer-wall strip face may remain"); let plane = body.body.face_plane(index).unwrap(); let point = mesh .triangles @@ -208,14 +208,14 @@ fn regularized_shell_keeps_legacy_and_explicit_single_face_projections_closed() .find_map(|(tri, face)| (*face as usize == index).then(|| mesh.tri(tri)).flatten()) .map(|[a, b, c]| (a + b + c) / 3.) .unwrap(); - let source = solvecraft_sketch::LinkSource::Face { body: "Case".into(), at: point }; + let source = solidcraft_sketch::LinkSource::Face { body: "Case".into(), at: point }; let (values, _) = doc.param_values(); - let legacy = crate::project::resolve(&doc, &values, &state, &plane, solvecraft_sketch::LinkKind::Project, &source).unwrap(); - let explicit = crate::project::resolve_face_indices(&state, &plane, solvecraft_sketch::LinkKind::Project, "Case", &[index]).unwrap(); + let legacy = crate::project::resolve(&doc, &values, &state, &plane, solidcraft_sketch::LinkKind::Project, &source).unwrap(); + let explicit = crate::project::resolve_face_indices(&state, &plane, solidcraft_sketch::LinkKind::Project, "Case", &[index]).unwrap(); assert_eq!(legacy.geom, explicit.geom); assert_eq!(explicit.geom.len(), 4, "raw planar piece keeps all four edges"); - let mut sketch = solvecraft_sketch::Sketch::new(); - let link = sketch.add_link(solvecraft_sketch::LinkKind::Project, source, &legacy.geom).unwrap(); + let mut sketch = solidcraft_sketch::Sketch::new(); + let link = sketch.add_link(solidcraft_sketch::LinkKind::Project, source, &legacy.geom).unwrap(); sketch.links[0].face_name = Some(crate::naming::face_names(body)[index].clone()); let warnings = crate::project::refresh_links(&doc, &values, &state, &plane, &mut sketch); assert!(warnings.is_empty(), "{warnings:?}"); @@ -242,11 +242,11 @@ fn saved_former_strip_projection_reports_lost_without_expanding_healthy_material // Legacy Shell did not propagate construction certificates. Omit transient // metadata here; operand paint also disables new wall-run coalescence so // this control retains the actual historical raw pieces. - let raw_source = solvecraft_kernel::box_solid(Vec3::ZERO, Vec3::new(20., 12., 10.)).unwrap().with_paint(Some(solvecraft_kernel::Paint { + let raw_source = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(20., 12., 10.)).unwrap().with_paint(Some(solidcraft_kernel::Paint { opacity: 1., - faces: vec![solvecraft_kernel::FacePaint { face: 0, color: [1., 0., 0.], opacity: 1. }], + faces: vec![solidcraft_kernel::FacePaint { face: 0, color: [1., 0., 0.], opacity: 1. }], })); - let raw = solvecraft_kernel::shell(&raw_source, &[Vec3::new(5., 6., 10.)], 2.).unwrap(); + let raw = solidcraft_kernel::shell(&raw_source, &[Vec3::new(5., 6., 10.)], 2.).unwrap(); let mut old_body = ModelBody::new("Case".into(), raw, shell); old_body.names = crate::naming::NameCell::new(Some(std::sync::Arc::new(crate::naming::NamingOrigin { feature: doc.feature(shell).unwrap().clone(), @@ -258,13 +258,13 @@ fn saved_former_strip_projection_reports_lost_without_expanding_healthy_material assert_eq!(crate::naming::parent_piece(&captured), Some(stem.as_str()), "actual strip capture records a piece name"); // What the builds before format 14 saved for that piece: its number by centroid order. let former = format!("{stem}#1"); - let mut sketch = solvecraft_sketch::Sketch::new(); + let mut sketch = solidcraft_sketch::Sketch::new(); let corners = [Vec2::new(0., 0.), Vec2::new(12., 0.), Vec2::new(12., 2.), Vec2::new(0., 2.)]; - let geometry: Vec<_> = (0..4).map(|i| solvecraft_sketch::LinkGeom::Line(corners[i], corners[(i + 1) % 4])).collect(); + let geometry: Vec<_> = (0..4).map(|i| solidcraft_sketch::LinkGeom::Line(corners[i], corners[(i + 1) % 4])).collect(); sketch .add_link( - solvecraft_sketch::LinkKind::Project, - solvecraft_sketch::LinkSource::Face { body: "Case".into(), at: Vec3::new(20., 6., 1.) }, + solidcraft_sketch::LinkKind::Project, + solidcraft_sketch::LinkSource::Face { body: "Case".into(), at: Vec3::new(20., 6., 1.) }, &geometry, ) .unwrap(); @@ -289,7 +289,7 @@ fn saved_former_strip_projection_reports_lost_without_expanding_healthy_material ) .unwrap(); let mut legacy: serde_json::Value = serde_json::from_str(&doc.to_json()).unwrap(); - legacy["format"] = json!("solvecraft/12"); + legacy["format"] = json!("solidcraft/12"); let mut doc = crate::format::read(&legacy.to_string()).unwrap(); for width in [20., 30.] { doc.set_param("width", &width.to_string(), Some("mm"), None).unwrap(); @@ -308,7 +308,7 @@ fn saved_former_strip_projection_reports_lost_without_expanding_healthy_material assert_eq!(solved.sketch.points, original.points); let material = state.bodies.iter().find(|body| body.feature == boss).unwrap(); assert!( - (solvecraft_kernel::measure(&material.body).unwrap().volume - 24.).abs() < 1e-8, + (solidcraft_kernel::measure(&material.body).unwrap().volume - 24.).abs() < 1e-8, "old24mm3 profile must not silently become120mm3 wall profile" ); let bounds = material.mesh().bounds(); @@ -320,8 +320,8 @@ fn saved_former_strip_projection_reports_lost_without_expanding_healthy_material #[test] fn saved_curved_strip_projection_is_lost_after_exact_wall_coalescence() { - use solvecraft_kernel::{FacePaint, Paint}; - use solvecraft_sketch::{LinkKind, LinkSource, Sketch}; + use solidcraft_kernel::{FacePaint, Paint}; + use solidcraft_sketch::{LinkKind, LinkSource, Sketch}; let mut doc = Document::new("saved curved strip"); doc.set_param("width", "80", Some("mm"), None).unwrap(); doc.set_param("wall", "2", Some("mm"), None).unwrap(); @@ -362,14 +362,14 @@ fn saved_curved_strip_projection_is_lost_after_exact_wall_coalescence() { .body .clone() .with_paint(Some(Paint { opacity: 1., faces: vec![FacePaint { face: 0, color: [1., 0., 0.], opacity: 1. }] })); - let cavity_box = solvecraft_kernel::box_solid(Vec3::new(-38., -28., 2.), Vec3::new(38., 28., 35.)).unwrap(); - let cavity = solvecraft_kernel::fillet( + let cavity_box = solidcraft_kernel::box_solid(Vec3::new(-38., -28., 2.), Vec3::new(38., 28., 35.)).unwrap(); + let cavity = solidcraft_kernel::fillet( &cavity_box, &[Vec3::new(38., 28., 15.), Vec3::new(-38., 28., 15.), Vec3::new(-38., -28., 15.), Vec3::new(38., -28., 15.)], 4., ) .unwrap(); - let raw = solvecraft_kernel::boolean(&old_source, &cavity, solvecraft_kernel::BoolOp::Cut).unwrap().unwrap(); + let raw = solidcraft_kernel::boolean(&old_source, &cavity, solidcraft_kernel::BoolOp::Cut).unwrap().unwrap(); let mut old_body = ModelBody::new("Case".into(), raw, shell); old_body.names = crate::naming::NameCell::new(Some(std::sync::Arc::new(crate::naming::NamingOrigin { feature: doc.feature(shell).unwrap().clone(), @@ -403,18 +403,18 @@ fn saved_curved_strip_projection_is_lost_after_exact_wall_coalescence() { continue; } let points = &mesh.edges[index]; - let a = solvecraft_geom::Plane::YZ.to_local(*points.first().unwrap()); - let b = solvecraft_geom::Plane::YZ.to_local(*points.last().unwrap()); + let a = solidcraft_geom::Plane::YZ.to_local(*points.first().unwrap()); + let b = solidcraft_geom::Plane::YZ.to_local(*points.last().unwrap()); let (a, b) = (Vec2::new(a.x, a.y), Vec2::new(b.x, b.y)); assert!(a.dist(b) > 1e-6); for point in points { - let q = solvecraft_geom::Plane::YZ.to_local(*point); + let q = solidcraft_geom::Plane::YZ.to_local(*point); let q = Vec2::new(q.x, q.y); let direction = b - a; let t = ((q - a).dot(direction) / direction.dot(direction)).clamp(0., 1.); assert!(q.dist(a + direction * t) < 1e-7, "exact cylindrical quadrant projects to a straight support"); } - geometry.push(solvecraft_sketch::LinkGeom::Line(a, b)); + geometry.push(solidcraft_sketch::LinkGeom::Line(a, b)); } assert_eq!(geometry.len(), 4, "historical curved strip has a complete four-edge rectangular projection"); let mut sketch = Sketch::new(); @@ -422,7 +422,7 @@ fn saved_curved_strip_projection_is_lost_after_exact_wall_coalescence() { sketch.links[0].face_name = Some(former); let original = sketch.clone(); let sk = doc - .add_feature(FeatureKind::Sketch { plane: PlaneRef::Custom { plane: solvecraft_geom::Plane::YZ }, sketch }, Some("Saved corner strip")) + .add_feature(FeatureKind::Sketch { plane: PlaneRef::Custom { plane: solidcraft_geom::Plane::YZ }, sketch }, Some("Saved corner strip")) .unwrap(); let boss = doc .add_feature( @@ -454,7 +454,7 @@ fn saved_curved_strip_projection_is_lost_after_exact_wall_coalescence() { assert_eq!(solved.sketch.curves, original.curves); let material = state.bodies.iter().find(|body| body.feature == boss).unwrap(); assert!( - (solvecraft_kernel::measure(&material.body).unwrap().volume - 12.).abs() < 1e-7, + (solidcraft_kernel::measure(&material.body).unwrap().volume - 12.).abs() < 1e-7, "two-mm strip cannot expand to a full thirty-mm wall" ); } @@ -463,8 +463,8 @@ fn saved_curved_strip_projection_is_lost_after_exact_wall_coalescence() { #[test] fn saved_stepped_tab_cut_piece_projection_is_lost_without_expanding_material() { - use solvecraft_kernel::{BoolOp, FacePaint, Paint}; - use solvecraft_sketch::{LinkKind, LinkSource, Sketch}; + use solidcraft_kernel::{BoolOp, FacePaint, Paint}; + use solidcraft_sketch::{LinkKind, LinkSource, Sketch}; let mut doc = Document::new("public stepped tab case"); doc.set_param("length", "40", Some("mm"), None).unwrap(); doc.set_param("wall", "2", Some("mm"), None).unwrap(); @@ -508,7 +508,7 @@ fn saved_stepped_tab_cut_piece_projection_is_lost_without_expanding_material() { .body .clone() .with_paint(Some(Paint { opacity: 1., faces: vec![FacePaint { face: 0, color: [1., 0., 0.], opacity: 1. }] })); - let old_cut = solvecraft_kernel::boolean(&old_stock, &before.body("Pocket tool").unwrap().body, BoolOp::Cut).unwrap().unwrap(); + let old_cut = solidcraft_kernel::boolean(&old_stock, &before.body("Pocket tool").unwrap().body, BoolOp::Cut).unwrap().unwrap(); let mut old_body = ModelBody::new("Case".into(), old_cut, cut); old_body.names = crate::naming::NameCell::new(Some(std::sync::Arc::new(crate::naming::NamingOrigin { feature: doc.feature(cut).unwrap().clone(), @@ -522,7 +522,7 @@ fn saved_stepped_tab_cut_piece_projection_is_lost_without_expanding_material() { let index = crate::naming::face_on_body(&old_body, &former, at).unwrap().0; let mut old_state = crate::ModelState::default(); old_state.bodies.push(old_body); - let plane = solvecraft_geom::Plane::YZ; + let plane = solidcraft_geom::Plane::YZ; let resolved = crate::project::resolve_face_indices(&old_state, &plane, LinkKind::Project, "Case", &[index]).unwrap(); let current_state = model.state(); let current = current_state.body("Case").unwrap(); @@ -569,15 +569,15 @@ fn saved_stepped_tab_cut_piece_projection_is_lost_without_expanding_material() { assert_eq!(saved.sketch.curves, original.curves); let stock = state.bodies.iter().find(|b| b.feature == material).unwrap(); assert!( - (solvecraft_kernel::measure(&stock.body).unwrap().volume - 200.).abs() < 1e-7, + (solidcraft_kernel::measure(&stock.body).unwrap().volume - 200.).abs() < 1e-7, "saved10x20 patch must not become30x20 wall; actualvolume={}, bounds={:?}, originalpoints={:?}", - solvecraft_kernel::measure(&stock.body).unwrap().volume, + solidcraft_kernel::measure(&stock.body).unwrap().volume, stock.mesh().bounds(), original.points ); let case = state.body("Case").unwrap(); let expected = length * 30. * 20. + 8. * 10. * 8. - (length - 2. * wall) * (30. - 2. * wall) * (20. - wall); - assert!((solvecraft_kernel::measure(&case.body).unwrap().volume - expected).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&case.body).unwrap().volume - expected).abs() < 1e-6); } } } diff --git a/crates/doc/src/config_styles.rs b/crates/doc/src/config_styles.rs index 0bdf010d..63d921fe 100644 --- a/crates/doc/src/config_styles.rs +++ b/crates/doc/src/config_styles.rs @@ -3,9 +3,9 @@ use super::Column; use crate::{DocError, Document, FeatureKind, ModelState, Result}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; -pub(crate) fn shell_plane_family(planes: impl IntoIterator>) -> bool { +pub(crate) fn shell_plane_family(planes: impl IntoIterator>) -> bool { let mut planes = planes.into_iter(); let Some(Some(first)) = planes.next() else { return false }; first.origin.is_finite() && first.x.is_finite() && first.y.is_finite() && planes.all(|plane| plane == Some(first)) @@ -112,7 +112,7 @@ impl Document { (FaceCertificate::ExtrudeCap { sketch, .. }, FeatureKind::Extrude { sketch: current, extent, operation: crate::Operation::NewBody, .. }) => sketch == current && extent.taper.is_none(), (FaceCertificate::ExtrudeSide { sketch, curve }, FeatureKind::Extrude { sketch: current, extent, operation: crate::Operation::NewBody, .. }) => { sketch == current && extent.taper.is_none() - && self.feature(*sketch).is_some_and(|feature| matches!(&feature.kind, FeatureKind::Sketch { sketch, .. } if sketch.curves.iter().any(|candidate| candidate.id == *curve && !candidate.construction && matches!(candidate.kind, solvecraft_sketch::CurveKind::Line { .. } | solvecraft_sketch::CurveKind::Circle { .. } | solvecraft_sketch::CurveKind::Arc { .. })))) + && self.feature(*sketch).is_some_and(|feature| matches!(&feature.kind, FeatureKind::Sketch { sketch, .. } if sketch.curves.iter().any(|candidate| candidate.id == *curve && !candidate.construction && matches!(candidate.kind, solidcraft_sketch::CurveKind::Line { .. } | solidcraft_sketch::CurveKind::Circle { .. } | solidcraft_sketch::CurveKind::Arc { .. })))) } (FaceCertificate::Imported { .. }, FeatureKind::Import { .. }) => true, _ => false, @@ -138,7 +138,7 @@ impl Document { } (super::FaceCertificate::ExtrudeSide { sketch, curve }, FeatureKind::Extrude { sketch: producer_sketch, extent, operation: crate::Operation::NewBody, .. }) => { sketch == producer_sketch && extent.taper.is_none() && role == format!("side:{curve}") - && self.feature(*sketch).is_some_and(|feature| matches!(&feature.kind, FeatureKind::Sketch { sketch, .. } if sketch.curves.iter().any(|candidate| candidate.id == *curve && !candidate.construction && matches!(candidate.kind, solvecraft_sketch::CurveKind::Line { .. } | solvecraft_sketch::CurveKind::Circle { .. } | solvecraft_sketch::CurveKind::Arc { .. })))) + && self.feature(*sketch).is_some_and(|feature| matches!(&feature.kind, FeatureKind::Sketch { sketch, .. } if sketch.curves.iter().any(|candidate| candidate.id == *curve && !candidate.construction && matches!(candidate.kind, solidcraft_sketch::CurveKind::Line { .. } | solidcraft_sketch::CurveKind::Circle { .. } | solidcraft_sketch::CurveKind::Arc { .. })))) } (super::FaceCertificate::Imported { .. }, FeatureKind::Import { .. }) => role.strip_prefix("face:").is_some_and(|index| index.parse::().is_ok()), (super::FaceCertificate::ShellPlanar { source_producer, source, inner }, FeatureKind::Shell { .. }) => { @@ -183,7 +183,7 @@ impl Document { } if let Some(super::FaceCertificate::ShellPlanar { source_producer, source, inner }) = &target.certificate { let indices: Vec<_> = body.body.face_sources().into_iter().flatten().enumerate().filter_map(|(index, current)| { - matches!(current, Some(solvecraft_kernel::FaceSource::ShellPlanar { producer: Some(producer), source: current_source, inner: current_inner, .. }) if *producer == target.origin_feature && inner == current_inner && super::flat_source_matches(source, current_source, *source_producer)).then_some(index) + matches!(current, Some(solidcraft_kernel::FaceSource::ShellPlanar { producer: Some(producer), source: current_source, inner: current_inner, .. }) if *producer == target.origin_feature && inner == current_inner && super::flat_source_matches(source, current_source, *source_producer)).then_some(index) }).collect(); // A shell can split one original support into several pieces. All pieces // must retain the same canonical frame, not merely lie on a common plane. @@ -203,11 +203,11 @@ impl Document { let matched = match (&target.certificate, source) { ( Some(super::FaceCertificate::Box { axis, high }), - Some(solvecraft_kernel::FaceSource::Box { producer: Some(producer), axis: current_axis, high: current_high, .. }), + Some(solidcraft_kernel::FaceSource::Box { producer: Some(producer), axis: current_axis, high: current_high, .. }), ) => *producer == target.origin_feature && axis == current_axis && high == current_high, ( Some(super::FaceCertificate::ExtrudeCap { sketch, high, outer, holes }), - Some(solvecraft_kernel::FaceSource::CertifiedExtrudeCap { + Some(solidcraft_kernel::FaceSource::CertifiedExtrudeCap { producer, sketch: current_sketch, high: current_high, @@ -234,7 +234,7 @@ impl Document { } if let Some(super::FaceCertificate::ExtrudeSide { sketch, curve }) = &target.certificate { let indices: Vec<_> = body.body.face_sources().into_iter().flatten().enumerate().filter_map(|(index, source)| { - matches!(source, Some(solvecraft_kernel::FaceSource::CertifiedExtrudeSide { producer, sketch: current_sketch, curve: current_curve, .. }) if *producer == target.origin_feature && current_sketch == sketch && current_curve == curve).then_some(index) + matches!(source, Some(solidcraft_kernel::FaceSource::CertifiedExtrudeSide { producer, sketch: current_sketch, curve: current_curve, .. }) if *producer == target.origin_feature && current_sketch == sketch && current_curve == curve).then_some(index) }).collect(); if indices.is_empty() { return Err(DocError::Unknown("constructed side certificate no longer exists".into())); @@ -243,7 +243,7 @@ impl Document { } if let Some(super::FaceCertificate::Imported { payload_sha256, item, face }) = &target.certificate { let indices: Vec<_> = body.body.face_sources().into_iter().flatten().enumerate().filter_map(|(index, source)| { - matches!(source, Some(solvecraft_kernel::FaceSource::Imported { item: current_item, face: current_face, payload_sha256: Some(digest), producer: Some(producer) }) if current_item == item && current_face == face && digest == payload_sha256 && *producer == target.origin_feature).then_some(index) + matches!(source, Some(solidcraft_kernel::FaceSource::Imported { item: current_item, face: current_face, payload_sha256: Some(digest), producer: Some(producer) }) if current_item == item && current_face == face && digest == payload_sha256 && *producer == target.origin_feature).then_some(index) }).collect(); if indices.is_empty() { return Err(DocError::Unknown("immutable import face certificate no longer exists".into())); diff --git a/crates/doc/src/construct.rs b/crates/doc/src/construct.rs index 9f040d05..916e1d1d 100644 --- a/crates/doc/src/construct.rs +++ b/crates/doc/src/construct.rs @@ -8,8 +8,8 @@ use std::collections::BTreeMap; use serde::{Deserialize, Serialize}; -use solvecraft_geom::{Plane, Vec2, Vec3}; -use solvecraft_kernel::{self as kernel, FaceSurface}; +use solidcraft_geom::{Plane, Vec2, Vec3}; +use solidcraft_kernel::{self as kernel, FaceSurface}; use crate::eval::{ModelBody, ModelState}; use crate::expr::{Kind, Value}; diff --git a/crates/doc/src/document.rs b/crates/doc/src/document.rs index dae2e0f9..c3ac3716 100644 --- a/crates/doc/src/document.rs +++ b/crates/doc/src/document.rs @@ -1,6 +1,6 @@ use serde::{Deserialize, Serialize}; -use solvecraft_geom::{Plane, Vec2, Vec3}; -use solvecraft_sketch::Sketch; +use solidcraft_geom::{Plane, Vec2, Vec3}; +use solidcraft_sketch::Sketch; use crate::construct::{AxisDef, GeoRef, PointDef}; use crate::expr::{self, Kind}; @@ -884,7 +884,7 @@ pub enum FeatureKind { step: String, /// The file's product/assembly tree, kept for components. #[serde(default, skip_serializing_if = "Vec::is_empty")] - components: Vec, + components: Vec, }, /// Rebuild an upstream constant-section mesh with analytic planar/cylindrical faces. MeshConvert { @@ -1697,7 +1697,7 @@ pub struct Document { pub struct NamedView { #[serde(default)] pub name: String, - pub target: solvecraft_geom::Vec3, + pub target: solidcraft_geom::Vec3, pub yaw: f64, pub pitch: f64, pub distance: f64, @@ -1774,13 +1774,13 @@ impl Document { let mut st = state.clone(); for b in &mut st.bodies { let c = self.body_color(b); - let faces: Vec = self + let faces: Vec = self .face_colors(b) .into_iter() - .map(|(face, l)| solvecraft_kernel::FacePaint { face, color: l.rgb(), opacity: l.opacity as f32 }) + .map(|(face, l)| solidcraft_kernel::FacePaint { face, color: l.rgb(), opacity: l.opacity as f32 }) .collect(); let opacity = self.model_body_opacity(b) as f32; - let paint = solvecraft_kernel::Paint { opacity, faces }; + let paint = solidcraft_kernel::Paint { opacity, faces }; if c != b.body.color() || b.body.paint() != Some(&paint) { b.body = b.body.clone().with_color(c).with_paint(Some(paint)); } @@ -2214,14 +2214,14 @@ impl Document { &self, vals: &std::collections::BTreeMap, sk: &mut Sketch, - stored: &solvecraft_sketch::DrivingValues, - ) -> Result { + stored: &solidcraft_sketch::DrivingValues, + ) -> Result { self.apply_dimension_values(vals, sk)?; - let mut report = solvecraft_sketch::solve_after_value_edit(sk, stored); + let mut report = solidcraft_sketch::solve_after_value_edit(sk, stored); if !sk.offsets.is_empty() || !sk.freeform_offsets.is_empty() { // Source dimensions may have moved curves during the solve. sk.refresh_offsets()?; - report = if report.ok() { solvecraft_sketch::solve(sk) } else { solvecraft_sketch::solve_after_value_edit(sk, stored) }; + report = if report.ok() { solidcraft_sketch::solve(sk) } else { solidcraft_sketch::solve_after_value_edit(sk, stored) }; } Ok(report) } diff --git a/crates/doc/src/eval.rs b/crates/doc/src/eval.rs index 31cc4e5f..50f19717 100644 --- a/crates/doc/src/eval.rs +++ b/crates/doc/src/eval.rs @@ -5,9 +5,9 @@ use std::hash::{Hash, Hasher}; use std::sync::{Arc, OnceLock}; use serde::Serialize; -use solvecraft_geom::{Mesh, Plane, Region2, Vec2, Vec3}; -use solvecraft_kernel::{self as kernel, Body, BoolOp}; -use solvecraft_sketch::{CurveKind, Profile, SolveReport, find_profiles}; +use solidcraft_geom::{Mesh, Plane, Region2, Vec2, Vec3}; +use solidcraft_kernel::{self as kernel, Body, BoolOp}; +use solidcraft_sketch::{CurveKind, Profile, SolveReport, find_profiles}; use crate::expr::{self, Kind, Value}; use crate::lineage::Traced; @@ -79,7 +79,7 @@ pub struct SolvedSketch { pub feature: u64, pub name: String, pub plane: Plane, - pub sketch: solvecraft_sketch::Sketch, + pub sketch: solidcraft_sketch::Sketch, pub report: SolveReport, pub profiles: Vec, } @@ -217,7 +217,7 @@ fn thread_on(st: &ModelState, feature: u64, p: Vec3, designation: Option<&str>, /// profile, 60° flanks): cut from the shaft (external) or the hole wall (internal), running from /// `start` to `end` along the axis. `radius` is the face's own radius; `open` says which ends /// of the threaded length are free ends of the face. -fn thread_groove(t: &ThreadInfo, radius: f64, open: (bool, bool)) -> Result { +fn thread_groove(t: &ThreadInfo, radius: f64, open: (bool, bool)) -> Result { let (p, d) = (t.pitch, t.major_diameter); let h = p * 3f64.sqrt() / 2.0; let slope = (std::f64::consts::PI / 6.0).tan(); @@ -538,7 +538,7 @@ const QUANTUM: f64 = 1e-6; /// Put what a solve writes into a sketch on the [`QUANTUM`] grid: positions, radii and the /// measured values of driven dimensions. -fn quantize(sk: &mut solvecraft_sketch::Sketch) { +fn quantize(sk: &mut solidcraft_sketch::Sketch) { let q = |v: f64| (v / QUANTUM).round() * QUANTUM; for p in &mut sk.points { p.pos = Vec2::new(q(p.pos.x), q(p.pos.y)); @@ -572,7 +572,7 @@ fn feature_json(f: &Feature) -> String { /// constraints' values. Nothing else: what evaluation derives for its own copy (an offset's /// upgraded topology, refreshed links) stays out of the stored sketch. A `solved` that does not /// match `stored` entity for entity is not adopted. -fn adopt_solution(stored: &mut solvecraft_sketch::Sketch, solved: &solvecraft_sketch::Sketch) { +fn adopt_solution(stored: &mut solidcraft_sketch::Sketch, solved: &solidcraft_sketch::Sketch) { if stored.points.len() != solved.points.len() || stored.curves.len() != solved.curves.len() || stored.constraints.len() != solved.constraints.len() @@ -771,7 +771,7 @@ impl Model { /// recomputed; the next [`Model::evaluate`] recomputes from the sketch on. Returns `false`, /// changing nothing, when the sketch has no evaluated result or a feature before it is /// already stale (only a full evaluation brings that up to date). - pub fn show_live_sketch(&mut self, feature: u64, sketch: solvecraft_sketch::Sketch, report: SolveReport) -> bool { + pub fn show_live_sketch(&mut self, feature: u64, sketch: solidcraft_sketch::Sketch, report: SolveReport) -> bool { let Some(i) = self.results.iter().position(|r| r.id == feature) else { return false }; if self.results.iter().take(i).any(|r| r.stale) { return false; @@ -1042,7 +1042,7 @@ pub fn select_profiles(ss: &SolvedSketch, sel: &ProfileSel) -> Result { - let drawn = solvecraft_sketch::find_drawn_profiles(&ss.sketch); + let drawn = solidcraft_sketch::find_drawn_profiles(&ss.sketch); if drawn.is_empty() { ps.iter().map(|p| p.region.clone()).collect() } else { drawn.into_iter().map(|p| p.region).collect() } } ProfileSel::Indices { indices } => { @@ -1066,7 +1066,7 @@ pub fn select_profiles(ss: &SolvedSketch, sel: &ProfileSel) -> Result Result DocError { +pub fn curve_pick_error(e: solidcraft_sketch::CurvePick, ids: &[String], sketch: &str) -> DocError { match e { - solvecraft_sketch::CurvePick::Missing => DocError::Invalid(format!( + solidcraft_sketch::CurvePick::Missing => DocError::Invalid(format!( "no profile of {sketch} is bounded by exactly the curves {ids:?} (a curve was deleted, or another curve now cuts the region)" )), - solvecraft_sketch::CurvePick::Ambiguous(n) => DocError::Invalid(format!( + solidcraft_sketch::CurvePick::Ambiguous(n) => DocError::Invalid(format!( "{n} profiles of {sketch} are bounded by exactly the curves {ids:?}; add a curve that tells them apart, or pick by index or point" )), } @@ -1389,7 +1389,7 @@ fn grow_profile_for_coplanar(st: &ModelState, r: &Region2, op: Operation, target let delta = (size * 0.005).max(1e-3); let lp = r.outer.ccw(); let lines: Option> = - lp.segs.iter().map(|s| if let solvecraft_geom::Seg2::Line { a, b } = *s { Some((a, b)) } else { None }).collect(); + lp.segs.iter().map(|s| if let solidcraft_geom::Seg2::Line { a, b } = *s { Some((a, b)) } else { None }).collect(); let Some(lines) = lines else { return r.clone() }; let inside_any = |p: Vec3| meshes.iter().any(|m| m.contains(p)); let mut grow = vec![0.0; lines.len()]; @@ -1436,7 +1436,7 @@ fn grow_profile_for_coplanar(st: &ModelState, r: &Region2, op: Operation, target let t = (p1 - p0).cross(d1) / den; pts.push(p0 + d0 * t); } - Region2 { outer: solvecraft_geom::Loop2::polygon(&pts), holes: r.holes.clone() } + Region2 { outer: solidcraft_geom::Loop2::polygon(&pts), holes: r.holes.clone() } } /// Booleans struggle with coincident faces (a cut starting on the face it cuts, a boss @@ -1771,7 +1771,7 @@ fn hole_tools( let seam = std::cell::Cell::new(0.0); let disc = |rad: f64| { seam.set(seam.get() + 0.613); - Region2 { outer: solvecraft_geom::Loop2::circle_from(Vec2::ZERO, rad, seam.get()), holes: vec![] } + Region2 { outer: solidcraft_geom::Loop2::circle_from(Vec2::ZERO, rad, seam.get()), holes: vec![] } }; let cyl = |rad: f64, lo: f64, hi: f64| -> Result { kernel::extrude(&plane, &[disc(rad)], lo, hi)?.pop().ok_or_else(|| DocError::Invalid("hole tool".into())) @@ -1794,7 +1794,7 @@ fn hole_tools( let side = up.any_perp(); let rp = Plane::new(*position, side, up).ok_or_else(|| DocError::Invalid("hole axis".into()))?; let pts = [Vec2::new(0.0, top), Vec2::new(r, top), Vec2::new(r, bottom), Vec2::new(0.0, bottom - tip)]; - let region = Region2 { outer: solvecraft_geom::Loop2::polygon(&pts), holes: vec![] }; + let region = Region2 { outer: solidcraft_geom::Loop2::polygon(&pts), holes: vec![] }; tools.extend(kernel::revolve(&rp, &[region], Vec2::ZERO, Vec2::Y, std::f64::consts::TAU)?); } else { tools.push(cyl(r, bottom, top)?); @@ -1987,7 +1987,7 @@ fn feature_tools_with_constructor( match &f.kind { FeatureKind::Extrude { sketch, profiles, extent, operation, targets } => { let ss = st.sketch(*sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?.clone(); - let regions = solvecraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); // From / To Object: where the face or point lies along the normal, measured over the // middle of the profiles. let n = ss.plane.normal(); @@ -2130,7 +2130,7 @@ fn feature_tools_with_constructor( } FeatureKind::Revolve { sketch, profiles, axis, angle, angle2, symmetric, convention, to, .. } => { let ss = st.sketch(*sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch} (it must come earlier in the timeline)")))?.clone(); - let regions = solvecraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); let (o, d) = revolve_axis(&ss, axis)?; let angles = if to.is_some() { (0.0, None) @@ -2257,7 +2257,7 @@ fn feature_tools_with_constructor( let [a, b] = §ions[..] else { return Err(DocError::Invalid("not supported yet: a point section in a loft of more than two".into())) }; let (prof, pt) = if a.point.is_some() { (b, a) } else { (a, b) }; let ss = st.sketch(prof.sketch).ok_or_else(|| DocError::Unknown(format!("sketch {}", prof.sketch)))?; - let regions = solvecraft_sketch::merge_regions(&select_profiles(ss, &prof.profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(ss, &prof.profiles)?); let [r] = ®ions[..] else { return Err(DocError::Invalid("each loft section must be one profile".into())) }; let ps = st.sketch(pt.sketch).ok_or_else(|| DocError::Unknown(format!("sketch {}", pt.sketch)))?; let id = pt.point.as_deref().unwrap_or_default(); @@ -2292,10 +2292,10 @@ fn feature_tools_with_constructor( let plane = Plane { origin: *base, x, y: k }; let c = Vec2::new(r, 0.0); let outer = match section { - crate::CoilSection::Circular => solvecraft_geom::Loop2::circle(c, s / 2.0), + crate::CoilSection::Circular => solidcraft_geom::Loop2::circle(c, s / 2.0), crate::CoilSection::Square => { let h = s / 2.0; - solvecraft_geom::Loop2::polygon(&[c + Vec2::new(-h, -h), c + Vec2::new(h, -h), c + Vec2::new(h, h), c + Vec2::new(-h, h)]) + solidcraft_geom::Loop2::polygon(&[c + Vec2::new(-h, -h), c + Vec2::new(h, -h), c + Vec2::new(h, h), c + Vec2::new(-h, h)]) } }; let region = Region2 { outer, holes: Vec::new() }; @@ -2310,7 +2310,7 @@ fn feature_tools_with_constructor( let ss = st .sketch(sec.sketch) .ok_or_else(|| DocError::Unknown(format!("sketch {} (it must come earlier in the timeline)", sec.sketch)))?; - let regions = solvecraft_sketch::merge_regions(&select_profiles(ss, &sec.profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(ss, &sec.profiles)?); let [r] = ®ions[..] else { return Err(DocError::Invalid("each loft section must be one profile".into())) }; secs.push((ss.plane, r.outer.clone())); } @@ -2332,7 +2332,7 @@ fn feature_tools_with_constructor( }; let mapped = path_in_frame(doc, ps, f.component)?; let ps = mapped.as_ref(); - let regions = solvecraft_sketch::merge_regions(&select_profiles(ss, profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(ss, profiles)?); let start = regions.first().map(|r| ss.plane.to_world(r.centroid())).unwrap_or_default(); let tool = |r: &Region2, body: Body| crate::lineage::ProfileRoles::swept(Some(ss), Some(r)).tool(body, None); // A path through 3D sketch curves follows them as points. @@ -2407,14 +2407,14 @@ fn pipe_region(vals: &BTreeMap, r: f64, wall: &Option) -> if !(wt > 0.0 && wt < r) { return Err(DocError::Invalid("the wall must be thinner than the radius".into())); } - vec![solvecraft_geom::Loop2::circle(Vec2::ZERO, r - wt).reversed()] + vec![solidcraft_geom::Loop2::circle(Vec2::ZERO, r - wt).reversed()] } None => Vec::new(), }; - Ok(Region2 { outer: solvecraft_geom::Loop2::circle(Vec2::ZERO, r), holes }) + Ok(Region2 { outer: solidcraft_geom::Loop2::circle(Vec2::ZERO, r), holes }) } -fn map_wire(wire: &mut solvecraft_sketch::Wire, matrix: &Mat) { +fn map_wire(wire: &mut solidcraft_sketch::Wire, matrix: &Mat) { for point in wire.pts.iter_mut().chain(&mut wire.fit) { *point = crate::apply_point(matrix, *point); } @@ -2453,7 +2453,7 @@ fn path_in_frame<'a>(doc: &Document, sketch: &'a SolvedSketch, consumer: u64) -> ) .ok_or_else(|| DocError::Invalid("path sketch frame is invalid".into()))?; for wire in &mut mapped.sketch.wires { - let matrix = if wire.space == solvecraft_sketch::WireSpace::Component { &relative } else { &inverse }; + let matrix = if wire.space == solidcraft_sketch::WireSpace::Component { &relative } else { &inverse }; map_wire(wire, matrix); } Ok(Cow::Owned(mapped)) @@ -2530,7 +2530,7 @@ fn path_segments(ps: &SolvedSketch, ids: &[String], start: Vec3) -> Result = Vec::new(); + let mut segs: Vec = Vec::new(); for id in ids { let ci = ps.sketch.curve_index(id).ok_or_else(|| DocError::Unknown(format!("path curve `{id}`")))?; segs.extend(ps.sketch.segs(ci)); @@ -2564,12 +2564,12 @@ fn path_segments(ps: &SolvedSketch, ids: &[String], start: Vec3) -> Result vec![kernel::PathSeg::Line { a: w(a), b: w(b) }], - solvecraft_geom::Seg2::Arc { center, sweep, .. } => { + solidcraft_geom::Seg2::Line { a, b } => vec![kernel::PathSeg::Line { a: w(a), b: w(b) }], + solidcraft_geom::Seg2::Arc { center, sweep, .. } => { vec![kernel::PathSeg::Arc { a: w(s.start()), center: w(center), axis: n * sweep.signum(), angle: sweep.abs() }] } // Free-form path curves are followed as polylines. - solvecraft_geom::Seg2::Cubic { .. } | solvecraft_geom::Seg2::Conic { .. } | solvecraft_geom::Seg2::Bezier { .. } => { + solidcraft_geom::Seg2::Cubic { .. } | solidcraft_geom::Seg2::Conic { .. } | solidcraft_geom::Seg2::Bezier { .. } => { s.polyline(1e-2).windows(2).map(|q| kernel::PathSeg::Line { a: w(q[0]), b: w(q[1]) }).collect() } }) @@ -3388,7 +3388,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap, f: &Feature, st: (None, Some(sid)) => { let ss = st.sketch(*sid).ok_or_else(|| DocError::Unknown(format!("sketch {sid}")))?; // Each curve's exact segments (lines and arcs split by their own plane and cylinder). - let curves: Vec> = (0..ss.sketch.curves.len()) + let curves: Vec> = (0..ss.sketch.curves.len()) .filter(|ci| ss.sketch.curves.get(*ci).is_some_and(|c| !c.construction)) .map(|ci| ss.sketch.segs(ci)) .filter(|segs| !segs.is_empty()) @@ -3681,7 +3681,7 @@ fn eval_feature(doc: &Document, vals: &BTreeMap, f: &Feature, st: } FeatureKind::BoundingSolid { bodies, margin } => { let m = val(vals, margin, Kind::Length)?; - let mut bb = solvecraft_geom::Aabb3::EMPTY; + let mut bb = solidcraft_geom::Aabb3::EMPTY; for b in st.bodies.iter().filter(|b| bodies.is_empty() || bodies.contains(&b.name)) { bb = bb.union(&b.mesh().bounds()); } @@ -3812,7 +3812,7 @@ pub fn state_in_frame_at<'a>(doc: &Document, st: &'a ModelState, comp: u64, fram .collect(); let sketch_m: Vec = st.sketches.iter().map(|s| rel(comp_of_feature(s.feature), None)).collect(); let world_wires_need_mapping = !crate::is_identity(&inv) - && st.sketches.iter().any(|sketch| sketch.sketch.wires.iter().any(|wire| wire.space == solvecraft_sketch::WireSpace::World)); + && st.sketches.iter().any(|sketch| sketch.sketch.wires.iter().any(|wire| wire.space == solidcraft_sketch::WireSpace::World)); if body_m.iter().chain(&sketch_m).all(crate::is_identity) && !world_wires_need_mapping { return Cow::Borrowed(st); } @@ -3835,7 +3835,7 @@ pub fn state_in_frame_at<'a>(doc: &Document, st: &'a ModelState, comp: u64, fram } for (s, m) in out.sketches.iter_mut().zip(&sketch_m) { // Only a sketch the frame moves is copied. - let maps_wire = |w: &solvecraft_sketch::Wire| !crate::is_identity(if w.space == solvecraft_sketch::WireSpace::Component { m } else { &inv }); + let maps_wire = |w: &solidcraft_sketch::Wire| !crate::is_identity(if w.space == solidcraft_sketch::WireSpace::Component { m } else { &inv }); if crate::is_identity(m) && !s.sketch.wires.iter().any(maps_wire) { continue; } @@ -3846,7 +3846,7 @@ pub fn state_in_frame_at<'a>(doc: &Document, st: &'a ModelState, comp: u64, fram s.plane = np; } for wire in &mut s.sketch.wires { - let matrix = if wire.space == solvecraft_sketch::WireSpace::Component { m } else { &inv }; + let matrix = if wire.space == solidcraft_sketch::WireSpace::Component { m } else { &inv }; map_wire(wire, matrix); } } @@ -3919,7 +3919,7 @@ pub fn world_state(doc: &Document, st: &ModelState) -> ModelState { } } for wire in &mut s2.sketch.wires { - if wire.space == solvecraft_sketch::WireSpace::Component { + if wire.space == solidcraft_sketch::WireSpace::Component { map_wire(wire, &m); } } diff --git a/crates/doc/src/eval_more.rs b/crates/doc/src/eval_more.rs index cb51deab..d0ab8e89 100644 --- a/crates/doc/src/eval_more.rs +++ b/crates/doc/src/eval_more.rs @@ -5,7 +5,7 @@ use super::*; use crate::EmbossFace; use crate::apply_point; use crate::sheet; -use solvecraft_geom::{Loop2, Seg2}; +use solidcraft_geom::{Loop2, Seg2}; pub(super) fn eval(doc: &Document, vals: &BTreeMap, f: &Feature, st: &mut ModelState) -> Result<()> { match &f.kind { @@ -357,7 +357,7 @@ pub(super) fn sheet_eval( match &f.kind { FeatureKind::SheetBase { sketch, profiles, rule, flip } => { let ss = st.sketch(*sketch).ok_or_else(|| DocError::Unknown(format!("sketch {sketch}")))?.clone(); - let regions = solvecraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); let r = doc.sheet_rule(rule.as_deref()); let rv = doc.rule_values(vals, &r)?; for (k, region) in regions.into_iter().enumerate() { @@ -631,7 +631,7 @@ pub(super) fn sheet_cut( return Ok(Vec::new()); } let Some(ss) = st.sketch(*sketch).cloned() else { return Ok(Vec::new()) }; - let regions = solvecraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(&ss, profiles)?); let n = ss.plane.normal(); let mut work = sheet::FlatWork::new(); let faces = tools.iter().fold(0usize, |count, body| count.saturating_add(body.face_count())); @@ -836,7 +836,7 @@ pub(super) fn sheet_cut( /// Outline loops (in `plane` coordinates) of the mesh triangles lying in the plane at distance /// `level` from `at` along `n`: the edges used by one of them only, chained. -fn face_loops(mesh: &solvecraft_geom::Mesh, plane: &Plane, at: Vec3, n: Vec3, level: f64, tol: f64) -> Vec> { +fn face_loops(mesh: &solidcraft_geom::Mesh, plane: &Plane, at: Vec3, n: Vec3, level: f64, tol: f64) -> Vec> { let on: Vec<[u32; 3]> = mesh .triangles .iter() diff --git a/crates/doc/src/eval_surface.rs b/crates/doc/src/eval_surface.rs index 1010c9e6..55d5a57a 100644 --- a/crates/doc/src/eval_surface.rs +++ b/crates/doc/src/eval_surface.rs @@ -8,7 +8,7 @@ pub(super) fn eval(doc: &Document, vals: &BTreeMap, f: &Feature, let mut made = Vec::new(); if let Some(sk) = sketch { let ss = st.sketch(*sk).ok_or_else(|| DocError::Unknown(format!("sketch {sk} (it must come earlier in the timeline)")))?; - let regions = solvecraft_sketch::merge_regions(&select_profiles(ss, profiles)?); + let regions = solidcraft_sketch::merge_regions(&select_profiles(ss, profiles)?); for r in ®ions { made.push(kernel::patch_region(&ss.plane, r)?); } diff --git a/crates/doc/src/face_anchor.rs b/crates/doc/src/face_anchor.rs index 6dde99d2..39961fd6 100644 --- a/crates/doc/src/face_anchor.rs +++ b/crates/doc/src/face_anchor.rs @@ -3,7 +3,7 @@ use crate::config::ConfigFaceTarget; use crate::{DocError, Document, EmbossFace, FeatureKind, Model, ModelBody, ModelState, PlaneRef, Result}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use solvecraft_geom::Plane; +use solidcraft_geom::Plane; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] #[serde(deny_unknown_fields)] @@ -311,7 +311,7 @@ pub fn validate_face_anchors_json(value: &Value) -> Result<()> { #[cfg(test)] mod tests { use super::*; - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; fn fixture() -> (Document, ModelBody, usize, Plane) { let mut doc = Document::default(); let kind: FeatureKind = @@ -319,7 +319,7 @@ mod tests { let id = doc.add_feature(kind, Some("Base")).unwrap(); let body = ModelBody::new( "Base".into(), - solvecraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 10.)).unwrap().with_box_certificate(id).unwrap(), + solidcraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 10.)).unwrap().with_box_certificate(id).unwrap(), id, ); let index = body @@ -327,7 +327,7 @@ mod tests { .face_sources() .unwrap() .iter() - .position(|source| matches!(source, Some(solvecraft_kernel::FaceSource::Box { axis: 2, high: true, .. }))) + .position(|source| matches!(source, Some(solidcraft_kernel::FaceSource::Box { axis: 2, high: true, .. }))) .unwrap(); let canonical = body.body.face_plane(index).unwrap(); let picked = Plane::new(canonical.origin + canonical.x * 2. + canonical.y * 3., canonical.y, -canonical.x).unwrap(); @@ -350,7 +350,7 @@ mod tests { assert!((initial.x - picked.x).len() < 1e-9); state.bodies[0] = ModelBody::new( "Base".into(), - solvecraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 20.)) + solidcraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 20.)) .unwrap() .with_box_certificate(anchor.target.origin_feature) .unwrap(), @@ -361,7 +361,7 @@ mod tests { assert!((changed.origin.x - initial.origin.x).abs() < 1e-9); state.bodies[0] = ModelBody::new( "Control".into(), - solvecraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 30.)) + solidcraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 30.)) .unwrap() .with_box_certificate(anchor.target.origin_feature) .unwrap(), @@ -369,7 +369,7 @@ mod tests { ); assert!(doc.resolve_face_anchor(&anchor, 0, &state).is_err()); state.bodies[0].name = "Base".into(); - state.bodies[0].body = solvecraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 30.)) + state.bodies[0].body = solidcraft_kernel::box_solid(Vec3::default(), Vec3::new(10., 10., 30.)) .unwrap() .with_box_certificate(anchor.target.origin_feature + 1) .unwrap(); @@ -400,8 +400,8 @@ mod tests { } #[test] fn extrude_cap_profile_reordering_never_retargets_an_ordinal_body() { - use solvecraft_geom::Vec2; - let mut sketch = solvecraft_sketch::Sketch::new(); + use solidcraft_geom::Vec2; + let mut sketch = solidcraft_sketch::Sketch::new(); for offset in [0., 30.] { let points: Vec<_> = [(0., 0.), (10., 0.), (10., 10.), (0., 10.)] .iter() @@ -432,7 +432,7 @@ mod tests { assert_eq!(state.bodies.len(), 2); let solved = state.sketch(source).unwrap(); let regions = crate::select_profiles(solved, &crate::ProfileSel::All).unwrap(); - let raw = solvecraft_kernel::extrude(&solved.plane, ®ions, 0., 10.).unwrap(); + let raw = solidcraft_kernel::extrude(&solved.plane, ®ions, 0., 10.).unwrap(); assert!( crate::config::extrude_cap_bindings(solved, &raw, 0).is_none(), "exhausted certification work must leave the complete producer uncertified" @@ -449,7 +449,7 @@ mod tests { .face_sources() .unwrap() .iter() - .position(|source| matches!(source, Some(solvecraft_kernel::FaceSource::CertifiedExtrudeCap { high: true, .. }))) + .position(|source| matches!(source, Some(solidcraft_kernel::FaceSource::CertifiedExtrudeCap { high: true, .. }))) .unwrap(); let picked = body.body.face_plane(index).unwrap(); let anchor = doc.capture_face_anchor(body, index, &picked, &[], 0, &[]).unwrap(); @@ -476,7 +476,7 @@ mod tests { base: Box::new(PlaneRef::Face { plane: picked, at: picked.origin, name: None, anchor: None }), distance: "2".into(), }, - sketch: solvecraft_sketch::Sketch::new(), + sketch: solidcraft_sketch::Sketch::new(), }, None, ) @@ -492,7 +492,7 @@ mod tests { } #[test] fn warm_root_body_offset_edit_matches_cold_attached_sketch_and_downstream_bounds() { - use solvecraft_geom::Vec2; + use solidcraft_geom::Vec2; let (mut doc, _, _, _) = fixture(); let mut warm = Model::new(); warm.evaluate(&doc); @@ -503,12 +503,12 @@ mod tests { .face_sources() .unwrap() .iter() - .position(|source| matches!(source, Some(solvecraft_kernel::FaceSource::Box { axis: 2, high: true, .. }))) + .position(|source| matches!(source, Some(solidcraft_kernel::FaceSource::Box { axis: 2, high: true, .. }))) .unwrap(); let picked = body.body.face_plane(index).unwrap(); let anchor = doc.capture_face_anchor(body, index, &picked, &[], 0, &[]).unwrap(); let source_name = body.name.clone(); - let mut sketch = solvecraft_sketch::Sketch::new(); + let mut sketch = solidcraft_sketch::Sketch::new(); let points: Vec<_> = [(2., 2.), (4., 2.), (4., 4.), (2., 4.)].iter().map(|(x, y)| sketch.add_point(Vec2::new(*x, *y), None).unwrap()).collect(); for edge in 0..4 { diff --git a/crates/doc/src/format.rs b/crates/doc/src/format.rs index 24118b0f..0816837f 100644 --- a/crates/doc/src/format.rs +++ b/crates/doc/src/format.rs @@ -1,6 +1,6 @@ //! The design file format version and the migrations that bring older files up to date. //! -//! A design file starts with `"format": "solvecraft/N"`. A file of an older format is read and +//! A design file starts with `"format": "solidcraft/N"`. A file of an older format is read and //! upgraded step by step (N → N+1 → … → current): JSON-level steps first, for changes in the //! file's shape, then document-level steps. A file of a newer format than this build knows is //! refused with a message saying so, never guessed at. @@ -55,12 +55,16 @@ use crate::{DocError, Document, Result}; pub const FORMAT: u32 = 14; pub fn format_string(n: u32) -> String { - format!("solvecraft/{n}") + format!("solidcraft/{n}") } -/// The version in a format string (`solvecraft/N`). +/// The format tag of designs saved before the rename. Compatibility shim (docs/compatibility.md): +/// read, never written. +pub const LEGACY_FORMAT_PREFIX: &str = "solvecraft/"; + +/// The version in a format string (`solidcraft/N`, or the legacy `solvecraft/N`). pub fn version_of(s: &str) -> Option { - s.strip_prefix("solvecraft/")?.parse().ok() + s.strip_prefix("solidcraft/").or_else(|| s.strip_prefix(LEGACY_FORMAT_PREFIX))?.parse().ok() } /// JSON-level upgrades: entry `i` turns format `i + 1` into `i + 2`. @@ -300,7 +304,7 @@ pub fn read(s: &str) -> Result { if let Some(crate::Feature { kind: crate::FeatureKind::Sketch { sketch, .. }, .. }) = d.features.get_mut(feature_index) { let mut spaces = std::collections::BTreeMap::new(); for link in &sketch.links { - spaces.entry(link.id.as_str()).or_insert_with(|| solvecraft_sketch::WireSpace::from_legacy_link(link)); + spaces.entry(link.id.as_str()).or_insert_with(|| solidcraft_sketch::WireSpace::from_legacy_link(link)); } for wire_index in wire_indices { if let Some(wire) = sketch.wires.get_mut(wire_index) { @@ -358,9 +362,9 @@ fn validate_sketch_sizes(value: &Value) -> Result<()> { } let Some(sketch) = feature.get("sketch") else { continue }; let len = |key: &str| sketch.get(key).and_then(Value::as_array).map_or(0, Vec::len); - if len("points") > solvecraft_sketch::MAX_POINTS - || len("curves") > solvecraft_sketch::MAX_CURVES - || len("constraints") > solvecraft_sketch::MAX_CONSTRAINTS + if len("points") > solidcraft_sketch::MAX_POINTS + || len("curves") > solidcraft_sketch::MAX_CURVES + || len("constraints") > solidcraft_sketch::MAX_CONSTRAINTS { return Err(invalid()); } @@ -553,7 +557,7 @@ fn validate_versioned_geometry(value: &Value) -> Result<()> { } let indices = curve.get("pts").and_then(Value::as_array).ok_or_else(invalid)?; work = work.saturating_add(indices.len()); - if !(4..=solvecraft_sketch::MAX_SPLINE_POINTS).contains(&indices.len()) + if !(4..=solidcraft_sketch::MAX_SPLINE_POINTS).contains(&indices.len()) || work > 1_000_000 || curve.get("control").is_some_and(|control| control.as_bool() != Some(false)) || curve.get("degree").is_some_and(|degree| degree.as_u64() != Some(3)) @@ -630,7 +634,7 @@ fn validate_freeform_offsets(value: &Value) -> Result<()> { if !(1..=256).contains(&divisions) || !divisions.is_power_of_two() || !(1..=256).contains(&pieces) - || pieces * divisions >= solvecraft_sketch::MAX_SPLINE_POINTS as u64 + || pieces * divisions >= solidcraft_sketch::MAX_SPLINE_POINTS as u64 || !distance.is_finite() || !(1e-9..=MAX_REAL).contains(&distance.abs()) || !tolerance.is_finite() @@ -727,7 +731,7 @@ mod tests { #[test] fn version_thirteen_names_keep_their_meaning_under_the_legacy_mark() { let file = serde_json::json!({ - "format": "solvecraft/13", + "format": "solidcraft/13", "features": [ {"type": "fillet", "edge_names": ["F2:side:l2|F2:top#2~1", ""], "face_names": ["F2:top#1", ""]}, {"type": "sheet_flange", "edge_names": ["base:l1:top"]}, @@ -774,7 +778,7 @@ mod tests { #[test] fn version_thirteen_marking_targets_name_fields_not_key_names() { let file = serde_json::json!({ - "format": "solvecraft/13", + "format": "solidcraft/13", "features": [ {"type": "extrude", "options": {"face": "flat", "face_name": "label", "face_names": ["a"]}}, {"type": "fillet", "settings": {"edge_names": ["kept"]}}, @@ -794,8 +798,8 @@ mod tests { #[test] fn version_nine_wire_spaces_preserve_saved_points_and_explicit_markers() { - use solvecraft_geom::Vec3; - use solvecraft_sketch::{LinkKind, LinkSource, Sketch, WireSpace}; + use solidcraft_geom::Vec3; + use solidcraft_sketch::{LinkKind, LinkSource, Sketch, WireSpace}; let mut sketch = Sketch::new(); sketch.add_drawn_wire(&[Vec3::new(12.0, 3.0, 4.0)]).unwrap(); let link = sketch @@ -810,7 +814,7 @@ mod tests { let mut doc = Document::new("legacy wire spaces"); doc.add_feature(crate::FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch }, None).unwrap(); let mut json: Value = serde_json::from_str(&doc.to_json()).unwrap(); - json["format"] = Value::from("solvecraft/9"); + json["format"] = Value::from("solidcraft/9"); for wire in json["features"][0]["sketch"]["wires"].as_array_mut().unwrap() { wire.as_object_mut().unwrap().remove("space"); } @@ -891,7 +895,7 @@ mod tests { let mut document = Document::new("legacy named extent"); document.set_param("offset", "2", Some("mm"), None).unwrap(); let sketch = document - .add_feature(FeatureKind::Sketch { plane: PlaneRef::Origin { name: "XY".into() }, sketch: solvecraft_sketch::Sketch::new() }, None) + .add_feature(FeatureKind::Sketch { plane: PlaneRef::Origin { name: "XY".into() }, sketch: solidcraft_sketch::Sketch::new() }, None) .unwrap(); document .add_feature( @@ -905,9 +909,9 @@ mod tests { start_offset: None, through_all: false, taper: Some("0 deg".into()), - to: Some(solvecraft_geom::Vec3::new(0.0, 0.0, 10.0)), + to: Some(solidcraft_geom::Vec3::new(0.0, 0.0, 10.0)), to_offset: Some("offset + 1 mm".into()), - from: Some(solvecraft_geom::Vec3::new(0.0, 0.0, 5.0)), + from: Some(solidcraft_geom::Vec3::new(0.0, 0.0, 5.0)), }, operation: Operation::NewBody, targets: vec![], @@ -928,7 +932,7 @@ mod tests { ) .unwrap(); let mut legacy: Value = serde_json::from_str(&document.to_json()).unwrap(); - legacy["format"] = Value::from("solvecraft/8"); + legacy["format"] = Value::from("solidcraft/8"); // Version 8 exposed distance and taper, but had no model slot for To Offset. legacy["features"][1]["param_names"] = serde_json::json!(["d1", "d2"]); legacy["features"][2]["param_names"] = serde_json::json!(["d3", "d4", "d5"]); @@ -952,8 +956,8 @@ mod tests { #[test] fn saved_offset_cannot_alias_and_overwrite_its_source_geometry() { - use solvecraft_geom::Vec2; - use solvecraft_sketch::{CurveKind, Sketch}; + use solidcraft_geom::Vec2; + use solidcraft_sketch::{CurveKind, Sketch}; let mut sketch = Sketch::new(); let center = sketch.add_point(Vec2::ZERO, None).unwrap(); let major = sketch.add_point(Vec2::new(10.0, 0.0), None).unwrap(); @@ -976,7 +980,7 @@ mod tests { let mut document = Document::new("missing offset geometry"); document .add_feature( - crate::FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch: solvecraft_sketch::Sketch::new() }, + crate::FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch: solidcraft_sketch::Sketch::new() }, None, ) .unwrap(); @@ -1052,7 +1056,7 @@ mod tests { ("degree", serde_json::json!(2)), ("pts", serde_json::json!([0, 1, 2])), ("pts", serde_json::json!([0, 1, 2, u64::MAX])), - ("pts", serde_json::json!(vec![0; solvecraft_sketch::MAX_SPLINE_POINTS + 1])), + ("pts", serde_json::json!(vec![0; solidcraft_sketch::MAX_SPLINE_POINTS + 1])), ] { let mut damaged = curve.clone(); damaged[key] = malformed; @@ -1091,8 +1095,8 @@ mod tests { #[test] fn periodic_file_validation_refuses_damaged_closed_geometry() { - use solvecraft_geom::Vec2; - use solvecraft_sketch::{CurveKind, Sketch}; + use solidcraft_geom::Vec2; + use solidcraft_sketch::{CurveKind, Sketch}; let mut sketch = Sketch::new(); let mut pts = Vec::new(); for point in [Vec2::ZERO, Vec2::new(10.0, 0.0), Vec2::new(0.0, 10.0), Vec2::ZERO] { @@ -1199,7 +1203,7 @@ mod tests { ]; d.configs.active = Some("Default".into()); let mut legacy: Value = serde_json::from_str(&d.to_json()).unwrap(); - legacy["format"] = Value::from("solvecraft/6"); + legacy["format"] = Value::from("solidcraft/6"); let original_table = legacy["configs"].clone(); let upgraded = read(&legacy.to_string()).unwrap(); d.features.iter_mut().for_each(|f| f.shell_piece_order = true); @@ -1224,7 +1228,7 @@ mod tests { }]; d.apply_configuration("Blue steel").unwrap(); let mut value: Value = serde_json::from_str(&d.to_json()).unwrap(); - value["format"] = Value::from("solvecraft/7"); + value["format"] = Value::from("solidcraft/7"); let original_table = value["configs"].clone(); let roundtrip = read(&value.to_string()).unwrap(); assert_eq!(roundtrip, d); @@ -1241,8 +1245,8 @@ mod tests { #[test] fn version_seven_origin_sketch_keeps_geometry_dimension_ids_and_expressions() { - use solvecraft_geom::Vec2; - use solvecraft_sketch::{ConstraintKind, Sketch}; + use solidcraft_geom::Vec2; + use solidcraft_sketch::{ConstraintKind, Sketch}; let mut d = Document::new("legacy origin dimensions"); d.set_param("reach", "25 mm", Some("mm"), None).unwrap(); let dim = d.new_model_param("reach", "mm"); @@ -1253,7 +1257,7 @@ mod tests { sketch.add_constraint(ConstraintKind::Length { l: line, value: 25.0 }, Some(dim)).unwrap(); d.add_feature(crate::FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch }, Some("Dimensioned")).unwrap(); let mut legacy: Value = serde_json::from_str(&d.to_json()).unwrap(); - legacy["format"] = Value::from("solvecraft/7"); + legacy["format"] = Value::from("solidcraft/7"); let mut features = legacy["features"].clone(); pieces_in_shell_order(&mut features); let params = legacy["params"].clone(); @@ -1268,8 +1272,8 @@ mod tests { #[test] fn version_eight_persistent_angle_sweep_and_slot_dimensions_roundtrip() { - use solvecraft_geom::Vec2; - use solvecraft_sketch::{ConstraintKind, Sketch}; + use solidcraft_geom::Vec2; + use solidcraft_sketch::{ConstraintKind, Sketch}; let mut d = Document::new("persistent typed dimensions"); let heading = d.new_model_param("45 deg", "deg"); let sweep = d.new_model_param("180 deg", "deg"); @@ -1358,8 +1362,11 @@ mod tests { #[test] fn versions() { - assert_eq!(version_of("solvecraft/1"), Some(1)); - assert_eq!(version_of("solvecraft/x"), None); + assert_eq!(version_of("solidcraft/1"), Some(1)); + assert_eq!(version_of("solidcraft/x"), None); + // Compatibility shim: files saved before the rename carry the legacy tag. + assert_eq!(version_of("solvecraft/13"), Some(13)); + assert_eq!(version_of("solvecraftx/13"), None); let d = Document::new("A"); assert_eq!(d.format, format_string(FORMAT)); let back = read(&d.to_json()).unwrap(); @@ -1378,44 +1385,44 @@ mod tests { ) .unwrap(); let mut json: Value = serde_json::from_str(&doc.to_json()).unwrap(); - json["format"] = Value::from("solvecraft/5"); + json["format"] = Value::from("solidcraft/5"); let migrated = read(&json.to_string()).unwrap(); let crate::FeatureKind::ConstructionPlane { plane } = &migrated.feature(id).unwrap().kind else { panic!("plane") }; let (values, errors) = migrated.param_values(); assert!(errors.is_empty()); let p = migrated.resolve_plane(&values, plane, 0).unwrap(); - assert_eq!(p.y, solvecraft_geom::Vec3::Z); - assert_eq!(p.normal(), -solvecraft_geom::Vec3::Y); - assert_eq!(p.origin, solvecraft_geom::Vec3::new(0.0, -7.0, 0.0)); + assert_eq!(p.y, solidcraft_geom::Vec3::Z); + assert_eq!(p.normal(), -solidcraft_geom::Vec3::Y); + assert_eq!(p.origin, solidcraft_geom::Vec3::new(0.0, -7.0, 0.0)); assert_eq!(read(&migrated.to_json()).unwrap(), migrated); let fresh = doc.resolve_plane(&values, &crate::PlaneRef::Origin { name: "XZ".into() }, 0).unwrap(); - assert_eq!(fresh.normal(), solvecraft_geom::Vec3::Y); + assert_eq!(fresh.normal(), solidcraft_geom::Vec3::Y); } #[test] fn older_upgraded_newer_refused_others_rejected() { let mut v: Value = serde_json::from_str(&Document::new("A").to_json()).unwrap(); - v["format"] = Value::from("solvecraft/1"); + v["format"] = Value::from("solidcraft/1"); assert_eq!(read(&v.to_string()).unwrap().format, format_string(FORMAT)); v.as_object_mut().unwrap().remove("format"); assert!(read(&v.to_string()).is_ok(), "a file without a format is format 1"); v["format"] = Value::from(format_string(FORMAT + 1)); let e = read(&v.to_string()).unwrap_err().to_string(); assert!(e.contains("newer SolidCraft"), "{e}"); - for bad in ["solvecraft/0", "acad/3", "solvecraft/-1", "solvecraft/99999999999999999999"] { + for bad in ["solidcraft/0", "acad/3", "solidcraft/-1", "solidcraft/99999999999999999999"] { v["format"] = Value::from(bad); assert!(read(&v.to_string()).is_err(), "{bad}"); } assert!(read("[]").is_err()); // Format 2 body colours become body appearances. let mut old: Value = serde_json::from_str(&Document::new("A").to_json()).unwrap(); - old["format"] = Value::from("solvecraft/2"); + old["format"] = Value::from("solidcraft/2"); old["appearances"] = serde_json::json!({"Body1": [200, 10, 20]}); let d = read(&old.to_string()).unwrap(); let l = d.appearances.bodies.get("Body1").unwrap(); // Format 3 joints (aligned unless flipped) keep their placement. let mut j: Value = serde_json::from_str(&Document::new("A").to_json()).unwrap(); - j["format"] = Value::from("solvecraft/3"); + j["format"] = Value::from("solidcraft/3"); j["assembly"] = serde_json::json!({"joints": [{"id": 1, "name": "J", "kind": "rigid", "a": {"occurrence": 0, "snap": {"type": "point", "point": [0, 0, 0]}}, "b": {"occurrence": 0, "snap": {"type": "point", "point": [0, 0, 0]}}}]}); let dj = read(&j.to_string()).unwrap(); assert!(dj.assembly.joints[0].flip, "aligned before = not mated now"); @@ -1431,14 +1438,14 @@ mod tests { /// file too, before the arrays are deserialised. #[test] fn oversized_sketches_are_refused_before_typed_decoding() { - use solvecraft_sketch::{MAX_CONSTRAINTS, MAX_CURVES, MAX_POINTS, Sketch}; + use solidcraft_sketch::{MAX_CONSTRAINTS, MAX_CURVES, MAX_POINTS, Sketch}; // A sketch right at the point cap, chained by coincident constraints. let mut sketch = Sketch::new(); let mut previous = 0; while sketch.points.len() < MAX_POINTS { - let i = sketch.add_point(solvecraft_geom::Vec2::new(0.001 * sketch.points.len() as f64, 0.0), None).unwrap(); - sketch.add_constraint(solvecraft_sketch::ConstraintKind::Coincident { p: previous, q: i }, None).unwrap(); + let i = sketch.add_point(solidcraft_geom::Vec2::new(0.001 * sketch.points.len() as f64, 0.0), None).unwrap(); + sketch.add_constraint(solidcraft_sketch::ConstraintKind::Coincident { p: previous, q: i }, None).unwrap(); previous = i; } let mut doc = Document::new("big sketch"); diff --git a/crates/doc/src/format_offsets.rs b/crates/doc/src/format_offsets.rs index 82591c3b..c6087d19 100644 --- a/crates/doc/src/format_offsets.rs +++ b/crates/doc/src/format_offsets.rs @@ -380,11 +380,11 @@ mod tests { #[test] fn formats_one_through_eleven_keep_legacy_offset_geometry_and_ids() { - let mut sketch = solvecraft_sketch::Sketch::new(); - let center = sketch.add_point(solvecraft_geom::Vec2::new(4., 7.), Some("center")).unwrap(); - sketch.add_circle(solvecraft_geom::Vec2::new(4., 7.), 10., Some(center), Some("source")).unwrap(); - sketch.add_circle(solvecraft_geom::Vec2::new(4., 7.), 9., Some(center), Some("offset")).unwrap(); - sketch.offsets.push(solvecraft_sketch::OffsetChain { + let mut sketch = solidcraft_sketch::Sketch::new(); + let center = sketch.add_point(solidcraft_geom::Vec2::new(4., 7.), Some("center")).unwrap(); + sketch.add_circle(solidcraft_geom::Vec2::new(4., 7.), 10., Some(center), Some("source")).unwrap(); + sketch.add_circle(solidcraft_geom::Vec2::new(4., 7.), 9., Some(center), Some("offset")).unwrap(); + sketch.offsets.push(solidcraft_sketch::OffsetChain { sources: vec![("source".into(), true)], outputs: vec!["offset".into()], distance: 1., @@ -409,15 +409,15 @@ mod tests { let state = model.state(); let solved = state.sketch(sketch_id).unwrap(); let radius = solved.sketch.curves.iter().find(|curve| curve.id == "offset").map(|curve| &curve.kind); - assert!(matches!(radius,Some(solvecraft_sketch::CurveKind::Circle {r,..}) if *r ==9.)); + assert!(matches!(radius,Some(solidcraft_sketch::CurveKind::Circle {r,..}) if *r ==9.)); } } #[test] fn missing_boundary_or_defining_sketch_reopens_as_a_model_error_without_retarget() { for missing_sketch in [false, true] { - let mut sketch = solvecraft_sketch::Sketch::new(); - sketch.add_circle(solvecraft_geom::Vec2::new(0., 0.), 10., None, Some("unrelated-circle")).unwrap(); + let mut sketch = solidcraft_sketch::Sketch::new(); + sketch.add_circle(solidcraft_geom::Vec2::new(0., 0.), 10., None, Some("unrelated-circle")).unwrap(); let mut document = Document::new("repairable lost boundary"); let id = document.add_feature(FeatureKind::Sketch { plane: crate::PlaneRef::Origin { name: "XY".into() }, sketch }, None).unwrap(); document diff --git a/crates/doc/src/history_names.rs b/crates/doc/src/history_names.rs index af1757e5..80d400ec 100644 --- a/crates/doc/src/history_names.rs +++ b/crates/doc/src/history_names.rs @@ -17,7 +17,7 @@ use std::collections::{BTreeMap, BTreeSet}; use std::sync::{Arc, OnceLock}; -use solvecraft_kernel::history::{FaceOrigin, FaceProv, History}; +use solidcraft_kernel::history::{FaceOrigin, FaceProv, History}; use crate::ModelBody; use crate::lineage::{Lineage, Operand}; diff --git a/crates/doc/src/joints.rs b/crates/doc/src/joints.rs index ead63f33..f0f56044 100644 --- a/crates/doc/src/joints.rs +++ b/crates/doc/src/joints.rs @@ -13,7 +13,7 @@ use std::collections::{BTreeMap, BTreeSet, VecDeque}; use serde::{Deserialize, Serialize}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use crate::{DocError, Document, IDENTITY, Mat, ModelState, Result, apply_point, apply_vector, mat_inverse, mat_mul}; @@ -273,9 +273,9 @@ pub fn resolve_origin(doc: &Document, st: &ModelState, o: &JointOrigin) -> Resul let comp = component_of(doc, o.occurrence)?; let cyl = component_bodies(doc, st, comp) .into_iter() - .filter_map(|b| solvecraft_kernel::cylinder_face_at(&b.body, *pick)) + .filter_map(|b| solidcraft_kernel::cylinder_face_at(&b.body, *pick)) .min_by(|a, b| { - let off = |c: &solvecraft_kernel::CylinderFace| { + let off = |c: &solidcraft_kernel::CylinderFace| { let v = *pick - c.axis_point; ((v - c.axis * v.dot(c.axis)).len() - c.radius).abs() }; diff --git a/crates/doc/src/lib.rs b/crates/doc/src/lib.rs index 2617b468..b0aba13d 100644 --- a/crates/doc/src/lib.rs +++ b/crates/doc/src/lib.rs @@ -1,4 +1,4 @@ -//! The SolveCraft document: user and model parameters, and a parametric timeline of features +//! The SolidCraft document: user and model parameters, and a parametric timeline of features //! (sketches, extrudes, revolves, fillets, primitives, combines…). Evaluating the timeline with //! the kernel produces the model (bodies and solved sketches). Evaluation is incremental: a //! feature is recomputed only when it, a parameter it uses, or an earlier feature changed. @@ -47,8 +47,8 @@ pub use eval::{ state_in_frame_at, through_all_distance, world_state, }; pub use params::{ParamRow, ParamUser, cycles}; -pub use solvecraft_kernel as kernel; -pub use solvecraft_sketch as sketch; +pub use solidcraft_kernel as kernel; +pub use solidcraft_sketch as sketch; #[derive(Debug, Clone, PartialEq, thiserror::Error)] pub enum DocError { @@ -59,9 +59,9 @@ pub enum DocError { #[error("unknown {0}")] Unknown(String), #[error(transparent)] - Sketch(#[from] solvecraft_sketch::SketchError), + Sketch(#[from] solidcraft_sketch::SketchError), #[error(transparent)] - Kernel(#[from] solvecraft_kernel::KernelError), + Kernel(#[from] solidcraft_kernel::KernelError), } pub type Result = std::result::Result; diff --git a/crates/doc/src/lineage.rs b/crates/doc/src/lineage.rs index a883135d..505faa2e 100644 --- a/crates/doc/src/lineage.rs +++ b/crates/doc/src/lineage.rs @@ -11,9 +11,9 @@ use std::sync::{Arc, OnceLock}; -use solvecraft_geom::Region2; -use solvecraft_kernel::Body; -use solvecraft_kernel::history::{Change, FaceOrigin, FaceProv, History}; +use solidcraft_geom::Region2; +use solidcraft_kernel::Body; +use solidcraft_kernel::history::{Change, FaceOrigin, FaceProv, History}; use crate::SolvedSketch; @@ -183,7 +183,7 @@ impl<'a> ProfileRoles<'a> { if history.operands != 0 { return None; } - let segments: Vec<&solvecraft_geom::Seg2> = + let segments: Vec<&solidcraft_geom::Seg2> = self.region.map(|r| std::iter::once(&r.outer).chain(&r.holes).flat_map(|l| &l.segs).collect()).unwrap_or_default(); let curves = self.sketch.map(crate::config::ExactSketchCurves::new); let curve = |segment: usize| -> Option { curves.as_ref()?.find(segments.get(segment)?).map(|id| format!("side:{id}")) }; @@ -235,7 +235,7 @@ pub(crate) fn box_tool(body: Body) -> Traced { sources .iter() .map(|source| match source { - Some(solvecraft_kernel::FaceSource::Box { axis, high, .. }) => { + Some(solidcraft_kernel::FaceSource::Box { axis, high, .. }) => { ["x", "y", "z"].get(usize::from(*axis)).map(|a| vec![format!("{}{a}", if *high { "+" } else { "-" })]).unwrap_or_default() } _ => Vec::new(), diff --git a/crates/doc/src/motion.rs b/crates/doc/src/motion.rs index e99d487f..1b0a9217 100644 --- a/crates/doc/src/motion.rs +++ b/crates/doc/src/motion.rs @@ -3,7 +3,7 @@ //! shown without changing the design). use serde::{Deserialize, Serialize}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; /// Occurrences whose bodies may touch but not pass through each other while joints move. #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/crates/doc/src/name_lookup.rs b/crates/doc/src/name_lookup.rs index d9305c92..c9036e7b 100644 --- a/crates/doc/src/name_lookup.rs +++ b/crates/doc/src/name_lookup.rs @@ -8,7 +8,7 @@ //! was worked out from geometry, found where the reference's point no longer lies, is reported: //! only geometry decided. -use solvecraft_geom::{Mesh, Vec3}; +use solidcraft_geom::{Mesh, Vec3}; use crate::history_names::{Match, Names}; use crate::naming::{self, closest, edge_names, face_names, legacy_edge_names, legacy_face_names, strip_piece}; diff --git a/crates/doc/src/named_projection_tests.rs b/crates/doc/src/named_projection_tests.rs index 201f30b8..4adae985 100644 --- a/crates/doc/src/named_projection_tests.rs +++ b/crates/doc/src/named_projection_tests.rs @@ -1,11 +1,11 @@ use crate::{Document, ModelBody, ModelState}; -use solvecraft_geom::{Plane, Vec3}; -use solvecraft_sketch::{LinkKind, LinkSource, Sketch}; +use solidcraft_geom::{Plane, Vec3}; +use solidcraft_sketch::{LinkKind, LinkSource, Sketch}; use std::collections::BTreeMap; use std::sync::Arc; fn named_box(name: &str, width: f64, height: f64) -> ModelBody { - let body = solvecraft_kernel::box_solid(Vec3::ZERO, Vec3::new(width, width, height)).unwrap(); + let body = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(width, width, height)).unwrap(); let mut names = vec!["unrelated".to_string(); body.faces(0.01).unwrap().len()]; let top = body.faces(0.01).unwrap().into_iter().find(|face| face.plane_normal.is_some_and(|normal| normal.z > 0.99)).unwrap().index; names[top] = "shared:top".into(); diff --git a/crates/doc/src/naming.rs b/crates/doc/src/naming.rs index ff23126d..a2d0204e 100644 --- a/crates/doc/src/naming.rs +++ b/crates/doc/src/naming.rs @@ -17,7 +17,7 @@ use std::sync::{Arc, OnceLock}; -use solvecraft_geom::{Mesh, Seg2, Vec2, Vec3}; +use solidcraft_geom::{Mesh, Seg2, Vec2, Vec3}; use crate::history_names::Names; use crate::lineage::Lineage; @@ -737,7 +737,7 @@ mod tests { #[test] fn merged_face_votes_collectively_for_pieces_of_one_source() { - let body = ModelBody::new("Input".into(), solvecraft_kernel::box_solid(Vec3::ZERO, Vec3::new(4., 1., 1.)).unwrap(), 1); + let body = ModelBody::new("Input".into(), solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(4., 1., 1.)).unwrap(), 1); let mut mesh = Mesh::default(); let mut points = Vec::new(); for i in 0..3u32 { diff --git a/crates/doc/src/profile_refs.rs b/crates/doc/src/profile_refs.rs index 7359809d..5ca45488 100644 --- a/crates/doc/src/profile_refs.rs +++ b/crates/doc/src/profile_refs.rs @@ -5,7 +5,7 @@ use crate::{DocError, ProfileSel, Result, SolvedSketch, WholeProfileBoundary}; const MAX_SELECTIONS: usize = 128; -fn whole_boundary(profile: &solvecraft_sketch::Profile) -> Option { +fn whole_boundary(profile: &solidcraft_sketch::Profile) -> Option { let (outer, holes) = profile.whole_curve_boundaries()?; Some(WholeProfileBoundary { outer, holes }) } @@ -117,10 +117,10 @@ pub fn profile_selection_indices(ss: &SolvedSketch, selection: &ProfileSel) -> R if curves.iter().collect::>().len() != curves.len() && !ss.profiles.iter().any(|profile| same_ids(curves, &profile.outer_curves)) { - return Err(crate::curve_pick_error(solvecraft_sketch::CurvePick::Missing, curves, &ss.name)); + return Err(crate::curve_pick_error(solidcraft_sketch::CurvePick::Missing, curves, &ss.name)); } indices.push( - solvecraft_sketch::profile_by_curves(&ss.profiles, curves) + solidcraft_sketch::profile_by_curves(&ss.profiles, curves) .map_err(|error| crate::curve_pick_error(error, curves, &ss.name))?, ); } @@ -230,8 +230,8 @@ pub fn capture_profile_selection(ss: &SolvedSketch, indices: &[usize]) -> Result mod tests { #![allow(clippy::unwrap_used, clippy::panic)] use super::*; - use solvecraft_geom::{Plane, Vec2}; - use solvecraft_sketch::{Sketch, find_profiles, solve}; + use solidcraft_geom::{Plane, Vec2}; + use solidcraft_sketch::{Sketch, find_profiles, solve}; fn rounded_source(sketch: &mut Sketch, radius: f64) -> Vec<(String, bool)> { let endpoints = [ @@ -328,13 +328,13 @@ mod tests { ) .unwrap(); model.evaluate(&document); - let volume = solvecraft_kernel::measure(&model.state().bodies[0].body).unwrap().volume; + let volume = solidcraft_kernel::measure(&model.state().bodies[0].body).unwrap().volume; assert!((volume - 2. * 73.2 * 63.2).abs() < 1e-6, "{volume}"); let reopened = crate::Document::from_json(&document.to_json()).unwrap(); assert_eq!(reopened, document); let mut cold = crate::Model::new(); cold.evaluate(&reopened); - assert!((solvecraft_kernel::measure(&cold.state().bodies[0].body).unwrap().volume - volume).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&cold.state().bodies[0].body).unwrap().volume - volume).abs() < 1e-6); let crate::FeatureKind::Sketch { sketch, .. } = &mut document.features[0].kind else { panic!("sketch") }; set_source_radius(sketch, 2.); model.evaluate(&document); @@ -345,7 +345,7 @@ mod tests { assert_eq!(active, original_roles); let rounded_area = 75.2 * 65.2 - (4. - std::f64::consts::PI) * 0.6 * 0.6; assert!((crate::select_profiles(solved, &selection).unwrap()[0].area() - rounded_area).abs() < 1e-7); - let volume = solvecraft_kernel::measure(&model.state().bodies[0].body).unwrap().volume; + let volume = solidcraft_kernel::measure(&model.state().bodies[0].body).unwrap().volume; // Kernel volume integrates the curved display mesh; the analytic region above // checks the exact rounded geometry independently of that tessellation. assert!((volume - 2. * rounded_area).abs() < 0.002, "{volume}"); @@ -359,7 +359,7 @@ mod tests { ); let reopened = crate::Document::from_json(&document.to_json()).unwrap(); cold.evaluate(&reopened); - assert!((solvecraft_kernel::measure(&cold.state().bodies[0].body).unwrap().volume - volume).abs() < 1e-5); + assert!((solidcraft_kernel::measure(&cold.state().bodies[0].body).unwrap().volume - volume).abs() < 1e-5); } #[test] @@ -387,7 +387,7 @@ mod tests { fn periodic_offset_ring_whole_source_survives_adaptive_spans_and_reopen() { let mut sketch = Sketch::new(); let major = sketch.add_point(Vec2::new(10., 0.), None).unwrap(); - let ellipse = sketch.add_curve(solvecraft_sketch::CurveKind::Ellipse { c: 0, m: major, r: 5. }, None).unwrap(); + let ellipse = sketch.add_curve(solidcraft_sketch::CurveKind::Ellipse { c: 0, m: major, r: 5. }, None).unwrap(); let first = sketch.add_freeform_offset(ellipse, true, -1.).unwrap(); sketch.add_freeform_offset(first, true, -0.5).unwrap(); let mut document = crate::Document::new("periodic offset ring multiset"); @@ -453,7 +453,7 @@ mod tests { ) .unwrap(); model.evaluate(&document); - let initial_volume = solvecraft_kernel::measure(&model.state().bodies[0].body).unwrap().volume; + let initial_volume = solidcraft_kernel::measure(&model.state().bodies[0].body).unwrap().volume; // Kernel measurement integrates the curved mesh. Scale the established // periodic-ring tolerance (0.05 mm³ at height3) to this height2 extrusion. // Exact profile identity and cold/warm equality below remain strict. @@ -481,7 +481,7 @@ mod tests { assert!(updated_region.contains(ring_point)); assert!(!updated_region.contains(source_point + normal * 0.5)); assert!(!updated_region.contains(source_point + normal * 1.75)); - let updated_volume = solvecraft_kernel::measure(&updated_state.bodies[0].body).unwrap().volume; + let updated_volume = solidcraft_kernel::measure(&updated_state.bodies[0].body).unwrap().volume; assert!(updated_volume > initial_volume); assert!( (updated_volume - 2. * updated_region.area()).abs() < volume_tolerance, @@ -491,7 +491,7 @@ mod tests { let reopened_document = crate::Document::from_json(&document.to_json()).unwrap(); cold.evaluate(&reopened_document); assert!(cold.results.iter().all(|result| result.error.is_none())); - assert!((solvecraft_kernel::measure(&cold.state().bodies[0].body).unwrap().volume - updated_volume).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&cold.state().bodies[0].body).unwrap().volume - updated_volume).abs() < 1e-6); // A new hole is a semantic boundary change, not the old ring. Whole-source // identities include holes and must not silently absorb the new material cutout. diff --git a/crates/doc/src/project.rs b/crates/doc/src/project.rs index 8188e858..b3a17dea 100644 --- a/crates/doc/src/project.rs +++ b/crates/doc/src/project.rs @@ -8,8 +8,8 @@ use std::collections::BTreeMap; -use solvecraft_geom::{Mesh, Plane, Vec2, Vec3}; -use solvecraft_sketch::{CurveKind, LinkGeom, LinkKind, LinkSource, Sketch}; +use solidcraft_geom::{Mesh, Plane, Vec2, Vec3}; +use solidcraft_sketch::{CurveKind, LinkGeom, LinkKind, LinkSource, Sketch}; use crate::eval::ModelState; use crate::expr::Value; @@ -259,7 +259,7 @@ fn resolve_uncached( _ => vec![LinkGeom::Point(o)], } } - LinkSource::Text { text, at, height, angle } => solvecraft_sketch::text_geometry(text, *at, *height, *angle)?, + LinkSource::Text { text, at, height, angle } => solidcraft_sketch::text_geometry(text, *at, *height, *angle)?, LinkSource::Plane { name } => { let pr = if Plane::named(name).is_some() { PlaneRef::Origin { name: name.clone() } } else { PlaneRef::Construction { name: name.clone() } }; @@ -310,7 +310,7 @@ fn projection_face_edges(body: &crate::ModelBody, mesh: &Mesh, index: u32, plane .body .face_sources() .and_then(|sources| sources.get(index as usize)) - .is_some_and(|source| matches!(source, Some(solvecraft_kernel::FaceSource::ShellPlanar { .. }))); + .is_some_and(|source| matches!(source, Some(solidcraft_kernel::FaceSource::ShellPlanar { .. }))); if shell_plane { order::raw_face_edges(mesh, index, plane) } else { order::face_edges(mesh, index, plane) } } @@ -319,10 +319,10 @@ fn certified_planar_construction(body: &crate::ModelBody, index: usize) -> bool matches!( source, Some( - solvecraft_kernel::FaceSource::ShellPlanar { producer: Some(_), .. } - | solvecraft_kernel::FaceSource::Box { producer: Some(_), .. } - | solvecraft_kernel::FaceSource::CertifiedExtrudeSide { plane: Some(_), .. } - | solvecraft_kernel::FaceSource::CertifiedExtrudeCap { .. } + solidcraft_kernel::FaceSource::ShellPlanar { producer: Some(_), .. } + | solidcraft_kernel::FaceSource::Box { producer: Some(_), .. } + | solidcraft_kernel::FaceSource::CertifiedExtrudeSide { plane: Some(_), .. } + | solidcraft_kernel::FaceSource::CertifiedExtrudeCap { .. } ) ) }) @@ -785,7 +785,7 @@ fn as_spline(pts: &[Vec2], tol: f64) -> Option { if fit.len() < 2 { return None; } - let poly = solvecraft_sketch::spline_polyline(&fit, false, 3); + let poly = solidcraft_sketch::spline_polyline(&fit, false, 3); let ok = pts.iter().all(|q| { poly.windows(2) .map(|w| { diff --git a/crates/doc/src/project3d.rs b/crates/doc/src/project3d.rs index df1bb10b..42f23a37 100644 --- a/crates/doc/src/project3d.rs +++ b/crates/doc/src/project3d.rs @@ -4,8 +4,8 @@ use std::collections::BTreeMap; use std::sync::Arc; -use solvecraft_geom::{Mesh, Plane, Vec2, Vec3}; -use solvecraft_sketch::LinkSource; +use solidcraft_geom::{Mesh, Plane, Vec2, Vec3}; +use solidcraft_sketch::LinkSource; use crate::eval::ModelState; @@ -439,8 +439,8 @@ fn tri_dist(p: Vec3, t: &[Vec3; 3]) -> f64 { } /// Sample a fitted planar curve. -fn link_polyline(g: &solvecraft_sketch::LinkGeom) -> Vec { - use solvecraft_sketch::LinkGeom; +fn link_polyline(g: &solidcraft_sketch::LinkGeom) -> Vec { + use solidcraft_sketch::LinkGeom; match *g { LinkGeom::Line(a, b) => vec![a, b], LinkGeom::Circle(c, r) => (0..=96).map(|i| c + Vec2::from_angle(i as f64 / 96.0 * std::f64::consts::TAU) * r).collect(), @@ -452,11 +452,11 @@ fn link_polyline(g: &solvecraft_sketch::LinkGeom) -> Vec { let r = c.dist(a); (0..=48).map(|i| c + Vec2::from_angle(s0 + sw * i as f64 / 48.0) * r).collect() } - LinkGeom::Conic { a, apex, b, rho } => (0..=32).map(|i| solvecraft_sketch::conic_point(a, apex, b, rho, i as f64 / 32.0)).collect(), + LinkGeom::Conic { a, apex, b, rho } => (0..=32).map(|i| solidcraft_sketch::conic_point(a, apex, b, rho, i as f64 / 32.0)).collect(), LinkGeom::Ellipse { c, major, minor } => { - (0..=96).map(|i| solvecraft_sketch::ellipse_point(c, major, minor, i as f64 / 96.0 * std::f64::consts::TAU)).collect() + (0..=96).map(|i| solidcraft_sketch::ellipse_point(c, major, minor, i as f64 / 96.0 * std::f64::consts::TAU)).collect() } - LinkGeom::Spline { ref pts, control, degree, periodic } => solvecraft_sketch::spline_polyline_mode(pts, control, degree, periodic), + LinkGeom::Spline { ref pts, control, degree, periodic } => solidcraft_sketch::spline_polyline_mode(pts, control, degree, periodic), LinkGeom::Point(_) => Vec::new(), } } diff --git a/crates/doc/src/project_order.rs b/crates/doc/src/project_order.rs index 18bcdef6..16717692 100644 --- a/crates/doc/src/project_order.rs +++ b/crates/doc/src/project_order.rs @@ -3,7 +3,7 @@ use std::collections::BTreeMap; -use solvecraft_geom::{Mesh, Plane, Vec3}; +use solidcraft_geom::{Mesh, Plane, Vec3}; use crate::{DocError, Result}; @@ -60,7 +60,7 @@ fn boundary_edges(mesh: &Mesh, mut edges: Vec, plane: &Plane) -> Result= 2 && p.iter().all(|p| p.is_finite()))) { return Err(DocError::Invalid("face projection has an invalid boundary edge".into())); } - if edges.len() > solvecraft_sketch::MAX_LINK_ENTITIES { + if edges.len() > solidcraft_sketch::MAX_LINK_ENTITIES { return Err(DocError::Invalid("face projection exceeds the boundary edge limit".into())); } let geometry_key = |e: usize| { @@ -197,17 +197,17 @@ mod tests { let edges = family_edges(&mesh, &[0, 1].into_iter().collect(), &Plane::XY).unwrap(); assert_eq!(edges.len(), 8); let geom: Vec<_> = edges.iter().flat_map(|edge| super::super::fit_world(&Plane::XY, &mesh.edges[*edge])).collect(); - let mut sk = solvecraft_sketch::Sketch::new(); + let mut sk = solidcraft_sketch::Sketch::new(); let link = sk - .add_link(solvecraft_sketch::LinkKind::Project, solvecraft_sketch::LinkSource::Face { body: "ambiguous".into(), at: Vec3::ZERO }, &geom) + .add_link(solidcraft_sketch::LinkKind::Project, solidcraft_sketch::LinkSource::Face { body: "ambiguous".into(), at: Vec3::ZERO }, &geom) .unwrap(); assert!(sk.update_link(&link, &geom).is_err()); } #[test] fn family_line_normalization_preserves_corners_arcs_branches_and_retracing() { - use solvecraft_geom::Vec2; - use solvecraft_sketch::LinkGeom; + use solidcraft_geom::Vec2; + use solidcraft_sketch::LinkGeom; let line = |a, b| LinkGeom::Line(Vec2::new(a, 0.), Vec2::new(b, 0.)); assert_eq!(super::super::merge_family_lines(vec![line(0., 1.), line(2., 1.)]).unwrap(), vec![line(0., 2.)]); for geometry in [ @@ -271,8 +271,8 @@ mod tests { let normalized: Vec<_> = expected .iter() .map(|g| { - let solvecraft_sketch::LinkGeom::Line(a, b) = g[0] else { panic!("line") }; - if a.x > b.x || a.y > b.y { vec![solvecraft_sketch::LinkGeom::Line(b, a)] } else { g.clone() } + let solidcraft_sketch::LinkGeom::Line(a, b) = g[0] else { panic!("line") }; + if a.x > b.x || a.y > b.y { vec![solidcraft_sketch::LinkGeom::Line(b, a)] } else { g.clone() } }) .collect(); assert_eq!(actual, normalized); @@ -288,8 +288,8 @@ mod tests { #[test] fn rounded_boundary_arc_sweeps_survive_individual_edge_reversal() { - use solvecraft_geom::{Seg2, Vec2}; - use solvecraft_sketch::LinkGeom; + use solidcraft_geom::{Seg2, Vec2}; + use solidcraft_sketch::LinkGeom; let mut mesh = Mesh::default(); for (extent, radius) in [(10.0, 3.0), (8.0, 1.0)] { for k in 0..4 { diff --git a/crates/doc/src/sheet.rs b/crates/doc/src/sheet.rs index ab7616ab..31a2ac1c 100644 --- a/crates/doc/src/sheet.rs +++ b/crates/doc/src/sheet.rs @@ -10,8 +10,8 @@ //! together; the flat solid is the outline extruded through the thickness. use serde::{Deserialize, Serialize}; -use solvecraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3}; -use solvecraft_kernel::{self as kernel, Body, EdgeSpec, FaceSpec, SurfSpec}; +use solidcraft_geom::{Loop2, Plane, Region2, Seg2, Vec2, Vec3}; +use solidcraft_kernel::{self as kernel, Body, EdgeSpec, FaceSpec, SurfSpec}; use crate::expr::{self, Kind, Value}; use crate::{DocError, Document, IDENTITY, Mat, Result, apply_point, apply_vector, mat_inverse, mat_mul}; diff --git a/crates/doc/src/tests.rs b/crates/doc/src/tests.rs index 9546678e..75cd08ab 100644 --- a/crates/doc/src/tests.rs +++ b/crates/doc/src/tests.rs @@ -1,6 +1,6 @@ -use solvecraft_geom::{Vec2, Vec3}; -use solvecraft_kernel::measure; -use solvecraft_sketch::{ConstraintKind, Sketch}; +use solidcraft_geom::{Vec2, Vec3}; +use solidcraft_kernel::measure; +use solidcraft_sketch::{ConstraintKind, Sketch}; use crate::*; @@ -379,7 +379,7 @@ fn input_sketches_lists_every_variant_with_a_sketch_field() { #[test] fn timeline_clones_share_meshes_even_when_cloned_before_tessellation() { - let body = solvecraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap(); + let body = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap(); let a = crate::eval::ModelBody::new("box".into(), body, 1); let b = a.clone(); assert!(std::sync::Arc::ptr_eq(&a.mesh(), &b.mesh())); diff --git a/crates/engine/Cargo.toml b/crates/engine/Cargo.toml index 2f875a30..89d54bfd 100644 --- a/crates/engine/Cargo.toml +++ b/crates/engine/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "solvecraft-engine" +name = "solidcraft-engine" description = "SolidCraft engine: the session, the command registry (everything is a command), undo/redo and selection" version.workspace = true edition.workspace = true @@ -9,12 +9,12 @@ license.workspace = true workspace = true [dependencies] -solvecraft-geom = { workspace = true } -solvecraft-sketch = { workspace = true } -solvecraft-kernel = { workspace = true } -solvecraft-doc = { workspace = true } -solvecraft-io = { workspace = true } -solvecraft-render = { workspace = true } +solidcraft-geom = { workspace = true } +solidcraft-sketch = { workspace = true } +solidcraft-kernel = { workspace = true } +solidcraft-doc = { workspace = true } +solidcraft-io = { workspace = true } +solidcraft-render = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } thiserror = { workspace = true } diff --git a/crates/engine/src/boolean_regression_tests.rs b/crates/engine/src/boolean_regression_tests.rs index 20bcea16..d030f243 100644 --- a/crates/engine/src/boolean_regression_tests.rs +++ b/crates/engine/src/boolean_regression_tests.rs @@ -13,7 +13,7 @@ fn replay(script: &str) -> Session { } fn volume(s: &Session, body: &str) -> f64 { - s.model.state().body(body).map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).unwrap_or(0.0) + s.model.state().body(body).map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).unwrap_or(0.0) } /// Slab less a 557.68 × 100 × 120 pocket and the loft below its floor (556.68 × 99 down 4 mm to diff --git a/crates/engine/src/cmd/browser.rs b/crates/engine/src/cmd/browser.rs index d03501ca..016f4301 100644 --- a/crates/engine/src/cmd/browser.rs +++ b/crates/engine/src/cmd/browser.rs @@ -6,7 +6,7 @@ use serde_json::{Value, json}; -use solvecraft_doc::FeatureKind; +use solidcraft_doc::FeatureKind; use super::CommandSpec; use crate::params::{bad, bool_, str_}; @@ -92,7 +92,7 @@ fn group(s: &mut Session, p: &Value) -> Result { for g in d.browser_groups.iter_mut().filter(|g| g.component == component && g.folder == folder) { g.items.retain(|k| !items.contains(k)); } - d.browser_groups.push(solvecraft_doc::BrowserGroup { id, name: name.clone(), component, folder, items }); + d.browser_groups.push(solidcraft_doc::BrowserGroup { id, name: name.clone(), component, folder, items }); Ok(json!({ "group": id, "name": name })) } @@ -326,7 +326,7 @@ fn document_units(s: &mut Session, p: &Value) -> Result { .doc .params .iter() - .filter(|p| !p.model && p.unit.is_empty() && solvecraft_doc::expr::is_literal(&p.expr) && p.expr.trim().parse::().is_ok()) + .filter(|p| !p.model && p.unit.is_empty() && solidcraft_doc::expr::is_literal(&p.expr) && p.expr.trim().parse::().is_ok()) .map(|p| (p.name.clone(), p.expr.trim().to_string())) .collect(); for (name, e) in bare { @@ -354,7 +354,7 @@ fn document_units(s: &mut Session, p: &Value) -> Result { fn view_save(s: &mut Session, p: &Value) -> Result { let cmd = "view.save"; let c = p.get("camera").ok_or_else(|| bad(cmd, "`camera` is required"))?; - let mut v: solvecraft_doc::NamedView = serde_json::from_value(c.clone()).map_err(|e| bad(cmd, format!("bad camera: {e}")))?; + let mut v: solidcraft_doc::NamedView = serde_json::from_value(c.clone()).map_err(|e| bad(cmd, format!("bad camera: {e}")))?; let ok = [v.target.x, v.target.y, v.target.z, v.yaw, v.pitch, v.distance, v.fov].iter().all(|x| x.is_finite()) && v.distance > 0.0; if !ok { return Err(bad(cmd, "the camera must be finite with a positive distance")); @@ -405,7 +405,7 @@ fn view_list(s: &mut Session, _p: &Value) -> Result { } /// Rename an item key in the browser's groups and orders of a folder. -fn rename_key(d: &mut solvecraft_doc::Document, folder: &str, old: &str, new: &str) { +fn rename_key(d: &mut solidcraft_doc::Document, folder: &str, old: &str, new: &str) { for g in d.browser_groups.iter_mut().filter(|g| g.folder == folder) { for k in g.items.iter_mut().filter(|k| *k == old) { *k = new.to_string(); @@ -472,7 +472,7 @@ fn rename_body(s: &mut Session, p: &Value) -> Result { let bodies = std::collections::BTreeMap::from([(old.clone(), name.to_string())]); anchor.remap_target(&ids, &bodies)?; for link in &mut sketch.links { - if link.auto_face && matches!(&link.source,solvecraft_sketch::LinkSource::Face {body,..} if *body==old) { + if link.auto_face && matches!(&link.source,solidcraft_sketch::LinkSource::Face {body,..} if *body==old) { link.face_name = Some(anchor.target.name.clone()); } } @@ -499,10 +499,10 @@ fn rename_body(s: &mut Session, p: &Value) -> Result { } for column in &mut d.configs.columns { match column { - solvecraft_doc::config::Column::Material(body) | solvecraft_doc::config::Column::Appearance(body) if *body == old => { + solidcraft_doc::config::Column::Material(body) | solidcraft_doc::config::Column::Appearance(body) if *body == old => { *body = name.to_string(); } - solvecraft_doc::config::Column::FaceAppearance(target) if target.body == old => { + solidcraft_doc::config::Column::FaceAppearance(target) if target.body == old => { target.body = name.to_string(); } _ => {} @@ -592,7 +592,7 @@ fn redefine_sketch(s: &mut Session, p: &Value) -> Result { .filter(|link| { link.auto_face && link.face_name.as_ref() == Some(&anchor.target.name) - && matches!(&link.source,solvecraft_sketch::LinkSource::Face {body,..} if *body==anchor.target.body) + && matches!(&link.source,solidcraft_sketch::LinkSource::Face {body,..} if *body==anchor.target.body) }) .map(|link| link.id.clone()) .collect(); @@ -723,7 +723,7 @@ mod tests { run(&mut s, "view.save", json!({"camera": cam, "name": "Detail"})); run(&mut s, "view.rename", json!({"view": "Named View1", "name": "Overview"})); assert!(s.execute("view.rename", &json!({"view": "Overview", "name": "Detail"})).is_err()); - let back: solvecraft_doc::Document = serde_json::from_str(&serde_json::to_string(&*s.doc).unwrap()).unwrap(); + let back: solidcraft_doc::Document = serde_json::from_str(&serde_json::to_string(&*s.doc).unwrap()).unwrap(); assert_eq!(back.named_views.len(), 2); assert_eq!(back.named_views[0].target.z, 3.0); run(&mut s, "view.delete", json!({"view": "Detail"})); @@ -748,7 +748,7 @@ mod tests { run(&mut s, "construct.plane.offset", json!({"base": "XY", "offset": 5, "name": "Floor"})); let body = s.model.state().bodies[0].name.clone(); let drain_name = s.doc.feature(drain).unwrap().name.clone(); - let cam = solvecraft_render::Camera::default(); + let cam = solidcraft_render::Camera::default(); let lines = |s: &Session| crate::view::scene(s, &cam).lines.len(); let shown = |s: &mut Session| { let d = run(s, "document.inspect", json!({})); @@ -806,7 +806,7 @@ mod tests { run(&mut s, "browser.move", json!({"folder": "bodies", "items": [b.clone()]})); assert_eq!(s.doc.browser_groups[0].items, vec!["Bolt".to_string()]); run(&mut s, "browser.order", json!({"folder": "bodies", "order": [b.clone(), "Bolt"]})); - let saved: solvecraft_doc::Document = serde_json::from_str(&serde_json::to_string(&*s.doc).unwrap()).unwrap(); + let saved: solidcraft_doc::Document = serde_json::from_str(&serde_json::to_string(&*s.doc).unwrap()).unwrap(); assert_eq!(saved.browser_groups, s.doc.browser_groups); assert_eq!(saved.browser_order["0/bodies"], vec![b, "Bolt".to_string()]); run(&mut s, "browser.ungroup", json!({"group": g})); diff --git a/crates/engine/src/cmd/clipboard.rs b/crates/engine/src/cmd/clipboard.rs index c26baefd..238abb7c 100644 --- a/crates/engine/src/cmd/clipboard.rs +++ b/crates/engine/src/cmd/clipboard.rs @@ -5,7 +5,7 @@ use std::collections::BTreeMap; use serde_json::{Value, json}; -use solvecraft_doc::{FeatureKind, PlaneRef}; +use solidcraft_doc::{FeatureKind, PlaneRef}; use super::CommandSpec; use crate::params::{bad, string_list, vec3}; @@ -31,7 +31,7 @@ fn copy(s: &mut Session, p: &Value) -> Result { ids.push(s.doc.find_feature(k).map(|f| f.id).ok_or_else(|| bad(cmd, format!("no feature `{k}`")))?); } // Keep timeline order so sketches come before the features that use them. - let copied: Vec = s.doc.features.iter().filter(|f| ids.contains(&f.id)).cloned().collect(); + let copied: Vec = s.doc.features.iter().filter(|f| ids.contains(&f.id)).cloned().collect(); let names: Vec = copied.iter().map(|f| f.name.clone()).collect(); s.clipboard = copied; Ok(json!({"copied": names})) @@ -41,7 +41,7 @@ fn copy(s: &mut Session, p: &Value) -> Result { fn explicit_planes(p: &mut PlaneRef) { match p { PlaneRef::Origin { name } => { - if let Some(plane) = solvecraft_geom::Plane::named(name) { + if let Some(plane) = solidcraft_geom::Plane::named(name) { *p = PlaneRef::Custom { plane }; } } @@ -66,7 +66,7 @@ fn paste(s: &mut Session, p: &Value) -> Result { let mut used: std::collections::BTreeSet = s.model.state().bodies.iter().map(|body| body.name.clone()).collect(); let mut notices = Vec::new(); let author_path = s.active_occurrence_path.clone().unwrap_or_else(|| s.doc.first_path(comp)); - solvecraft_doc::EmbossFace::component_frame(&s.placed_document(), comp, &author_path)?; + solidcraft_doc::EmbossFace::component_frame(&s.placed_document(), comp, &author_path)?; let mut names: BTreeMap = BTreeMap::new(); let mut made = Vec::new(); for f in feats { @@ -92,7 +92,7 @@ fn paste(s: &mut Session, p: &Value) -> Result { if let FeatureKind::Sketch { sketch, .. } = &mut kind { for link in &mut sketch.links { if link.auto_face { - if let solvecraft_sketch::LinkSource::Face { body, .. } = &mut link.source + if let solidcraft_sketch::LinkSource::Face { body, .. } = &mut link.source && let Some(mapped) = bodies.get(body) { *body = mapped.clone(); @@ -138,7 +138,7 @@ fn paste(s: &mut Session, p: &Value) -> Result { if let FeatureKind::Sketch { plane, .. } | FeatureKind::ConstructionPlane { plane } = &mut kind { explicit_planes(plane); } - let mut m = solvecraft_doc::IDENTITY; + let mut m = solidcraft_doc::IDENTITY; m[3][0] = t.x; m[3][1] = t.y; m[3][2] = t.z; diff --git a/crates/engine/src/cmd/component.rs b/crates/engine/src/cmd/component.rs index 16bda80f..4f086549 100644 --- a/crates/engine/src/cmd/component.rs +++ b/crates/engine/src/cmd/component.rs @@ -1,11 +1,11 @@ -//! Components and occurrences: the design as a tree (see `solvecraft_doc` assembly docs). +//! Components and occurrences: the design as a tree (see `solidcraft_doc` assembly docs). //! New sketches and features go into the active component, authored in its frame; bodies //! belong to their feature's component unless moved; occurrences place components (instances //! share contents, "paste new" copies them) and can be grounded and moved. use serde_json::{Value, json}; -use solvecraft_doc::{FeatureKind, Mat, Occurrence}; -use solvecraft_geom::Vec3; +use solidcraft_doc::{FeatureKind, Mat, Occurrence}; +use solidcraft_geom::Vec3; use super::CommandSpec; use crate::params::{bad, bool_, str_, string_list, vec3}; @@ -70,7 +70,7 @@ fn occurrence(s: &Session, p: &Value, cmd: &str) -> Result { } fn translation(t: Vec3) -> Mat { - solvecraft_doc::rigid(t, Vec3::ZERO, Vec3::Z, 0.0) + solidcraft_doc::rigid(t, Vec3::ZERO, Vec3::Z, 0.0) } fn move_vector(p: &Value, key: &str, default: Vec3, coordinate: bool, cmd: &str) -> Result { @@ -285,7 +285,7 @@ fn copy_occurrence(s: &mut Session, p: &Value) -> Result { let parent = if p.get("parent").is_some() { component(s, p, "parent", cmd)? } else { src.parent }; let t = move_vector(p, "translate", Vec3::ZERO, true, cmd)?; let base = s.pending_moves.get(&src.id).unwrap_or(&src.transform); - let m = solvecraft_doc::mat_mul(&translation(t), base); + let m = solidcraft_doc::mat_mul(&translation(t), base); if !valid_position(&m) { return Err(bad(cmd, "copy transform exceeds supported numeric range")); } @@ -314,8 +314,8 @@ fn paste_new(s: &mut Session, p: &Value) -> Result { s.doc.occurrence_of(comp).cloned() }; let t = move_vector(p, "translate", Vec3::ZERO, true, cmd)?; - let base = occ.as_ref().map(|o| s.pending_moves.get(&o.id).copied().unwrap_or(o.transform)).unwrap_or(solvecraft_doc::IDENTITY); - let transform = solvecraft_doc::mat_mul(&translation(t), &base); + let base = occ.as_ref().map(|o| s.pending_moves.get(&o.id).copied().unwrap_or(o.transform)).unwrap_or(solidcraft_doc::IDENTITY); + let transform = solidcraft_doc::mat_mul(&translation(t), &base); if !valid_position(&transform) { return Err(bad(cmd, "copy transform exceeds supported numeric range")); } @@ -361,7 +361,7 @@ fn move_occurrence(s: &mut Session, p: &Value) -> Result { let t = move_vector(p, "translate", Vec3::ZERO, true, cmd)?; let axis = move_vector(p, "axis", Vec3::Z, false, cmd)?; let angle = match p.get("angle") { - Some(Value::String(e)) => s.doc.eval(e, solvecraft_doc::expr::Kind::Angle).map_err(|e| bad(cmd, e.to_string()))?, + Some(Value::String(e)) => s.doc.eval(e, solidcraft_doc::expr::Kind::Angle).map_err(|e| bad(cmd, e.to_string()))?, None => 0.0, Some(value) => value.as_f64().ok_or_else(|| bad(cmd, "angle must be a number or angle expression"))?, }; @@ -372,7 +372,7 @@ fn move_occurrence(s: &mut Session, p: &Value) -> Result { return Err(bad(cmd, "rotation axis must have a finite nonzero length")); } let origin = move_vector(p, "origin", Vec3::ZERO, true, cmd)?; - let delta = solvecraft_doc::rigid(t, origin, axis, angle); + let delta = solidcraft_doc::rigid(t, origin, axis, angle); let space = match p.get("space") { None => "local", Some(Value::String(space)) => space.as_str(), @@ -384,8 +384,8 @@ fn move_occurrence(s: &mut Session, p: &Value) -> Result { let path = if p.get("path").is_some() || space == "world" { Some(occurrence_path(s, p, id, cmd)?) } else { None }; let delta = if space == "world" { let placement = s.occurrence_placement(path.as_deref().unwrap_or(&[]))?; - let inverse = solvecraft_doc::mat_inverse(&placement.parent_world).ok_or_else(|| bad(cmd, "parent frame is singular"))?; - solvecraft_doc::mat_mul(&solvecraft_doc::mat_mul(&inverse, &delta), &placement.parent_world) + let inverse = solidcraft_doc::mat_inverse(&placement.parent_world).ok_or_else(|| bad(cmd, "parent frame is singular"))?; + solidcraft_doc::mat_mul(&solidcraft_doc::mat_mul(&inverse, &delta), &placement.parent_world) } else { delta }; @@ -396,7 +396,7 @@ fn move_occurrence(s: &mut Session, p: &Value) -> Result { || base[1][3].abs() > 1e-12 || base[2][3].abs() > 1e-12 || (base[3][3] - 1.0).abs() > 1e-12 - || solvecraft_doc::mat_inverse(&base).is_none() + || solidcraft_doc::mat_inverse(&base).is_none() { return Err(bad(cmd, "base_transform must be finite, affine and invertible")); } @@ -404,7 +404,7 @@ fn move_occurrence(s: &mut Session, p: &Value) -> Result { } else { s.pending_moves.get(&id).copied().unwrap_or(o.transform) }; - let next = solvecraft_doc::mat_mul(&delta, &cur); + let next = solidcraft_doc::mat_mul(&delta, &cur); if !valid_position(&next) { return Err(bad(cmd, "move transform exceeds supported numeric range")); } @@ -474,10 +474,10 @@ pub fn to_active_frame(s: &Session, kind: &mut FeatureKind) { return; } let m = s.active_component_frame(); - if solvecraft_doc::is_identity(&m) { + if solidcraft_doc::is_identity(&m) { return; } - if let Some(inv) = solvecraft_doc::mat_inverse(&m) { + if let Some(inv) = solidcraft_doc::mat_inverse(&m) { kind.transform_geometry(&inv); } } diff --git a/crates/engine/src/cmd/component_clone.rs b/crates/engine/src/cmd/component_clone.rs index 403b928f..c5895f4e 100644 --- a/crates/engine/src/cmd/component_clone.rs +++ b/crates/engine/src/cmd/component_clone.rs @@ -4,12 +4,12 @@ use std::collections::{BTreeMap, BTreeSet}; use crate::params::bad; use crate::{Result, Sel, Session}; use serde_json::{Value, json}; -use solvecraft_doc::{Document, Feature, FeatureKind, Mat, Occurrence}; +use solidcraft_doc::{Document, Feature, FeatureKind, Mat, Occurrence}; const CMD: &str = "component.paste_new"; const MAX_WORK: usize = 10_000; -fn mesh_distance(mesh: &solvecraft_geom::Mesh, point: solvecraft_geom::Vec3) -> f64 { +fn mesh_distance(mesh: &solidcraft_geom::Mesh, point: solidcraft_geom::Vec3) -> f64 { mesh.triangles .iter() .filter_map(|triangle| mesh.tri(triangle)) @@ -32,7 +32,7 @@ fn mesh_distance(mesh: &solvecraft_geom::Mesh, point: solvecraft_geom::Vec3) -> .fold(f64::INFINITY, f64::min) } -fn material_probe_matches(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh, point: solvecraft_geom::Vec3, tol_a: f64, tol_b: f64) -> bool { +fn material_probe_matches(a: &solidcraft_geom::Mesh, b: &solidcraft_geom::Mesh, point: solidcraft_geom::Vec3, tol_a: f64, tol_b: f64) -> bool { if !point.is_finite() || !tol_a.is_finite() || !tol_b.is_finite() || tol_a <= 0. || tol_b <= 0. { return false; } @@ -47,7 +47,7 @@ fn material_probe_matches(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh, let offset = 4. * tol_a.max(tol_b); let mut material = false; let mut void = false; - for axis in [solvecraft_geom::Vec3::X, solvecraft_geom::Vec3::Y, solvecraft_geom::Vec3::Z] { + for axis in [solidcraft_geom::Vec3::X, solidcraft_geom::Vec3::Y, solidcraft_geom::Vec3::Z] { for sign in [-1., 1.] { let q = point + axis * (sign * offset); if !q.is_finite() { @@ -294,7 +294,7 @@ pub(super) fn paste( if body.body.is_mesh() { return Err(bad(CMD, "Paste New of a reassigned mesh body is not supported yet")); } - let offset = source.model_body_offset(body).copied().unwrap_or(solvecraft_doc::IDENTITY); + let offset = source.model_body_offset(body).copied().unwrap_or(solidcraft_doc::IDENTITY); // An external transfer establishes the local baseline in this graph. // Freeze AFTER that boundary, then replay it as an identity ownership event. let entry = feats.iter().find(|f| { @@ -313,8 +313,8 @@ pub(super) fn paste( let entered = entry.and_then(|f| s.model.result(f.id)).map(|r| r.state.clone()); let base = entered.as_ref().and_then(|st| st.body(&body.name)).or_else(|| before.as_ref().and_then(|st| st.body(&body.name))).unwrap_or(body); let baseline_component = source.model_body_component(base); - let geometry = solvecraft_kernel::transform_matrix(&base.body, offset)?; - let step = solvecraft_kernel::step_export(&[&geometry], "SolidCraft independent reassigned body")?; + let geometry = solidcraft_kernel::transform_matrix(&base.body, offset)?; + let step = solidcraft_kernel::step_export(&[&geometry], "SolidCraft independent reassigned body")?; let new_name = unique(&body.name, &mut used); bodies.insert(body.name.clone(), new_name.clone()); let feature_name = unique(&format!("{} Base", body.name), &mut used); @@ -351,7 +351,7 @@ pub(super) fn paste( && let Some(p) = source_parameters.get(name.as_str()) && p.model { - if scratch.doc.params.len() >= solvecraft_doc::MAX_PARAMS { + if scratch.doc.params.len() >= solidcraft_doc::MAX_PARAMS { return Err(bad(CMD, "too many copied parameters")); } let new = scratch.doc_mut().new_model_param(&p.expr, &p.unit); @@ -367,7 +367,7 @@ pub(super) fn paste( if let Some(p) = source_parameters.get(old.as_str()) && p.model { - let expr = solvecraft_doc::clone_expression(&p.expr, ¶meters); + let expr = solidcraft_doc::clone_expression(&p.expr, ¶meters); if let Some(p) = scratch.doc_mut().params.iter_mut().find(|p| p.name == *new) { p.expr = expr; p.comment = source_parameters.get(old.as_str()).map(|p| p.comment.clone()).unwrap_or_default(); @@ -389,7 +389,7 @@ pub(super) fn paste( *from = *mapped; } else { *from = *to; - *transform = solvecraft_doc::IDENTITY; + *transform = solidcraft_doc::IDENTITY; } } if let FeatureKind::Emboss { face: Some(face), .. } = &mut f.kind @@ -466,15 +466,15 @@ pub(super) fn paste( return Err(bad(CMD, "cannot copy strict face paint through uncertified foreign re-export")); } if let Some(name) = &face.name { - face.point = solvecraft_doc::naming::point_on_face(&state, name, face.point) + face.point = solidcraft_doc::naming::point_on_face(&state, name, face.point) .map(|(point, _)| point) .ok_or_else(|| bad(CMD, "source frozen face appearance lost its identity"))?; } let source_body = state.body(&face.body).ok_or_else(|| bad(CMD, "source frozen face appearance lost its body"))?; - face.point = solvecraft_doc::apply_point(source.model_body_offset(source_body).unwrap_or(&solvecraft_doc::IDENTITY), face.point); + face.point = solidcraft_doc::apply_point(source.model_body_offset(source_body).unwrap_or(&solidcraft_doc::IDENTITY), face.point); face.name = None; } else if let Some(name) = &mut face.name { - *name = solvecraft_doc::remap_topology(name, &features, &bodies); + *name = solidcraft_doc::remap_topology(name, &features, &bodies); } face.body = new.clone(); if let Some(certificate) = &mut face.certificate { @@ -484,7 +484,7 @@ pub(super) fn paste( } } // Typed configuration and assembly helpers preserve original targets. - scratch.doc_mut().configs.clone_targets(&solvecraft_doc::config::ConfigCloneMaps { + scratch.doc_mut().configs.clone_targets(&solidcraft_doc::config::ConfigCloneMaps { features: features.clone(), components: components.clone(), bodies: bodies.clone(), @@ -500,7 +500,7 @@ pub(super) fn paste( notices.push(format!("{} retains external selected occurrence path {:?}", feature.name, face.path)); } if let FeatureKind::Sketch { sketch, .. } = &feature.kind { - use solvecraft_sketch::LinkSource as L; + use solidcraft_sketch::LinkSource as L; let mut pending: Vec<_> = sketch.links.iter().map(|l| &l.source).collect(); let mut work = 0; while let Some(link) = pending.pop() { @@ -563,7 +563,7 @@ pub(super) fn paste( } } for expression in feature.kind.expressions() { - for name in solvecraft_doc::expr::references(expression) { + for name in solidcraft_doc::expr::references(expression) { reference_work += 1; if reference_work > 100_000 { return Err(bad(CMD, "expression references exceed copy work limit")); @@ -590,8 +590,8 @@ pub(super) fn paste( if bodies.iter().any(|(old, new)| frozen.contains(old) && *new == face.body) { let body = resolved.body(&face.body).ok_or_else(|| bad(CMD, "missing frozen face body"))?; let face_index = - solvecraft_doc::appearance::face_index_at(body, face.point).ok_or_else(|| bad(CMD, "cannot reproduce frozen face appearance"))?; - let name = solvecraft_doc::naming::face_names(body).get(face_index).cloned().ok_or_else(|| bad(CMD, "missing frozen face identity"))?; + solidcraft_doc::appearance::face_index_at(body, face.point).ok_or_else(|| bad(CMD, "cannot reproduce frozen face appearance"))?; + let name = solidcraft_doc::naming::face_names(body).get(face_index).cloned().ok_or_else(|| bad(CMD, "missing frozen face identity"))?; face_updates.push((index, name)); } } @@ -638,10 +638,10 @@ pub(super) fn paste( for body in state.bodies.iter().filter(|b| selected.contains(&source.model_body_component(b))) { let new = bodies.get(&body.name).ok_or_else(|| bad(CMD, format!("body {} has no clone identity", body.name)))?; let cloned = copied_bodies.get(new.as_str()).ok_or_else(|| bad(CMD, format!("copy did not reproduce body {}", body.name)))?; - let a = solvecraft_kernel::transform_matrix(&body.body, *source.model_body_offset(body).unwrap_or(&solvecraft_doc::IDENTITY))?; - let b = solvecraft_kernel::transform_matrix(&cloned.body, *scratch.doc.model_body_offset(cloned).unwrap_or(&solvecraft_doc::IDENTITY))?; - let ma = solvecraft_kernel::measure(&a)?; - let mb = solvecraft_kernel::measure(&b)?; + let a = solidcraft_kernel::transform_matrix(&body.body, *source.model_body_offset(body).unwrap_or(&solidcraft_doc::IDENTITY))?; + let b = solidcraft_kernel::transform_matrix(&cloned.body, *scratch.doc.model_body_offset(cloned).unwrap_or(&solidcraft_doc::IDENTITY))?; + let ma = solidcraft_kernel::measure(&a)?; + let mb = solidcraft_kernel::measure(&b)?; let tolerance = 1e-5 * (1.0 + (ma.bbox.max - ma.bbox.min).len()) + 64.0 * f64::EPSILON * (ma.bbox.min.len() + ma.bbox.max.len()); let mut material_matches = true; let tol_a = (a.size() * 1e-3).max(1e-3).max(a.size() * 1e-6); @@ -658,7 +658,7 @@ pub(super) fn paste( for y in [0.2, 0.5, 0.8] { for z in [0.2, 0.5, 0.8] { let d = ma.bbox.max - ma.bbox.min; - let p = ma.bbox.min + solvecraft_geom::Vec3::new(d.x * x, d.y * y, d.z * z); + let p = ma.bbox.min + solidcraft_geom::Vec3::new(d.x * x, d.y * y, d.z * z); let roundoff = 64. * f64::EPSILON * (1. + p.len()); material_matches &= material_probe_matches(&mesh_a, &mesh_b, p, tol_a + roundoff, tol_b + roundoff); } @@ -668,7 +668,7 @@ pub(super) fn paste( // chord-volume biases after STEP. Only independently certified current // sections may replace that volume comparison; shape mismatches still // refuse even when their volumes happen to agree. - let volume_matches = match solvecraft_kernel::certified_prism_equivalent(&a, &b)? { + let volume_matches = match solidcraft_kernel::certified_prism_equivalent(&a, &b)? { Some(same) => same, None => (ma.volume - mb.volume).abs() <= 1e-5 * (1.0 + ma.volume.abs()), }; diff --git a/crates/engine/src/cmd/component_clone_assembly.rs b/crates/engine/src/cmd/component_clone_assembly.rs index 22a186fc..a07845ab 100644 --- a/crates/engine/src/cmd/component_clone_assembly.rs +++ b/crates/engine/src/cmd/component_clone_assembly.rs @@ -2,8 +2,8 @@ use std::collections::{BTreeMap, BTreeSet}; -use solvecraft_doc::Document; -use solvecraft_doc::joints::{Assembly, JointOrigin, Snap}; +use solidcraft_doc::Document; +use solidcraft_doc::joints::{Assembly, JointOrigin, Snap}; use crate::{EngineError, Result}; diff --git a/crates/engine/src/cmd/component_clone_assembly_tests.rs b/crates/engine/src/cmd/component_clone_assembly_tests.rs index 99a6e57d..6930db75 100644 --- a/crates/engine/src/cmd/component_clone_assembly_tests.rs +++ b/crates/engine/src/cmd/component_clone_assembly_tests.rs @@ -1,9 +1,9 @@ #![allow(clippy::unwrap_used, clippy::panic)] use super::*; -use solvecraft_doc::joints::{Joint, JointKind, MotionLink, NamedOrigin}; -use solvecraft_doc::motion::{ContactSet, ExplodedView, MotionStudy, StudyKey}; -use solvecraft_doc::{IDENTITY, Occurrence}; -use solvecraft_geom::Vec3; +use solidcraft_doc::joints::{Joint, JointKind, MotionLink, NamedOrigin}; +use solidcraft_doc::motion::{ContactSet, ExplodedView, MotionStudy, StudyKey}; +use solidcraft_doc::{IDENTITY, Occurrence}; +use solidcraft_geom::Vec3; fn at(occurrence: u64) -> JointOrigin { JointOrigin { occurrence, snap: Snap::Point { point: Vec3::ZERO, z: None } } diff --git a/crates/engine/src/cmd/component_clone_tests.rs b/crates/engine/src/cmd/component_clone_tests.rs index 49483555..f755ed25 100644 --- a/crates/engine/src/cmd/component_clone_tests.rs +++ b/crates/engine/src/cmd/component_clone_tests.rs @@ -28,8 +28,8 @@ fn coincident_independent_primitives_keep_their_own_semantic_faces() { let state = s.model.state(); let a = state.body("Cube").unwrap(); let b = state.body("Other").unwrap(); - let a_names = solvecraft_doc::naming::face_names(a); - let b_names = solvecraft_doc::naming::face_names(b); + let a_names = solidcraft_doc::naming::face_names(a); + let b_names = solidcraft_doc::naming::face_names(b); assert!(a_names.contains(&format!("F{first}:+z"))); assert!(b_names.contains(&format!("F{second}:+z"))); assert!(!b_names.contains(&format!("F{first}:+z"))); @@ -55,7 +55,7 @@ fn nested_definition_dag_and_histories_are_independent() { near(volume(&mut s, parent), 24.0); near(volume(&mut s, new), 16.0); let saved = s.doc.to_json(); - let reopened = solvecraft_doc::Document::from_json(&saved).unwrap(); + let reopened = solidcraft_doc::Document::from_json(&saved).unwrap(); assert_eq!(reopened.occurrences.iter().filter(|o| o.parent == new).count(), 2); } @@ -144,7 +144,7 @@ fn generated_feature_parameter_dependencies_are_independent() { .doc .features .iter() - .find(|f| f.component == clone && matches!(f.kind, solvecraft_doc::FeatureKind::Box { .. })) + .find(|f| f.component == clone && matches!(f.kind, solidcraft_doc::FeatureKind::Box { .. })) .unwrap() .param_names .clone(); @@ -195,12 +195,12 @@ fn copied_sheet_base_targets_its_own_sketch_history() { let copied = copy(&mut s, part); let new = copied["component"].as_u64().unwrap(); near(volume(&mut s, new), source_volume); - let clone_sketch = s.doc.features.iter().find(|f| f.component == new && matches!(f.kind, solvecraft_doc::FeatureKind::Sketch { .. })).unwrap().id; + let clone_sketch = s.doc.features.iter().find(|f| f.component == new && matches!(f.kind, solidcraft_doc::FeatureKind::Sketch { .. })).unwrap().id; assert_ne!(clone_sketch, sketch); - let base = s.doc.features.iter().find(|f| f.component == new && matches!(f.kind, solvecraft_doc::FeatureKind::SheetBase { .. })).unwrap(); + let base = s.doc.features.iter().find(|f| f.component == new && matches!(f.kind, solidcraft_doc::FeatureKind::SheetBase { .. })).unwrap(); assert_eq!(base.kind.input_sketches(), vec![clone_sketch]); // Mutate only source geometry, then verify the copied base remains unchanged. - if let solvecraft_doc::FeatureKind::Sketch { sketch, .. } = &mut s.doc_mut().feature_mut(sketch).unwrap().kind { + if let solidcraft_doc::FeatureKind::Sketch { sketch, .. } = &mut s.doc_mut().feature_mut(sketch).unwrap().kind { for p in &mut sketch.points { p.pos.x *= 2.0; } @@ -223,8 +223,8 @@ fn renamed_pattern_outputs_and_later_body_operands_follow_clone() { near(volume(&mut s, new), 24.0); let state = s.model.state(); let target = state.bodies.iter().find(|b| b.name == "Custom Output (copy)").unwrap(); - near(solvecraft_kernel::measure(&target.body).unwrap().centroid.y, 6.0); - assert_eq!(s.doc.features.iter().filter(|f| f.component == new && matches!(f.kind, solvecraft_doc::FeatureKind::Move { .. })).count(), 1); + near(solidcraft_kernel::measure(&target.body).unwrap().centroid.y, 6.0); + assert_eq!(s.doc.features.iter().filter(|f| f.component == new && matches!(f.kind, solidcraft_doc::FeatureKind::Move { .. })).count(), 1); } #[test] @@ -233,15 +233,15 @@ fn configuration_rows_follow_copied_parameters_material_and_strict_face_lineage( let part = component(&mut s, "Configured"); let source = box2(&mut s); let length = s.doc.feature(source).unwrap().param_names[0].clone(); - let target = solvecraft_doc::config::ConfigFaceTarget { + let target = solidcraft_doc::config::ConfigFaceTarget { body: "Cube".into(), origin_feature: source, name: format!("F{source}:+z"), - point: solvecraft_geom::Vec3::new(1.0, 1.0, 2.0), + point: solidcraft_geom::Vec3::new(1.0, 1.0, 2.0), certificate: None, }; run(&mut s, "config.configure", json!({"params":[length],"materials":["Cube"],"component_appearances":[part],"face_appearances":[target]})); - let face_key = s.doc.config_key(&solvecraft_doc::config::Column::FaceAppearance(target.clone())); + let face_key = s.doc.config_key(&solidcraft_doc::config::Column::FaceAppearance(target.clone())); run( &mut s, "config.row", @@ -271,26 +271,26 @@ fn configured_extrude_cap_copy_keeps_independent_semantic_lineage() { run(&mut s, "sketch.finish", json!({})); let feature = run(&mut s, "solid.extrude", json!({"distance":2}))["feature"].as_u64().unwrap(); let body_name = s.model.state().bodies.last().unwrap().name.clone(); - let target = solvecraft_doc::config::ConfigFaceTarget { + let target = solidcraft_doc::config::ConfigFaceTarget { body: body_name, origin_feature: feature, name: format!("F{feature}:top"), - point: solvecraft_geom::Vec3::new(1.0, 1.0, 2.0), + point: solidcraft_geom::Vec3::new(1.0, 1.0, 2.0), certificate: None, }; run(&mut s, "config.configure", json!({"face_appearances":[target]})); - let key = s.doc.config_key(&solvecraft_doc::config::Column::FaceAppearance(target)); + let key = s.doc.config_key(&solidcraft_doc::config::Column::FaceAppearance(target)); run(&mut s, "config.row", json!({"name":"Painted","values":{key:{"name":"Custom","color":[0,255,0],"opacity":1.0}}})); let copied = copy(&mut s, part)["component"].as_u64().unwrap(); run(&mut s, "config.activate", json!({"row":"Painted"})); let state = s.model.state(); let body = state.bodies.iter().find(|b| b.component == copied).unwrap(); - assert!(solvecraft_doc::naming::face_names(body).contains(&format!("F{}:top", body.feature))); + assert!(solidcraft_doc::naming::face_names(body).contains(&format!("F{}:top", body.feature))); assert!(s.doc.face_colors(body).iter().any(|(_, look)| look.color == [0, 255, 0])); assert!(s.doc.configuration_style_status(&state).is_ok()); let saved = s.doc.to_json(); - let reopened = solvecraft_doc::Document::from_json(&saved).unwrap(); - let mut model = solvecraft_doc::Model::default(); + let reopened = solidcraft_doc::Document::from_json(&saved).unwrap(); + let mut model = solidcraft_doc::Model::default(); model.evaluate(&reopened); assert!(reopened.configuration_style_status(&model.state()).is_ok()); } @@ -310,8 +310,8 @@ fn selected_second_occurrence_emboss_copy_remaps_target_and_author_paths_and_reo let copied = run(&mut s, "component.paste_new", json!({"component":part,"occurrence":second,"path":[second],"translate":[20,0,0]})); let new = copied["component"].as_u64().unwrap(); let placement = copied["occurrence"].as_u64().unwrap(); - let emboss = s.doc.features.iter().find(|f| f.component == new && matches!(f.kind, solvecraft_doc::FeatureKind::Emboss { .. })).unwrap(); - let solvecraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &emboss.kind else { panic!("missing selected face") }; + let emboss = s.doc.features.iter().find(|f| f.component == new && matches!(f.kind, solidcraft_doc::FeatureKind::Emboss { .. })).unwrap(); + let solidcraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &emboss.kind else { panic!("missing selected face") }; assert_eq!(face.body, "Sleeve (copy)"); assert_eq!(face.path, vec![placement]); assert_eq!(face.author_path, vec![placement]); @@ -321,7 +321,7 @@ fn selected_second_occurrence_emboss_copy_remaps_target_and_author_paths_and_reo run(&mut s, "parameters.change", json!({"name":height,"expression":"30 mm"})); near(volume(&mut s, new), clone_volume); assert!(volume(&mut s, part) > original_volume); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert!(reopened.doc.features.iter().all(|f| reopened.model.result(f.id).is_some_and(|r| r.error.is_none()))); near(volume(&mut reopened, new), clone_volume); @@ -339,7 +339,7 @@ fn reassigned_sample_plate_paste_new_preserves_complex_frozen_geometry() { run(&mut s, "occurrence.ground", json!({"occurrence":occurrence,"grounded":true})); let original_volume = volume(&mut s, part); let source_mesh = s.model.state().body(&body).unwrap().mesh(); - let boss_probe = solvecraft_geom::Vec3::new(40.0, 35.0, 12.0); + let boss_probe = solidcraft_geom::Vec3::new(40.0, 35.0, 12.0); let source_boss = source_mesh.contains(boss_probe); let copied = run(&mut s, "component.paste_new", json!({"component":part,"translate":[10,10,0]})); assert_eq!(s.doc.components.len(), 2); @@ -354,11 +354,11 @@ fn reassigned_sample_plate_paste_new_preserves_complex_frozen_geometry() { for (point, expected) in [([5.0, 25.0, 4.0], true), ([12.0, 25.0, 4.0], false), ([68.0, 25.0, 4.0], false), ([40.0, 25.0, 12.0], false), ([0.0, 0.0, 4.0], false)] { - assert_eq!(mesh.contains(solvecraft_geom::Vec3::new(point[0], point[1], point[2])), expected, "{point:?}"); + assert_eq!(mesh.contains(solidcraft_geom::Vec3::new(point[0], point[1], point[2])), expected, "{point:?}"); } assert_eq!(mesh.contains(boss_probe), source_boss); assert_eq!(state.body(&body).unwrap().mesh().contains(boss_probe), source_boss); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert!((volume(&mut reopened, new) - original_volume).abs() < 1e-5 * original_volume); } @@ -375,7 +375,7 @@ fn generated_copy_parameters_cannot_heal_an_unrelated_missing_reference() { let missing = probe.new_model_param("2 mm", "mm"); let feature = s.doc_mut().feature_mut(broken).unwrap(); feature.component = 0; - if let solvecraft_doc::FeatureKind::Box { width, .. } = &mut feature.kind { + if let solidcraft_doc::FeatureKind::Box { width, .. } = &mut feature.kind { *width = missing; } s.refresh(); @@ -418,7 +418,7 @@ fn external_transfer_entry_and_internal_rotated_transfer_clone_once() { assert!(before.min.dist(after.min) < 1e-6); assert!(before.max.dist(after.max) < 1e-6); assert!(body.transferred); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert!(reopened.model.state().body("Foreign (copy)").unwrap().mesh().bounds().min.dist(before.min) < 1e-6); } @@ -433,7 +433,7 @@ fn suppressed_external_entry_does_not_supply_clone_baseline() { let skipped = s .doc_mut() .add_feature( - solvecraft_doc::FeatureKind::BodyTransfer { bodies: vec!["Foreign".into()], from: 0, to: part, transform: solvecraft_doc::IDENTITY }, + solidcraft_doc::FeatureKind::BodyTransfer { bodies: vec!["Foreign".into()], from: 0, to: part, transform: solidcraft_doc::IDENTITY }, None, ) .unwrap(); @@ -454,13 +454,13 @@ fn transferred_frozen_face_paint_ignores_consumed_legacy_offset() { run(&mut s, "appearance.assign", json!({"body":"Foreign","faces":[[1,1,2]],"color":"#00ff00"})); s.doc_mut().body_offsets.insert( "Foreign".into(), - solvecraft_doc::rigid(solvecraft_geom::Vec3::new(7.0, 0.0, 0.0), solvecraft_geom::Vec3::ZERO, solvecraft_geom::Vec3::Z, 0.0), + solidcraft_doc::rigid(solidcraft_geom::Vec3::new(7.0, 0.0, 0.0), solidcraft_geom::Vec3::ZERO, solidcraft_geom::Vec3::Z, 0.0), ); s.refresh(); let part = component(&mut s, "Part"); run(&mut s, "component.move_bodies", json!({"component":part,"bodies":["Foreign"]})); let original = s.doc.appearances.faces.iter().find(|face| face.body == "Foreign").unwrap(); - let expected = solvecraft_doc::naming::point_on_face(&s.model.state(), original.name.as_ref().unwrap(), original.point).unwrap().0; + let expected = solidcraft_doc::naming::point_on_face(&s.model.state(), original.name.as_ref().unwrap(), original.point).unwrap().0; copy(&mut s, part); let paint = s.doc.appearances.faces.iter().find(|face| face.body == "Foreign (copy)").unwrap(); assert!(paint.point.dist(expected) < 1e-6, "{:?} versus {expected:?}", paint.point); @@ -468,18 +468,18 @@ fn transferred_frozen_face_paint_ignores_consumed_legacy_offset() { #[test] fn boundary_material_proof_requires_resolvable_stock_and_void() { - use solvecraft_geom::Vec3; - let a = solvecraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap().tessellate(1e-3).unwrap(); - let b = solvecraft_kernel::box_solid(Vec3::new(1e-5, 0., 0.), Vec3::new(10.00001, 10., 10.)).unwrap().tessellate(1e-3).unwrap(); + use solidcraft_geom::Vec3; + let a = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 10.)).unwrap().tessellate(1e-3).unwrap(); + let b = solidcraft_kernel::box_solid(Vec3::new(1e-5, 0., 0.), Vec3::new(10.00001, 10., 10.)).unwrap().tessellate(1e-3).unwrap(); let p = Vec3::new(5e-6, 5., 5.); assert_ne!(a.contains(p), b.contains(p)); assert!((super::mesh_distance(&a, p) - 5e-6).abs() < 1e-12); assert!((super::mesh_distance(&b, p) - 5e-6).abs() < 1e-12); assert!(super::material_probe_matches(&a, &b, p, 1e-3, 1e-3)); - let shifted = solvecraft_kernel::box_solid(Vec3::new(1., 0., 0.), Vec3::new(11., 10., 10.)).unwrap().tessellate(1e-3).unwrap(); + let shifted = solidcraft_kernel::box_solid(Vec3::new(1., 0., 0.), Vec3::new(11., 10., 10.)).unwrap().tessellate(1e-3).unwrap(); assert!(!super::material_probe_matches(&a, &shifted, p, 1e-3, 1e-3)); - let thin = solvecraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 1e-4)).unwrap().tessellate(1e-6).unwrap(); - let thin_shifted = solvecraft_kernel::box_solid(Vec3::new(0., 0., 1e-4), Vec3::new(10., 10., 2e-4)).unwrap().tessellate(1e-6).unwrap(); + let thin = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(10., 10., 1e-4)).unwrap().tessellate(1e-6).unwrap(); + let thin_shifted = solidcraft_kernel::box_solid(Vec3::new(0., 0., 1e-4), Vec3::new(10., 10., 2e-4)).unwrap().tessellate(1e-6).unwrap(); // Every resolvable replacement is void: thin ambiguous material is refused. assert!(!super::material_probe_matches(&thin, &thin_shifted, Vec3::new(5., 5., 5e-5), 1e-3, 1e-3)); assert!(!super::material_probe_matches(&a, &b, p, f64::INFINITY, 1e-3)); @@ -487,7 +487,7 @@ fn boundary_material_proof_requires_resolvable_stock_and_void() { #[test] fn frozen_bore_boundary_copy_preserves_stock_void_and_reopen() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let bore = |center: f64| { let mut s = Session::default(); run(&mut s, "solid.box", json!({"length":30,"width":20,"height":10,"body_name":"Drilled"})); @@ -500,7 +500,7 @@ fn frozen_bore_boundary_copy_preserves_stock_void_and_reopen() { // cardinal angle, so its distance is bounded by the requested sag tolerance. assert!(super::mesh_distance(&source, Vec3::new(6., 10., 5.)) <= 1e-3); let shifted = bore(11.).model.state().body("Drilled").unwrap().body.tessellate(1e-3).unwrap(); - let lost = solvecraft_kernel::box_solid(Vec3::ZERO, Vec3::new(30., 20., 10.)).unwrap().tessellate(1e-3).unwrap(); + let lost = solidcraft_kernel::box_solid(Vec3::ZERO, Vec3::new(30., 20., 10.)).unwrap().tessellate(1e-3).unwrap(); assert!(!super::material_probe_matches(&source, &shifted, Vec3::new(6.5, 10., 5.), 1e-3, 1e-3)); assert!(!super::material_probe_matches(&source, &lost, Vec3::new(10., 10., 5.), 1e-3, 1e-3)); let created = run(&mut s, "component.from_bodies", json!({"bodies":["Drilled"]})); @@ -517,17 +517,17 @@ fn frozen_bore_boundary_copy_preserves_stock_void_and_reopen() { } }; check(&s); - check(&Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap())); + check(&Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap())); } #[test] fn frozen_r6_bore_copy_uses_current_geometry_despite_mesh_measure_bias() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let mut s = Session::default(); run(&mut s, "solid.box", json!({"length":20,"width":20,"height":10,"body_name":"Drilled"})); run(&mut s, "solid.cylinder", json!({"base":[10,10,0],"radius":6,"height":10,"operation":"cut"})); let source = s.model.state().body("Drilled").unwrap().body.clone(); - let original = solvecraft_kernel::step_export(&[&source], "source").unwrap(); + let original = solidcraft_kernel::step_export(&[&source], "source").unwrap(); let created = run(&mut s, "component.from_bodies", json!({"bodies":["Drilled"]})); let part = created["components"][0]["component"].as_u64().unwrap(); let before = s.doc.to_json(); @@ -535,7 +535,7 @@ fn frozen_r6_bore_copy_uses_current_geometry_despite_mesh_measure_bias() { let check = |session: &Session| { let state = session.model.state(); let body = state.bodies.iter().find(|b| session.doc.model_body_component(b) == copied).unwrap(); - assert_eq!(solvecraft_kernel::certified_prism_equivalent(&source, &body.body).unwrap(), Some(true)); + assert_eq!(solidcraft_kernel::certified_prism_equivalent(&source, &body.body).unwrap(), Some(true)); let mesh = body.body.tessellate(1e-3).unwrap(); for (point, material) in [(Vec3::new(3., 10., 5.), true), (Vec3::new(5., 10., 5.), false), (Vec3::new(10., 10., 5.), false), (Vec3::new(18., 10., 5.), true)] @@ -543,10 +543,10 @@ fn frozen_r6_bore_copy_uses_current_geometry_despite_mesh_measure_bias() { assert_eq!(mesh.contains(point), material, "{point:?}"); } let original_body = state.body("Drilled").unwrap(); - assert_eq!(solvecraft_kernel::step_export(&[&original_body.body], "source").unwrap(), original); + assert_eq!(solidcraft_kernel::step_export(&[&original_body.body], "source").unwrap(), original); }; check(&s); - check(&Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap())); + check(&Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap())); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), before); run(&mut s, "edit.redo", json!({})); diff --git a/crates/engine/src/cmd/component_properties.rs b/crates/engine/src/cmd/component_properties.rs index 3d70262a..d26e8900 100644 --- a/crates/engine/src/cmd/component_properties.rs +++ b/crates/engine/src/cmd/component_properties.rs @@ -1,7 +1,7 @@ //! Physical properties of an assembly placement, with original body material assignments. use serde_json::{Value, json}; -use solvecraft_doc::{Document, Mat}; -use solvecraft_geom::Vec3; +use solidcraft_doc::{Document, Mat}; +use solidcraft_geom::Vec3; use crate::params::bad; use crate::{Result, Session}; @@ -27,8 +27,8 @@ fn placed_doc(s: &Session) -> Document { /// A property total must cover the entire assembly. The display traversal intentionally /// truncates hostile designs, so use an explicit error instead of accepting its prefix. pub(in crate::cmd) fn placements(doc: &Document, cmd: &str) -> Result, Mat)>> { - let mut out = vec![(0, Vec::new(), solvecraft_doc::IDENTITY)]; - let mut stack = vec![(0, Vec::::new(), solvecraft_doc::IDENTITY)]; + let mut out = vec![(0, Vec::new(), solidcraft_doc::IDENTITY)]; + let mut stack = vec![(0, Vec::::new(), solidcraft_doc::IDENTITY)]; let mut children: std::collections::BTreeMap> = std::collections::BTreeMap::new(); for occurrence in &doc.occurrences { children.entry(occurrence.parent).or_default().push(occurrence); @@ -40,7 +40,7 @@ pub(in crate::cmd) fn placements(doc: &Document, cmd: &str) -> Result Result { for occurrence in &doc.occurrences { children.entry(occurrence.parent).or_default().push(occurrence); } - let mut frames = BTreeMap::from([(0, Some((Vec::new(), solvecraft_doc::IDENTITY)))]); - let mut stack = vec![(0, Vec::new(), solvecraft_doc::IDENTITY)]; + let mut frames = BTreeMap::from([(0, Some((Vec::new(), solidcraft_doc::IDENTITY)))]); + let mut stack = vec![(0, Vec::new(), solidcraft_doc::IDENTITY)]; let mut work = 0usize; while let Some((component, path, matrix)) = stack.pop() { for occurrence in children.get(&component).into_iter().flatten() { @@ -21,7 +21,7 @@ fn unique_frames(s: &Session, cmd: &str) -> Result { } let mut child_path = path.clone(); child_path.push(occurrence.id); - let frame = solvecraft_doc::mat_mul(&matrix, &occurrence.transform); + let frame = solidcraft_doc::mat_mul(&matrix, &occurrence.transform); frames.entry(occurrence.component).and_modify(|entry| *entry = None).or_insert_with(|| Some((child_path.clone(), frame))); stack.push((occurrence.component, child_path, frame)); } @@ -61,16 +61,16 @@ fn transfer(s: &mut Session, name: &str, to: u64, frames: &UniqueFrames, cmd: &s }; let source = unique(from)?; let destination = unique(to)?; - let old = doc.model_body_offset(body).map(|offset| solvecraft_doc::mat_mul(&source.1, offset)).unwrap_or(source.1); - let inv = solvecraft_doc::mat_inverse(&destination.1).ok_or_else(|| bad(cmd, "destination frame is not invertible"))?; - let transform = solvecraft_doc::mat_mul(&inv, &old); - if !solvecraft_doc::valid_transfer_matrix(&transform) { + let old = doc.model_body_offset(body).map(|offset| solidcraft_doc::mat_mul(&source.1, offset)).unwrap_or(source.1); + let inv = solidcraft_doc::mat_inverse(&destination.1).ok_or_else(|| bad(cmd, "destination frame is not invertible"))?; + let transform = solidcraft_doc::mat_mul(&inv, &old); + if !solidcraft_doc::valid_transfer_matrix(&transform) { return Err(bad(cmd, "body transfer exceeds supported rigid coordinate range")); } let inherited = (!s.doc.appearances.bodies.contains_key(name)).then(|| s.doc.appearances.components.get(&from).cloned()).flatten(); let paths: BTreeSet = source.0.iter().chain(&destination.0).copied().collect(); let captures: Vec<_> = paths.into_iter().filter_map(|id| s.pending_moves.get(&id).map(|transform| (id, *transform))).collect(); - if captures.iter().any(|(_, transform)| !solvecraft_doc::valid_transfer_matrix(transform)) { + if captures.iter().any(|(_, transform)| !solidcraft_doc::valid_transfer_matrix(transform)) { return Err(bad(cmd, "pending transfer position is not a supported rigid transform")); } drop(doc); diff --git a/crates/engine/src/cmd/component_transfer_tests.rs b/crates/engine/src/cmd/component_transfer_tests.rs index 9a6fd2e8..052db71f 100644 --- a/crates/engine/src/cmd/component_transfer_tests.rs +++ b/crates/engine/src/cmd/component_transfer_tests.rs @@ -10,7 +10,7 @@ fn recorded_assignment_preserves_sample_joined_material_and_timeline() { s.run_script(&crate::sample::script()).unwrap(); let before = s.model.state(); assert_eq!(before.bodies.len(), 1); - let volume = solvecraft_kernel::measure(&before.body("Plate").unwrap().body).unwrap().volume; + let volume = solidcraft_kernel::measure(&before.body("Plate").unwrap().body).unwrap().volume; let created = run(&mut s, "component.from_bodies", json!({"bodies":["Plate"]})); let to = created["components"][0]["component"].as_u64().unwrap(); let feature = created["components"][0]["feature"].as_u64().unwrap(); @@ -19,7 +19,7 @@ fn recorded_assignment_preserves_sample_joined_material_and_timeline() { let body = after.body("Plate").unwrap(); assert_eq!(body.component, to); assert!(body.transferred); - assert!((solvecraft_kernel::measure(&body.body).unwrap().volume - volume).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&body.body).unwrap().volume - volume).abs() < 1e-6); assert!(body.mesh().contains(Vec3::new(40.0, 35.0, 12.0))); let index = s.doc.feature_index(feature).unwrap(); s.doc_mut().marker = Some(index); @@ -44,12 +44,12 @@ fn transformed_pending_destination_repeated_transfer_preserves_world_pose() { assert!(bounds.min.dist(Vec3::ZERO) < 1e-6); assert!(bounds.max.dist(Vec3::new(10.0, 10.0, 10.0)) < 1e-6); assert!(!s.pending_moves.contains_key(&occurrence)); - let mut destination_reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut destination_reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; destination_reopened.refresh(); assert!(destination_reopened.world_state().body("Block").unwrap().mesh().bounds().min.dist(Vec3::ZERO) < 1e-6); run(&mut s, "component.move_bodies", json!({"component":0,"bodies":["Block"]})); assert!(s.world_state().body("Block").unwrap().mesh().bounds().min.dist(Vec3::ZERO) < 1e-6); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert!(reopened.world_state().body("Block").unwrap().mesh().bounds().min.dist(Vec3::ZERO) < 1e-6); } @@ -75,7 +75,7 @@ fn historical_join_cut_and_new_destination_modeling_keep_material() { run(&mut s, "solid.box", json!({"corner":[2,2,0],"length":2,"width":2,"height":10,"operation":"cut"})); let original = s.model.state(); assert_eq!(original.bodies.len(), 1); - let v = solvecraft_kernel::measure(&original.body("Block").unwrap().body).unwrap().volume; + let v = solidcraft_kernel::measure(&original.body("Block").unwrap().body).unwrap().volume; assert!((v - 1760.0).abs() < 1e-6); run(&mut s, "material.assign", json!({"bodies":["Block"],"material":"Brass"})); run(&mut s, "appearance.assign", json!({"bodies":["Block"],"color":"#bc8537"})); @@ -87,15 +87,15 @@ fn historical_join_cut_and_new_destination_modeling_keep_material() { run(&mut s, "solid.box", json!({"corner":[18,0,0],"length":2,"width":10,"height":10,"operation":"join"})); assert_eq!(s.model.state().bodies.len(), 1); assert_eq!(s.model.state().body("Block").unwrap().component, c); - let final_volume = solvecraft_kernel::measure(&s.model.state().body("Block").unwrap().body).unwrap().volume; + let final_volume = solidcraft_kernel::measure(&s.model.state().body("Block").unwrap().body).unwrap().volume; assert!((final_volume - 1960.0).abs() < 1e-6); run(&mut s, "component.move_bodies", json!({"component":0,"bodies":["Block"]})); assert_eq!(s.model.state().body("Block").unwrap().component, 0); run(&mut s, "edit.undo", json!({})); assert_eq!(s.model.state().body("Block").unwrap().component, c); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); - assert!((solvecraft_kernel::measure(&reopened.model.state().body("Block").unwrap().body).unwrap().volume - final_volume).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&reopened.model.state().body("Block").unwrap().body).unwrap().volume - final_volume).abs() < 1e-6); } #[test] @@ -107,7 +107,7 @@ fn ownership_only_boundary_invalidates_old_boolean_cache() { let c = s.doc_mut().add_component(Some("Destination"), 0).unwrap(); let id = s .doc_mut() - .add_feature(FeatureKind::BodyTransfer { bodies: vec!["Block".into()], from: 0, to: c, transform: solvecraft_doc::IDENTITY }, None) + .add_feature(FeatureKind::BodyTransfer { bodies: vec!["Block".into()], from: 0, to: c, transform: solidcraft_doc::IDENTITY }, None) .unwrap(); let index = s.doc.feature_index(id).unwrap(); let transfer = s.doc_mut().features.remove(index); @@ -115,7 +115,7 @@ fn ownership_only_boundary_invalidates_old_boolean_cache() { s.refresh(); assert_eq!(s.model.state().bodies.len(), 2); assert_eq!(s.model.state().body("Block").unwrap().component, c); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert_eq!(reopened.model.state().bodies.len(), 2); assert_eq!(reopened.model.state().body("Block").unwrap().component, c); @@ -128,7 +128,7 @@ fn failed_transfer_cannot_change_evaluated_world_owner() { let c = s.doc_mut().add_component(Some("Destination"), 0).unwrap(); let id = s .doc_mut() - .add_feature(FeatureKind::BodyTransfer { bodies: vec!["Block".into()], from: c, to: c, transform: solvecraft_doc::IDENTITY }, None) + .add_feature(FeatureKind::BodyTransfer { bodies: vec!["Block".into()], from: c, to: c, transform: solidcraft_doc::IDENTITY }, None) .unwrap(); s.refresh(); assert!(s.model.result(id).unwrap().error.is_some()); @@ -149,7 +149,7 @@ fn transferred_thread_follows_body_frame_without_changing_world_axis() { let after = s.world_state().threads[0].clone(); assert!(after.axis_point.dist(before.axis_point) < 1e-6); assert!(after.axis.dist(before.axis) < 1e-6); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert!(reopened.world_state().threads[0].axis.dist(before.axis) < 1e-6); } @@ -159,7 +159,7 @@ fn inherited_component_appearance_and_legacy_assignment_survive_transfer() { let mut s = Session::default(); let source = run(&mut s, "component.create", json!({"name":"Source"}))["component"].as_u64().unwrap(); run(&mut s, "solid.box", json!({"length":10,"width":10,"height":10,"body_name":"Block"})); - s.doc_mut().appearances.components.insert(source, solvecraft_doc::appearance::Look { name: "Green".into(), color: [0, 150, 0], opacity: 0.7 }); + s.doc_mut().appearances.components.insert(source, solidcraft_doc::appearance::Look { name: "Green".into(), color: [0, 150, 0], opacity: 0.7 }); s.refresh(); let look = s.doc.model_body_look(s.model.state().body("Block").unwrap()).unwrap(); run(&mut s, "component.move_bodies", json!({"component":0,"bodies":["Block"]})); @@ -169,12 +169,12 @@ fn inherited_component_appearance_and_legacy_assignment_survive_transfer() { run(&mut legacy, "solid.box", json!({"length":10,"width":10,"height":10,"body_name":"Old"})); let c = legacy.doc_mut().add_component(Some("Legacy"), 0).unwrap(); legacy.doc_mut().body_components.insert("Old".into(), c); - legacy.doc_mut().body_offsets.insert("Old".into(), solvecraft_doc::rigid(Vec3::new(7.0, 0.0, 0.0), Vec3::ZERO, Vec3::Z, 0.0)); + legacy.doc_mut().body_offsets.insert("Old".into(), solidcraft_doc::rigid(Vec3::new(7.0, 0.0, 0.0), Vec3::ZERO, Vec3::Z, 0.0)); legacy.refresh(); assert!(!legacy.model.state().body("Old").unwrap().transferred); run(&mut legacy, "component.move_bodies", json!({"component":0,"bodies":["Old"]})); assert!((legacy.world_state().body("Old").unwrap().mesh().bounds().min.x - 7.0).abs() < 1e-6); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&legacy.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&legacy.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert!((reopened.world_state().body("Old").unwrap().mesh().bounds().min.x - 7.0).abs() < 1e-6); } @@ -190,7 +190,7 @@ fn deleting_external_transfer_source_refuses_without_dangling_archive() { let document = s.doc.to_json(); assert!(s.execute("component.delete", &json!({"component":a})).is_err()); assert_eq!(s.doc.to_json(), document); - assert!(solvecraft_doc::Document::from_json(&document).is_ok()); + assert!(solidcraft_doc::Document::from_json(&document).is_ok()); } #[test] @@ -204,7 +204,7 @@ fn earlier_selected_cylinder_emboss_keeps_source_frame_after_transfer_and_edit() run(&mut s, "sketch.rectangle.two_point", json!({"p0":[-2,5],"p1":[2,10]})); run(&mut s, "sketch.finish", json!({})); let emboss = run(&mut s, "solid.emboss", json!({"sketch":"Badge","face":[30,10,7.5],"depth":1}))["feature"].as_u64().unwrap(); - let before = solvecraft_kernel::measure(&s.world_state().body("Sleeve").unwrap().body).unwrap().volume; + let before = solidcraft_kernel::measure(&s.world_state().body("Sleeve").unwrap().body).unwrap().volume; run(&mut s, "component.activate", json!({"component":0})); let destination = run(&mut s, "component.create", json!({"name":"Destination"})); let to = destination["component"].as_u64().unwrap(); @@ -212,13 +212,13 @@ fn earlier_selected_cylinder_emboss_keeps_source_frame_after_transfer_and_edit() run(&mut s, "occurrence.move", json!({"occurrence":destination_occurrence,"translate":[0,40,0],"axis":[1,0,0],"angle":90,"capture":true})); run(&mut s, "component.move_bodies", json!({"component":to,"bodies":["Sleeve"]})); assert!(s.doc.features.iter().all(|feature| s.model.result(feature.id).is_some_and(|result| result.error.is_none()))); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert!(reopened.doc.features.iter().all(|feature| reopened.model.result(feature.id).is_some_and(|result| result.error.is_none()))); - assert!((solvecraft_kernel::measure(&reopened.world_state().body("Sleeve").unwrap().body).unwrap().volume - before).abs() < 1e-5); + assert!((solidcraft_kernel::measure(&reopened.world_state().body("Sleeve").unwrap().body).unwrap().volume - before).abs() < 1e-5); let depth = reopened.doc.feature(emboss).unwrap().param_names[0].clone(); run(&mut reopened, "parameters.change", json!({"name":depth,"expression":"2 mm"})); - assert!(solvecraft_kernel::measure(&reopened.world_state().body("Sleeve").unwrap().body).unwrap().volume > before); + assert!(solidcraft_kernel::measure(&reopened.world_state().body("Sleeve").unwrap().body).unwrap().volume > before); } #[test] @@ -280,7 +280,7 @@ fn edited_transfer_destination_updates_history_owner_copy_and_reopen() { assert_eq!(s.pending_moves, pending); let copied = run(&mut s, "component.paste_new", json!({"component":b}))["component"].as_u64().unwrap(); assert_eq!(s.model.state().body("Block (copy)").unwrap().component, copied); - let mut reopened = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; + let mut reopened = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Session::default() }; reopened.refresh(); assert_eq!(reopened.model.state().body("Block").unwrap().component, b); assert_eq!(reopened.model.state().body("Block (copy)").unwrap().component, copied); diff --git a/crates/engine/src/cmd/config.rs b/crates/engine/src/cmd/config.rs index 171cb20f..56e373f4 100644 --- a/crates/engine/src/cmd/config.rs +++ b/crates/engine/src/cmd/config.rs @@ -2,7 +2,7 @@ //! suppressions) and switching between its rows. use serde_json::{Value, json}; -use solvecraft_doc::config::{Column, ConfigRow, MAX_COLUMNS, MAX_ROWS}; +use solidcraft_doc::config::{Column, ConfigRow, MAX_COLUMNS, MAX_ROWS}; use super::CommandSpec; use crate::params::{bad, bool_, str_, string_list}; @@ -64,8 +64,8 @@ fn column_of(s: &Session, p: &Value, cmd: &str) -> Result { .ok_or_else(|| bad(cmd, "no face column")); } if let Some(target) = value.get("target") { - solvecraft_doc::config::validate_face_target_json(target).map_err(|error| bad(cmd, error.to_string()))?; - let target: solvecraft_doc::config::ConfigFaceTarget = + solidcraft_doc::config::validate_face_target_json(target).map_err(|error| bad(cmd, error.to_string()))?; + let target: solidcraft_doc::config::ConfigFaceTarget = serde_json::from_value(target.clone()).map_err(|error| bad(cmd, error.to_string()))?; let state = s.model.state(); let body = state.body(&target.body).ok_or_else(|| bad(cmd, "no face definition"))?; @@ -79,7 +79,7 @@ fn column_of(s: &Session, p: &Value, cmd: &str) -> Result { if body.body.is_mesh() { return Err(bad(cmd, "mesh faces have no supported semantic lineage")); } - let index = solvecraft_doc::appearance::face_index_at(body, point).ok_or_else(|| bad(cmd, "no face at picked point"))?; + let index = solidcraft_doc::appearance::face_index_at(body, point).ok_or_else(|| bad(cmd, "no face at picked point"))?; let target = s.doc.configuration_face_target(body, index, point).map_err(|error| bad(cmd, error.to_string()))?; s.doc.certify_config_face(&target).map_err(|error| bad(cmd, error.to_string()))?; s.doc.configuration_face_indices(body, &target).map_err(|error| bad(cmd, error.to_string()))?; @@ -265,10 +265,10 @@ fn row(s: &mut Session, p: &Value) -> Result { .ok_or_else(|| bad(cmd, format!("`{k}` is not a column (config.column adds one)")))?; match s.doc.configs.columns.get(col) { Some(Column::Material(_)) => { - text = solvecraft_doc::config::material_cell(&text).map_err(|e| bad(cmd, e.to_string()))?.unwrap_or_else(|| "Default".into()); + text = solidcraft_doc::config::material_cell(&text).map_err(|e| bad(cmd, e.to_string()))?.unwrap_or_else(|| "Default".into()); } Some(Column::Appearance(_) | Column::ComponentAppearance(_) | Column::FaceAppearance(_)) => { - text = match solvecraft_doc::config::appearance_cell(&text).map_err(|e| bad(cmd, e.to_string()))? { + text = match solidcraft_doc::config::appearance_cell(&text).map_err(|e| bad(cmd, e.to_string()))? { Some(look) => serde_json::to_string(&look).map_err(|e| bad(cmd, e.to_string()))?, None => String::new(), }; @@ -329,8 +329,8 @@ fn validate_rows(s: &Session, cmd: &str) -> Result<()> { fn rule(s: &mut Session, p: &Value) -> Result { let cmd = "config.rule"; if let Some(value) = p.get("rule") { - solvecraft_doc::config::validate_rules_json(&json!({"configs":{"rules":[value]}})).map_err(|e| bad(cmd, e.to_string()))?; - let mut rule: solvecraft_doc::config::ConfigRule = serde_json::from_value(value.clone()).map_err(|e| bad(cmd, e.to_string()))?; + solidcraft_doc::config::validate_rules_json(&json!({"configs":{"rules":[value]}})).map_err(|e| bad(cmd, e.to_string()))?; + let mut rule: solidcraft_doc::config::ConfigRule = serde_json::from_value(value.clone()).map_err(|e| bad(cmd, e.to_string()))?; if rule.length_unit.is_none() { rule.length_unit = s .doc @@ -345,7 +345,7 @@ fn rule(s: &mut Session, p: &Value) -> Result { if let Some(index) = rules.iter().position(|r| r.id == rule.id) { rules[index] = rule; } else { - if rules.len() >= solvecraft_doc::config::MAX_RULES { + if rules.len() >= solidcraft_doc::config::MAX_RULES { return Err(bad(cmd, "too many rules")); } rules.push(rule); @@ -541,7 +541,7 @@ mod tests { run(&mut s, "config.rule", json!({"id":"limit","delete":true})); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc, before); - let reopened = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let reopened = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); assert_eq!(reopened.configs.rules, s.doc.configs.rules); } @@ -580,7 +580,7 @@ mod tests { assert!((volume(&mut s) - 1000.0).abs() < 1e-6, "captured Default preserves geometry in an inch design"); run(&mut s, "config.row", json!({"name":"Inches", "values":{length.clone():"2", format!("suppress:{feature}"):"NO"}})); run(&mut s, "config.activate", json!({"row":"Inches"})); - assert_eq!(s.doc.config_current(&solvecraft_doc::config::Column::Param(length.clone())).as_deref(), Some("2 in")); + assert_eq!(s.doc.config_current(&solidcraft_doc::config::Column::Param(length.clone())).as_deref(), Some("2 in")); assert!(s.doc.configuration_matches()); assert!((volume(&mut s) - 5080.0).abs() < 1e-6); let feature_id = s.doc.features[0].id; @@ -682,7 +682,7 @@ mod tests { let mass = run(&mut s, "inspect.measure", json!({})); assert!((mass["bodies"][0]["mass_g"].as_f64().unwrap() - 2.7).abs() < 1e-6); assert_eq!(s.doc.appearances.bodies.get(&body).unwrap().opacity, 0.4); - let reopened = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let reopened = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); assert_eq!(reopened, *s.doc); assert!(reopened.configuration_matches()); run(&mut s, "edit.undo", json!({})); @@ -740,7 +740,7 @@ mod tests { run(&mut s, "config.activate", json!({"row": "Default"})); assert_eq!(s.doc.appearances.bodies.get(&body).unwrap().name, name); assert!(s.doc.configuration_matches()); - let reopened = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let reopened = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); assert!(reopened.configuration_matches()); } @@ -775,7 +775,7 @@ mod tests { run(&mut s, "edit.undo", json!({})); assert_eq!(run(&mut s, "config.table", json!({}))["columns"][2], "material:Block"); run(&mut s, "edit.redo", json!({})); - let mut reopened = Session::new(solvecraft_doc::format::read(&s.doc.to_json()).unwrap()); + let mut reopened = Session::new(solidcraft_doc::format::read(&s.doc.to_json()).unwrap()); run(&mut reopened, "config.activate", json!({"row": "Default"})); assert!(!reopened.doc.materials.contains_key("Housing")); assert!(!reopened.doc.appearances.bodies.contains_key("Housing")); diff --git a/crates/engine/src/cmd/config_certificate_tests.rs b/crates/engine/src/cmd/config_certificate_tests.rs index 0bc25927..ec0eceb6 100644 --- a/crates/engine/src/cmd/config_certificate_tests.rs +++ b/crates/engine/src/cmd/config_certificate_tests.rs @@ -1,10 +1,10 @@ use crate::Session; use serde_json::{Value, json}; -use solvecraft_doc::{ +use solidcraft_doc::{ FeatureKind, config::{Column, FaceCertificate}, }; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; fn run(session: &mut Session, command: &str, params: Value) -> Value { session.execute(command, ¶ms).unwrap_or_else(|error| panic!("{command} {params}: {error}")) @@ -21,11 +21,11 @@ fn painted_radius(session: &Session, name: &str, radius: f64) -> usize { state.body(name).unwrap_or_else(|| panic!("missing {name}; bodies {:?}", state.bodies.iter().map(|body| &body.name).collect::>())); let painted = session.doc.face_colors(body); let mesh = body.body.tessellate(0.001).unwrap(); - let inverse = solvecraft_doc::mat_inverse(&body.source_transform).unwrap(); + let inverse = solidcraft_doc::mat_inverse(&body.source_transform).unwrap(); for (triangle, face) in mesh.triangles.iter().zip(&mesh.tri_face) { if painted.iter().any(|(index, _)| *index == *face as usize) { for vertex in mesh.tri(triangle).unwrap() { - let vertex = solvecraft_doc::apply_point(&inverse, vertex); + let vertex = solidcraft_doc::apply_point(&inverse, vertex); assert!((vertex.x.hypot(vertex.y) - radius).abs() < 1e-7, "{name}: {vertex:?}"); } } @@ -60,7 +60,7 @@ fn certified_annulus_side_rows_follow_radius_edits_and_clone() { assert_eq!(s.doc.configs.columns.len(), 2); run(&mut s, "config.activate", json!({"row":"Painted"})); assert_eq!(painted_radius(&s, "Ring (copy)", 12.), 2); - let reopened = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); + let reopened = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); let mut reopened = Session::new(reopened); run(&mut reopened, "config.activate", json!({"row":"Painted"})); assert_eq!(painted_radius(&reopened, "Ring", 12.), 2); @@ -83,19 +83,19 @@ fn imported(session: &mut Session, step: String, name: &str) -> u64 { #[test] fn legacy_v9_imported_emboss_anchor_and_ordinary_face_paint_replay_without_renaming() { let mut s = Session::default(); - let cylinder = solvecraft_kernel::cylinder(Vec3::ZERO, Vec3::Z, 10., 20.).unwrap(); - let step = solvecraft_kernel::step_export(&[&cylinder], "legacy anchored cylinder").unwrap(); + let cylinder = solidcraft_kernel::cylinder(Vec3::ZERO, Vec3::Z, 10., 20.).unwrap(); + let step = solidcraft_kernel::step_export(&[&cylinder], "legacy anchored cylinder").unwrap(); let source = imported(&mut s, step, "Sleeve"); run(&mut s, "material.assign", json!({"bodies":["Sleeve"],"material":"Steel"})); let before_volume = s.model.state().body("Sleeve").unwrap().body.tessellate_with(1e-4, true).unwrap().measure().volume; - let legacy_names = solvecraft_doc::naming::face_names(s.model.state().body("Sleeve").unwrap()); + let legacy_names = solidcraft_doc::naming::face_names(s.model.state().body("Sleeve").unwrap()); assert!(legacy_names.iter().all(|name| name.starts_with(&format!("F{source}:face:")))); run(&mut s, "appearance.assign", json!({"faces":[{"body":"Sleeve","point":[6,0,20]}],"appearance":"ABS - Blue"})); assert!(s.doc.appearances.faces[0].certificate.is_none()); let badge = run(&mut s, "sketch.create", json!({"plane":{"face":[0,0,20]},"name":"Badge","project_edges":false}))["sketch"].as_u64().unwrap(); let FeatureKind::Sketch { plane: saved_plane, .. } = &s.doc.feature(badge).unwrap().kind else { panic!() }; let saved_plane = saved_plane.clone(); - assert!(matches!(&saved_plane, solvecraft_doc::PlaneRef::Face { name: Some(name), .. } if legacy_names.contains(name))); + assert!(matches!(&saved_plane, solidcraft_doc::PlaneRef::Face { name: Some(name), .. } if legacy_names.contains(name))); run(&mut s, "sketch.rectangle.two_point", json!({"p0":[-2,-2],"p1":[2,2]})); run(&mut s, "sketch.finish", json!({})); run(&mut s, "solid.emboss", json!({"sketch":"Badge","profiles":"all","depth":1,"targets":["Sleeve"],"name":"Raised Badge"})); @@ -106,16 +106,16 @@ fn legacy_v9_imported_emboss_anchor_and_ordinary_face_paint_replay_without_renam assert!((volume - before_volume - 16.).abs() < 0.01, "addedvolume {} baseline {before_volume}", volume - before_volume); let paint = s.doc.appearances.faces.clone(); let mut legacy: Value = serde_json::from_str(&s.doc.to_json()).unwrap(); - legacy["format"] = json!("solvecraft/9"); - let reopened = Session::new(solvecraft_doc::Document::from_json(&legacy.to_string()).unwrap()); + legacy["format"] = json!("solidcraft/9"); + let reopened = Session::new(solidcraft_doc::Document::from_json(&legacy.to_string()).unwrap()); // Saved before format 14, it keeps naming the pieces it makes in the kernel's shell order. - let anchored = solvecraft_doc::Feature { shell_piece_order: true, ..anchored }; + let anchored = solidcraft_doc::Feature { shell_piece_order: true, ..anchored }; assert_eq!(reopened.doc.feature(anchored.id).unwrap(), &anchored); // Names saved before format 14 keep naming the face they named, marked so they are found as // the build that saved them found them. - let mark = |name: &Option| name.as_ref().map(|n| format!("{}{n}", solvecraft_doc::naming::LEGACY_MARK)); + let mark = |name: &Option| name.as_ref().map(|n| format!("{}{n}", solidcraft_doc::naming::LEGACY_MARK)); let mut saved_plane = saved_plane; - if let solvecraft_doc::PlaneRef::Face { name, .. } = &mut saved_plane { + if let solidcraft_doc::PlaneRef::Face { name, .. } = &mut saved_plane { *name = mark(name); } let paint: Vec<_> = paint @@ -173,10 +173,10 @@ fn legacy_v9_annulus_outer_side_emboss_keeps_its_original_heuristic_anchor() { run(&mut s, "sketch.circle.center", json!({"center":[0,0],"radius":9,"id":"inner"})); run(&mut s, "sketch.finish", json!({})); run(&mut s, "solid.extrude", json!({"distance":20,"profiles":[{"point":[9.5,0]}],"body_name":"Sleeve"})); - let names = solvecraft_doc::naming::face_names(s.model.state().body("Sleeve").unwrap()); + let names = solidcraft_doc::naming::face_names(s.model.state().body("Sleeve").unwrap()); // The outer wall is named after its own sketch curve: its history says so. (Builds before // format 14 named it after the curve nearest its centre, the inner circle; see below.) - let name = solvecraft_doc::naming::face_names_at(&s.model.state(), &[Vec3::new(0., 10., 7.5)]).remove(0); + let name = solidcraft_doc::naming::face_names_at(&s.model.state(), &[Vec3::new(0., 10., 7.5)]).remove(0); assert!(name.contains("side:outer")); run(&mut s, "sketch.create", json!({"plane":{"origin":[0,10,0],"x_dir":[-1,0,0],"y_dir":[0,0,1]},"name":"Badge"})); run(&mut s, "sketch.rectangle.two_point", json!({"p0":[-2,5],"p1":[2,10]})); @@ -189,7 +189,7 @@ fn legacy_v9_annulus_outer_side_emboss_keeps_its_original_heuristic_anchor() { let volume = s.model.state().body("Sleeve").unwrap().body.tessellate_with(1e-4, true).unwrap().measure().volume; assert!((volume - std::f64::consts::PI * 380. - 21.).abs() < 0.2, "volume {volume}"); let mut legacy: Value = serde_json::from_str(&s.doc.to_json()).unwrap(); - legacy["format"] = json!("solvecraft/9"); + legacy["format"] = json!("solidcraft/9"); // What a version 9 design stored for that pick: the centroid-family name of the outer wall // piece holding the point. It keeps finding that piece, as it did when saved. for feature in legacy["features"].as_array_mut().unwrap() { @@ -197,7 +197,7 @@ fn legacy_v9_annulus_outer_side_emboss_keeps_its_original_heuristic_anchor() { feature["face_names"] = json!(["F2:side:inner#4"]); } } - let reopened = Session::new(solvecraft_doc::Document::from_json(&legacy.to_string()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&legacy.to_string()).unwrap()); assert!(reopened.model.result(feature_id).unwrap().error.is_none()); assert!((reopened.model.state().body("Sleeve").unwrap().body.tessellate_with(1e-4, true).unwrap().measure().volume - volume).abs() < 1e-8); } @@ -218,7 +218,7 @@ fn certified_side_transfer_preserves_geometry_and_unsupported_taper_refuses_atom run(&mut s, "config.activate", json!({"row":"Painted"})); assert_eq!(painted_radius(&s, "Round", 10.), 2); let before = s.world_state().body("Round").unwrap().mesh().bounds(); - let mut s = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut s = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); run(&mut s, "config.activate", json!({"row":"Painted"})); assert_eq!(painted_radius(&s, "Round", 10.), 2); assert!( @@ -242,8 +242,8 @@ fn certified_side_transfer_preserves_geometry_and_unsupported_taper_refuses_atom #[test] fn certified_import_payload_binding_roundtrip_clone_and_replacement_refusal() { - let geometry = solvecraft_kernel::cylinder(Vec3::ZERO, Vec3::Z, 10., 10.).unwrap(); - let step = solvecraft_kernel::step_export(&[&geometry], "certificate fixture").unwrap(); + let geometry = solidcraft_kernel::cylinder(Vec3::ZERO, Vec3::Z, 10., 10.).unwrap(); + let step = solidcraft_kernel::step_export(&[&geometry], "certificate fixture").unwrap(); let mut s = Session::default(); let part = run(&mut s, "component.create", json!({"name":"ImportedPart"}))["component"].as_u64().unwrap(); let source = imported(&mut s, step.clone(), "Imported"); @@ -256,7 +256,7 @@ fn certified_import_payload_binding_roundtrip_clone_and_replacement_refusal() { run(&mut s, "component.paste_new", json!({"component":part,"translate":[30,0,0]})); run(&mut s, "config.activate", json!({"row":"Painted"})); assert_eq!(painted_radius(&s, "Imported (copy)", 10.), pieces); - let reopened = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); + let reopened = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); let mut s = Session::new(reopened); run(&mut s, "config.activate", json!({"row":"Painted"})); assert_eq!(painted_radius(&s, "Imported", 10.), pieces); diff --git a/crates/engine/src/cmd/config_style_tests.rs b/crates/engine/src/cmd/config_style_tests.rs index d81c0591..c34b8134 100644 --- a/crates/engine/src/cmd/config_style_tests.rs +++ b/crates/engine/src/cmd/config_style_tests.rs @@ -1,6 +1,6 @@ use crate::Session; use serde_json::{Value, json}; -use solvecraft_doc::config::{Column, ConfigFaceTarget}; +use solidcraft_doc::config::{Column, ConfigFaceTarget}; fn run(s: &mut Session, command: &str, params: Value) -> Value { s.execute(command, ¶ms).unwrap_or_else(|error| panic!("{command} {params}: {error}")) } @@ -63,7 +63,7 @@ fn strict_face_rows_resize_rename_preview_suppress_and_roundtrip() { let mb = state.body(&body).unwrap(); let colors = s.doc.face_colors(mb); assert_eq!(colors.len(), 1); - let names = solvecraft_doc::naming::face_names(mb); + let names = solidcraft_doc::naming::face_names(mb); assert!(names[colors[0].0].ends_with(":+z")); assert!(s.doc.appearances.faces[0].strict_name); run(&mut s, "appearance.assign", json!({"faces":[{"body":body,"point":[5,5,20]}]})); @@ -76,7 +76,7 @@ fn strict_face_rows_resize_rename_preview_suppress_and_roundtrip() { let hidden = run(&mut s, "config.activate", json!({"row":"Hidden"})); assert_eq!(hidden["styles"][0]["dormant"], true); assert!(s.model.state().bodies.is_empty()); - let reopened = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let reopened = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); assert_eq!(reopened, *s.doc); run(&mut s, "config.activate", json!({"row":"Tall"})); assert_eq!(s.doc.face_colors(s.model.state().body("Housing").unwrap())[0].1.name, "ABS - Red"); @@ -103,9 +103,9 @@ fn strict_face_split_pieces_share_lineage_without_index_targets() { let state = s.model.state(); let body = state.body(&body).unwrap(); let colors = s.doc.face_colors(body); - let names = solvecraft_doc::naming::face_names(body); + let names = solidcraft_doc::naming::face_names(body); assert!(colors.len() >= 2, "split should create multiple painted pieces: {names:?}"); - assert!(colors.iter().all(|(index, look)| solvecraft_doc::naming::strip_piece(&names[*index]).ends_with(":+z") && look.name == "ABS - Red")); + assert!(colors.iter().all(|(index, look)| solidcraft_doc::naming::strip_piece(&names[*index]).ends_with(":+z") && look.name == "ABS - Red")); } #[test] @@ -127,10 +127,10 @@ fn strict_face_missing_lineage_never_retargets_and_rolls_back() { assert_eq!(s.doc, before); assert_eq!(s.revision, revision); assert_eq!(s.undo.len(), history); - let look = solvecraft_doc::appearance::library("ABS - Red").unwrap(); - s.doc_mut().appearances.faces.push(solvecraft_doc::appearance::FaceLook { + let look = solidcraft_doc::appearance::library("ABS - Red").unwrap(); + s.doc_mut().appearances.faces.push(solidcraft_doc::appearance::FaceLook { body: body.clone(), - point: solvecraft_geom::Vec3::new(5., 5., 10.), + point: solidcraft_geom::Vec3::new(5., 5., 10.), name: Some(format!("F{other}:+z")), strict_name: true, certificate: None, @@ -150,11 +150,11 @@ fn face_occurrence_pick_uses_definition_identity_and_coordinates() { let world = s.world_state(); let placed = world.bodies.iter().find(|body| body.name != body.definition_name()).unwrap(); let placed_name = placed.name.clone(); - let point = solvecraft_doc::apply_point(&placed.source_transform, solvecraft_geom::Vec3::new(5., 5., 10.)); + let point = solidcraft_doc::apply_point(&placed.source_transform, solidcraft_geom::Vec3::new(5., 5., 10.)); let key = face_column(&mut s, &placed_name, json!(point)); let Column::FaceAppearance(target) = &s.doc.configs.columns[0] else { panic!() }; assert_eq!(target.body, body); - assert!(target.point.dist(solvecraft_geom::Vec3::new(5., 5., 10.)) < 1e-8); + assert!(target.point.dist(solidcraft_geom::Vec3::new(5., 5., 10.)) < 1e-8); run(&mut s, "config.row", json!({"name":"Paint","values":{key:"ABS - Blue"}})); run(&mut s, "config.activate", json!({"row":"Paint"})); assert!(s.world_state().bodies.iter().all(|body| s.doc.face_colors(body).len() == 1)); @@ -171,10 +171,10 @@ fn extrude_cap_certification_follows_actual_positive_negative_and_symmetric_name let state = s.model.state(); let body = state.body("Pad").unwrap(); let faces = body.body.faces(0.01).unwrap(); - let names = solvecraft_doc::naming::face_names(body); + let names = solidcraft_doc::naming::face_names(body); let mut caps = 0; for face in faces { - let name = solvecraft_doc::naming::strip_piece(&names[face.index]); + let name = solidcraft_doc::naming::strip_piece(&names[face.index]); let origin = name.strip_prefix('F').unwrap().split_once(':').unwrap().0.parse().unwrap(); let target = ConfigFaceTarget { body: "Pad".into(), origin_feature: origin, name: name.into(), point: face.centroid, certificate: None }; if name.ends_with(":start") || name.ends_with(":top") || name.ends_with(":bottom") { @@ -216,7 +216,7 @@ fn hostile_and_uncertified_face_targets_are_rejected() { body: body.clone(), origin_feature: id, name: format!("F{id}:face:0"), - point: solvecraft_geom::Vec3::ZERO, + point: solidcraft_geom::Vec3::ZERO, certificate: None, }; assert!(s.doc.certify_config_face(&target).is_err()); @@ -225,7 +225,7 @@ fn hostile_and_uncertified_face_targets_are_rejected() { json!({"body":body,"origin_feature":id,"name":format!("F{id}:+z"),"point":[0,0,1e20]}), json!({"body":body,"origin_feature":id,"name":format!("F{id}:+z"),"point":[0,0,0],"extra":[[]]}), ] { - assert!(solvecraft_doc::config::validate_styles_json(&json!({"configs":{"columns":[{"face_appearance":value}]}})).is_err()); + assert!(solidcraft_doc::config::validate_styles_json(&json!({"configs":{"columns":[{"face_appearance":value}]}})).is_err()); } assert!(s.execute("config.column", &json!({"component_appearance":0})).is_err()); assert!(s.execute("config.column", &json!({"face_appearance":{"body":body,"point":[0,0,1e20]}})).is_err()); diff --git a/crates/engine/src/cmd/construct.rs b/crates/engine/src/cmd/construct.rs index e1787bbf..38e4103e 100644 --- a/crates/engine/src/cmd/construct.rs +++ b/crates/engine/src/cmd/construct.rs @@ -7,8 +7,8 @@ //! persistent names. use serde_json::Value; -use solvecraft_doc::construct::{AxisDef, GeoRef, PointDef}; -use solvecraft_doc::{FeatureKind, PlaneRef}; +use solidcraft_doc::construct::{AxisDef, GeoRef, PointDef}; +use solidcraft_doc::{FeatureKind, PlaneRef}; use super::CommandSpec; use super::features::{add_feature, check_expr, plane_param, sketch_id}; @@ -137,7 +137,7 @@ fn geo(s: &Session, v: Option<&Value>, cmd: &str, what: &str) -> Result fn t_param(s: &Session, p: &Value, cmd: &str) -> Result { let t = if p.get("t").is_some() { req_expr(cmd, p, "t")? } else { "0.5".to_string() }; - check_expr(s, &t, solvecraft_doc::expr::Kind::Unitless, cmd, "t")?; + check_expr(s, &t, solidcraft_doc::expr::Kind::Unitless, cmd, "t")?; Ok(t) } diff --git a/crates/engine/src/cmd/contact_sweep.rs b/crates/engine/src/cmd/contact_sweep.rs index 21955181..b1875e22 100644 --- a/crates/engine/src/cmd/contact_sweep.rs +++ b/crates/engine/src/cmd/contact_sweep.rs @@ -3,9 +3,9 @@ use std::collections::{BTreeSet, HashMap}; use std::sync::Arc; -use solvecraft_doc::joints; -use solvecraft_doc::{IDENTITY, Mat, apply_point, mat_mul}; -use solvecraft_geom::{Aabb3, Mesh, Vec3}; +use solidcraft_doc::joints; +use solidcraft_doc::{IDENTITY, Mat, apply_point, mat_mul}; +use solidcraft_geom::{Aabb3, Mesh, Vec3}; use crate::params::bad; use crate::{Result, Session}; diff --git a/crates/engine/src/cmd/contact_sweep_tests.rs b/crates/engine/src/cmd/contact_sweep_tests.rs index 6e7b6baf..096731b4 100644 --- a/crates/engine/src/cmd/contact_sweep_tests.rs +++ b/crates/engine/src/cmd/contact_sweep_tests.rs @@ -1,6 +1,6 @@ use crate::Session; use serde_json::{Value, json}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; fn run(s: &mut Session, command: &str, p: Value) -> Value { s.execute(command, &p).unwrap() @@ -57,7 +57,7 @@ fn revolute_cannot_cross_a_blocker_between_clear_endpoints() { assert!(result["stopped_by_contact"].is_object(), "{result}"); let angle = s.doc.assembly.joints.iter().find(|j| j.name == "Pivot").unwrap().values[0]; assert!(angle > 60_f64.to_radians() && angle < 90_f64.to_radians(), "angle={angle}"); - let arm = solvecraft_doc::apply_point(&solvecraft_doc::joints::occurrence_world(&s.doc, occurrence(&s, "Arm")), Vec3::new(22.0, 0.0, 0.0)); + let arm = solidcraft_doc::apply_point(&solidcraft_doc::joints::occurrence_world(&s.doc, occurrence(&s, "Arm")), Vec3::new(22.0, 0.0, 0.0)); assert!(arm.y > 20.0 && arm.x > 2.0, "{arm:?}"); } @@ -105,7 +105,7 @@ fn over_budget_sweep_errors_and_restores_document() { #[test] fn thin_tilted_plates_cannot_cross_between_axis_bound_samples() { let mut s = Session::default(); - let normal = solvecraft_doc::apply_vector(&solvecraft_doc::rigid(Vec3::ZERO, Vec3::ZERO, Vec3::new(1.0, 1.0, 0.0), 55_f64.to_radians()), Vec3::Z); + let normal = solidcraft_doc::apply_vector(&solidcraft_doc::rigid(Vec3::ZERO, Vec3::ZERO, Vec3::new(1.0, 1.0, 0.0), 55_f64.to_radians()), Vec3::Z); for name in ["Plate A", "Plate B"] { run(&mut s, "component.activate", json!({"component": 0})); run(&mut s, "component.create", json!({"name": name})); diff --git a/crates/engine/src/cmd/delete.rs b/crates/engine/src/cmd/delete.rs index 5699c20d..9a3bc89b 100644 --- a/crates/engine/src/cmd/delete.rs +++ b/crates/engine/src/cmd/delete.rs @@ -5,8 +5,8 @@ //! changing anything, so the UI can ask first. use serde_json::{Value, json}; -use solvecraft_doc::FeatureKind; -use solvecraft_geom::Vec3; +use solidcraft_doc::FeatureKind; +use solidcraft_geom::Vec3; use super::CommandSpec; use crate::params::{bad, bool_}; diff --git a/crates/engine/src/cmd/diagnostics.rs b/crates/engine/src/cmd/diagnostics.rs index b8423d25..716b3bd8 100644 --- a/crates/engine/src/cmd/diagnostics.rs +++ b/crates/engine/src/cmd/diagnostics.rs @@ -24,7 +24,7 @@ pub static COMMANDS: &[CommandSpec] = &[ CommandSpec::new("diagnostics.crash_test", "Crash Test", crash_test).noundo().enabled(crash_test_enabled).params( "mode: panic | kernel | abort. Checks that crash reports work: panic = an internal error in this command (caught; \ the design is unchanged), kernel = one inside the geometry kernel (caught), abort = the app ends at once (reported \ - at the next start). Only when the SOLVECRAFT_CRASH_TEST environment variable is set", + at the next start). Only when the SOLIDCRAFT_CRASH_TEST environment variable is set", ), ]; @@ -52,18 +52,18 @@ fn clear(_: &mut Session, p: &Value) -> Result { } fn crash_test_enabled(_: &Session) -> std::result::Result<(), String> { - if std::env::var_os("SOLVECRAFT_CRASH_TEST").is_some() { Ok(()) } else { Err("set SOLVECRAFT_CRASH_TEST to use the crash test".into()) } + if solidcraft_geom::env::var_os("CRASH_TEST").is_some() { Ok(()) } else { Err("set SOLIDCRAFT_CRASH_TEST to use the crash test".into()) } } // The one deliberate panic: it proves the crash reports work, and runs only when asked for with -// SOLVECRAFT_CRASH_TEST. +// SOLIDCRAFT_CRASH_TEST. #[allow(clippy::panic)] fn crash_test(_: &mut Session, p: &Value) -> Result { let cmd = "diagnostics.crash_test"; match str_(p, "mode") { Some("panic") => std::panic::panic_any(String::from("crash test: an internal error in a command")), Some("kernel") => { - solvecraft_kernel::guard("crash test", || -> solvecraft_kernel::Result<()> { + solidcraft_kernel::guard("crash test", || -> solidcraft_kernel::Result<()> { std::panic::panic_any(String::from("an internal error in the kernel")) })?; Err(EngineError::Other("crash test: the kernel error was not caught".into())) diff --git a/crates/engine/src/cmd/edit.rs b/crates/engine/src/cmd/edit.rs index b4072325..a8636c4e 100644 --- a/crates/engine/src/cmd/edit.rs +++ b/crates/engine/src/cmd/edit.rs @@ -1,7 +1,7 @@ //! Undo/redo, the timeline, deletion and selection. use serde_json::{Value, json}; -use solvecraft_doc::{Feature, FeatureKind}; +use solidcraft_doc::{Feature, FeatureKind}; use super::CommandSpec; use crate::params::{bad, bool_, num, str_, string_list}; @@ -96,7 +96,7 @@ fn restore_checkpoint(s: &mut Session, p: &Value) -> Result { } fn compute_all(s: &mut Session, _p: &Value) -> Result { - s.model = solvecraft_doc::Model::new(); + s.model = solidcraft_doc::Model::new(); s.refresh(); let errors = s.model.results.iter().filter(|r| r.error.is_some()).count(); Ok(json!({"features": s.model.results.len(), "errors": errors})) @@ -303,7 +303,7 @@ fn edit_feature(s: &mut Session, p: &Value) -> Result { return Err(bad(cmd, "the feature type cannot change")); } if let FeatureKind::BodyTransfer { bodies, from, to, transform } = &nf.kind { - solvecraft_doc::validate_body_transfer(&s.doc, bodies, *from, *to, transform)?; + solidcraft_doc::validate_body_transfer(&s.doc, bodies, *from, *to, transform)?; nf.component = *to; } if let (FeatureKind::Sketch { sketch: a, .. }, FeatureKind::Sketch { sketch: b, .. }) = (&nf.kind, &f.kind) @@ -319,8 +319,8 @@ fn edit_feature(s: &mut Session, p: &Value) -> Result { && a != b { let before = s.model.state_before(id); - nf.edge_refs = solvecraft_doc::edge_refs(&before, body, a); - nf.edge_names = solvecraft_doc::edge_names_for(&before, body, a); + nf.edge_refs = solidcraft_doc::edge_refs(&before, body, a); + nf.edge_names = solidcraft_doc::edge_names_for(&before, body, a); } // Profiles picked by index or point are kept by their curves, as when the feature was made. if nf.kind.clone().profile_sels_mut() != f.kind.clone().profile_sels_mut() { @@ -328,7 +328,7 @@ fn edit_feature(s: &mut Session, p: &Value) -> Result { } // New face points: their faces' names now. if nf.kind.face_points() != f.kind.face_points() { - nf.face_names = solvecraft_doc::naming::face_names_at(&s.model.state_before(id), &nf.kind.face_points()); + nf.face_names = solidcraft_doc::naming::face_names_at(&s.model.state_before(id), &nf.kind.face_points()); } if let Some(slot) = s.doc_mut().feature_mut(id) { *slot = nf; @@ -372,7 +372,7 @@ fn select_clear(s: &mut Session, _p: &Value) -> Result { } /// Features that worked in `before` and fail in `after`: (name, error). -fn newly_broken(before: &solvecraft_doc::Model, after: &solvecraft_doc::Model) -> Vec<(String, String)> { +fn newly_broken(before: &solidcraft_doc::Model, after: &solidcraft_doc::Model) -> Vec<(String, String)> { after .results .iter() @@ -398,7 +398,7 @@ fn reorder(s: &mut Session, p: &Value) -> Result { let name = f.name.clone(); next.features.insert(to, f); // Everything that evaluated before must still evaluate. - let mut after = solvecraft_doc::Model::new(); + let mut after = solidcraft_doc::Model::new(); after.evaluate(&next); let broken = newly_broken(&s.model, &after); if !broken.is_empty() { @@ -409,13 +409,13 @@ fn reorder(s: &mut Session, p: &Value) -> Result { Ok(json!({"feature": id, "position": to})) } -fn input_keys(kind: &FeatureKind) -> Vec<(String, solvecraft_doc::expr::Kind)> { +fn input_keys(kind: &FeatureKind) -> Vec<(String, solidcraft_doc::expr::Kind)> { let mut keys: Vec<_> = kind.inputs().into_iter().map(|(label, _, unit)| (label.to_string(), unit)).collect(); if let FeatureKind::Fillet { style, .. } = kind { if let Some((key, _)) = keys.first_mut() { *key = "fillet.radius".into(); } - if let solvecraft_doc::FilletStyle::Variable { mid, .. } = style { + if let solidcraft_doc::FilletStyle::Variable { mid, .. } = style { for ((key, _), (position, _)) in keys.iter_mut().skip(2).zip(mid) { *key = format!("fillet.mid.{}", position.to_bits()); } @@ -583,7 +583,7 @@ fn dependents(s: &mut Session, p: &Value) -> Result { let id = feature_id(s, p.get("feature"), cmd)?; let mut next = (*s.doc).clone(); let gone = next.delete_feature(id)?; - let mut after = solvecraft_doc::Model::new(); + let mut after = solidcraft_doc::Model::new(); after.evaluate(&next); let names = |ids: &[u64]| -> Vec { ids.iter().filter(|x| **x != id).filter_map(|x| s.doc.feature(*x).map(|f| f.name.clone())).collect() }; let broken: Vec = newly_broken(&s.model, &after).into_iter().map(|(n, _)| n).collect(); diff --git a/crates/engine/src/cmd/edit_tests.rs b/crates/engine/src/cmd/edit_tests.rs index 1d1dd86e..65d866cb 100644 --- a/crates/engine/src/cmd/edit_tests.rs +++ b/crates/engine/src/cmd/edit_tests.rs @@ -135,7 +135,7 @@ fn redefine_rejects_sheet_exports_before_they_touch_any_files() { run(&mut s, "sketch.finish", json!({})); run(&mut s, "sheet.flange", json!({"name": "Plate"})); let doc = s.doc.clone(); - let base = std::env::temp_dir().join(format!("solvecraft-redefine-export-{}", std::process::id())); + let base = std::env::temp_dir().join(format!("solidcraft-redefine-export-{}", std::process::id())); std::fs::create_dir_all(&base).unwrap(); for command in ["sheet.export_dxf", "sheet.export_bend_table"] { for existing in [false, true] { @@ -182,9 +182,9 @@ fn redefine_keeps_allocator_above_component_and_occurrence_ids() { assert_eq!(s.doc.find_feature("Join").unwrap().id, original_feature); let combine_index = s.doc.feature_index(original_feature).unwrap(); assert!( - matches!(&s.doc.features[combine_index + 1].kind, solvecraft_doc::FeatureKind::BodyTransfer { bodies, to, .. } if bodies == &["Base"] && *to == destination) + matches!(&s.doc.features[combine_index + 1].kind, solidcraft_doc::FeatureKind::BodyTransfer { bodies, to, .. } if bodies == &["Base"] && *to == destination) ); - assert!((solvecraft_kernel::measure(&s.world_state().body("Base").unwrap().body).unwrap().volume - 6000.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&s.world_state().body("Base").unwrap().body).unwrap().volume - 6000.0).abs() < 1e-6); assert_eq!(s.world_state().bodies.len(), 1); run(&mut s, "timeline.rollback", json!({"position":combine_index + 1})); assert_eq!(s.world_state().body("Base").unwrap().component, 0, "the result transfers only AFTER Combine"); @@ -192,9 +192,9 @@ fn redefine_keeps_allocator_above_component_and_occurrence_ids() { run(&mut s, "edit.undo", json!({})); assert_eq!(*s.doc, *original); run(&mut s, "edit.redo", json!({})); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert_eq!(reopened.world_state().body("Base").unwrap().component, destination); - assert!((solvecraft_kernel::measure(&reopened.world_state().body("Base").unwrap().body).unwrap().volume - 6000.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&reopened.world_state().body("Base").unwrap().body).unwrap().volume - 6000.0).abs() < 1e-6); let max_id = s.doc.components.iter().map(|c| c.id).chain(s.doc.occurrences.iter().map(|o| o.id)).chain(s.doc.features.iter().map(|f| f.id)).max().unwrap(); assert!(s.doc.next_id > max_id); @@ -215,7 +215,7 @@ fn redefine_cannot_publish_a_second_transfer_that_breaks_existing_ownership() { let full = s.doc.to_json(); let join_index = s.doc.features.iter().position(|feature| feature.name == "Join").unwrap(); for marker in [None, Some(join_index), Some(join_index + 1)] { - let mut s = Session::new(solvecraft_doc::Document::from_json(&full).unwrap()); + let mut s = Session::new(solidcraft_doc::Document::from_json(&full).unwrap()); s.doc_mut().marker = marker; s.refresh(); let before = s.doc.to_json(); @@ -225,7 +225,7 @@ fn redefine_cannot_publish_a_second_transfer_that_breaks_existing_ownership() { assert_eq!(s.doc.to_json(), before); assert_eq!(s.pending_moves, pending); } - let reopened = Session::new(solvecraft_doc::Document::from_json(&full).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&full).unwrap()); assert_eq!(reopened.world_state().body("Base").unwrap().component, reopened.doc.find_component("Original").unwrap()); assert!(reopened.doc.features.iter().all(|feature| reopened.model.result(feature.id).is_some_and(|result| result.error.is_none()))); } diff --git a/crates/engine/src/cmd/face.rs b/crates/engine/src/cmd/face.rs index ca66bb49..59af605a 100644 --- a/crates/engine/src/cmd/face.rs +++ b/crates/engine/src/cmd/face.rs @@ -3,8 +3,8 @@ //! edges become lines, circular edges circles or arcs, anything else a chain of lines. use serde_json::{Value, json}; -use solvecraft_doc::{EmbossFace, apply_point, mat_inverse, mat_mul}; -use solvecraft_geom::{Mesh, Vec2, Vec3}; +use solidcraft_doc::{EmbossFace, apply_point, mat_inverse, mat_mul}; +use solidcraft_geom::{Mesh, Vec2, Vec3}; use crate::params::bad; use crate::{Result, Session}; diff --git a/crates/engine/src/cmd/face_tests.rs b/crates/engine/src/cmd/face_tests.rs index b2defefd..f58ea971 100644 --- a/crates/engine/src/cmd/face_tests.rs +++ b/crates/engine/src/cmd/face_tests.rs @@ -1,8 +1,8 @@ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] use crate::Session; use serde_json::{Value, json}; -use solvecraft_doc::{Document, FeatureKind, PlaneRef}; -use solvecraft_geom::Vec3; +use solidcraft_doc::{Document, FeatureKind, PlaneRef}; +use solidcraft_geom::Vec3; use std::f64::consts::PI; fn run(s: &mut Session, command: &str, params: Value) -> Value { @@ -12,7 +12,7 @@ fn check(s: &mut Session, want: f64, hole: bool, height: f64) { s.refresh(); assert!(s.model.results.iter().all(|result| result.error.is_none() && result.warning.is_none()), "{:?}", s.model.results); let state = s.model.state(); - let got: f64 = state.bodies.iter().map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).sum(); + let got: f64 = state.bodies.iter().map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).sum(); // Legacy linked outlines fit tessellated curves; enforce the existing 1e-4 material envelope. assert!((got - want).abs() < want * 1e-4, "{got} vs {want}"); assert!(state.bodies.iter().any(|b| b.body.contains(Vec3::new(5.0, 5.0, height - 1.0)))); @@ -39,7 +39,7 @@ fn generated_face_profiles_keep_exact_links_live_after_edit_and_reopen() { let FeatureKind::Sketch { plane, sketch: profile } = &generated.kind else { panic!("sketch") }; let PlaneRef::Face { anchor: Some(anchor), .. } = plane else { panic!("the original Box cap remains exact after a hole cut") }; assert_eq!(anchor.target.origin_feature, base); - assert!(matches!(anchor.target.certificate, Some(solvecraft_doc::config::FaceCertificate::Box { axis: 2, high: true }))); + assert!(matches!(anchor.target.certificate, Some(solidcraft_doc::config::FaceCertificate::Box { axis: 2, high: true }))); assert!(!profile.links.is_empty()); assert!(profile.links.iter().all(|link| link.auto_face && !link.lost)); let area = 1200.0 - if hole { 25.0 * PI } else { 0.0 }; @@ -54,12 +54,12 @@ fn generated_face_profiles_keep_exact_links_live_after_edit_and_reopen() { let solved = edited.sketch(sketch).unwrap().plane; assert!((solved.origin.z - 25.0).abs() < 1e-6); let before = s.model.state(); - let before_volume: f64 = before.bodies.iter().map(|body| solvecraft_kernel::measure(&body.body).unwrap().volume).sum(); + let before_volume: f64 = before.bodies.iter().map(|body| solidcraft_kernel::measure(&body.body).unwrap().volume).sum(); let before_bounds: Vec<_> = before.bodies.iter().map(|body| (body.name.clone(), body.mesh().bounds())).collect(); let mut reopened = Session::new(Document::from_json(&s.doc.to_json()).unwrap()); check(&mut reopened, area * 35.0, hole, 35.0); let after = reopened.model.state(); - let after_volume: f64 = after.bodies.iter().map(|body| solvecraft_kernel::measure(&body.body).unwrap().volume).sum(); + let after_volume: f64 = after.bodies.iter().map(|body| solidcraft_kernel::measure(&body.body).unwrap().volume).sum(); assert!((before_volume - after_volume).abs() < 1e-6); for (name, bounds) in before_bounds { let cold = after.body(&name).unwrap().mesh().bounds(); @@ -121,12 +121,12 @@ fn generated_profiles_and_press_pull_convert_rotated_occurrence_picks_once() { if hole { assert!(!world.bodies.iter().any(|body| body.body.contains(Vec3::new(50.0, 21.0 - height, 25.0)))); } - let before_volume: f64 = world.bodies.iter().map(|body| solvecraft_kernel::measure(&body.body).unwrap().volume).sum(); + let before_volume: f64 = world.bodies.iter().map(|body| solidcraft_kernel::measure(&body.body).unwrap().volume).sum(); let before_bounds: Vec<_> = world.bodies.iter().map(|body| (body.name.clone(), body.mesh().bounds())).collect(); let reopened = Session::new(Document::from_json(&s.doc.to_json()).unwrap()); let after = reopened.world_state(); assert!(after.bodies.iter().any(|body| body.body.contains(occupied))); - let v: f64 = after.bodies.iter().map(|body| solvecraft_kernel::measure(&body.body).unwrap().volume).sum(); + let v: f64 = after.bodies.iter().map(|body| solidcraft_kernel::measure(&body.body).unwrap().volume).sum(); assert!((v - area * height).abs() < area * height * 1e-4); assert!((v - before_volume).abs() < 1e-6); for (name, bounds) in before_bounds { diff --git a/crates/engine/src/cmd/features.rs b/crates/engine/src/cmd/features.rs index 666e0e46..df8c67da 100644 --- a/crates/engine/src/cmd/features.rs +++ b/crates/engine/src/cmd/features.rs @@ -1,8 +1,8 @@ //! Solid feature commands: extrude, revolve, fillet, chamfer, primitives, combine, move. use serde_json::{Value, json}; -use solvecraft_doc::{AxisRef, Direction, Extent, FeatureKind, Operation, PointExpr, ProfileSel, expr::Kind, point_expr}; -use solvecraft_geom::Vec3; +use solidcraft_doc::{AxisRef, Direction, Extent, FeatureKind, Operation, PointExpr, ProfileSel, expr::Kind, point_expr}; +use solidcraft_geom::Vec3; use super::CommandSpec; use crate::params::{bad, bool_, expr, req_expr, str_, string_list, vec2, vec3}; @@ -125,7 +125,7 @@ fn extrude_base(s: &Session, p: &Value) -> Option<(Vec3, Vec3)> { ProfileSel::Indices { indices } => ps.get(*indices.first()?)?.region.interior_point(), ProfileSel::Points { points } => *points.first()?, selection @ (ProfileSel::Curves { .. } | ProfileSel::OffsetBoundaries { .. } | ProfileSel::WholeBoundaries { .. }) => { - let indices = solvecraft_doc::profile_selection_indices(ss, &selection).ok()?; + let indices = solidcraft_doc::profile_selection_indices(ss, &selection).ok()?; ps.get(*indices.first()?)?.region.interior_point() } }; @@ -210,12 +210,12 @@ pub(super) fn feature_sketch(s: &Session, p: &Value, cmd: &str) -> Result { /// curves) stays as given. Curve picks are checked: one that names no profile, or several, is /// refused now rather than failing later. pub(super) fn stable_profiles(s: &Session, kind: &mut FeatureKind) -> Result<()> { - use solvecraft_doc::sketch::profile_at_point; + use solidcraft_doc::sketch::profile_at_point; let st = s.model.state(); for (sketch, sel) in kind.profile_sels_mut() { let Some(ss) = st.sketch(sketch) else { continue }; let indices = match sel { - ProfileSel::Indices { .. } => solvecraft_doc::profile_selection_indices(ss, sel)?, + ProfileSel::Indices { .. } => solidcraft_doc::profile_selection_indices(ss, sel)?, ProfileSel::Points { points } => { let Some(indices) = points.iter().map(|point| profile_at_point(&ss.profiles, *point)).collect::>>() else { continue }; indices @@ -240,19 +240,19 @@ pub(super) fn stable_profiles(s: &Session, kind: &mut FeatureKind) -> Result<()> } } let selection = ProfileSel::Curves { loops: normalized }; - solvecraft_doc::profile_selection_indices(ss, &selection)?; + solidcraft_doc::profile_selection_indices(ss, &selection)?; *sel = selection; continue; } ProfileSel::OffsetBoundaries { .. } | ProfileSel::WholeBoundaries { .. } => { // Explicit identities retain the caller's semantics. Validate through the // same resolver as recomputation, including reserved-role precedence. - solvecraft_doc::profile_selection_indices(ss, sel)?; + solidcraft_doc::profile_selection_indices(ss, sel)?; continue; } ProfileSel::All => continue, }; - let captured = solvecraft_doc::capture_profile_selection(ss, &indices)?; + let captured = solidcraft_doc::capture_profile_selection(ss, &indices)?; *sel = match captured { ProfileSel::OffsetBoundaries { associations } if associations.iter().any(|id| { @@ -288,7 +288,7 @@ pub(super) fn profiles(p: &Value, cmd: &str) -> Result { match v { Value::Object(fields) => { if fields.get("type").and_then(Value::as_str) == Some("whole_boundaries") { - return solvecraft_doc::whole_profile_selection_from_value(v).map_err(|error| bad(cmd, error.to_string())); + return solidcraft_doc::whole_profile_selection_from_value(v).map_err(|error| bad(cmd, error.to_string())); } let associations = fields .get("offset_boundaries") @@ -384,11 +384,11 @@ fn add_feature_in(s: &mut Session, p: &Value, kind: FeatureKind, world: bool) -> let (refs, names) = match &kind { FeatureKind::Fillet { edges, body, .. } | FeatureKind::Chamfer { edges, body, .. } => { let st = s.model.state(); - (solvecraft_doc::edge_refs(&st, body, edges), solvecraft_doc::edge_names_for(&st, body, edges)) + (solidcraft_doc::edge_refs(&st, body, edges), solidcraft_doc::edge_names_for(&st, body, edges)) } // Sheet metal edges are named by the sheet model. FeatureKind::SheetFlange { edges, .. } | FeatureKind::SheetHem { edges, .. } => { - (Vec::new(), solvecraft_doc::sheet::sheet_edge_names(&s.model.state(), edges)) + (Vec::new(), solidcraft_doc::sheet::sheet_edge_names(&s.model.state(), edges)) } _ => (Vec::new(), Vec::new()), }; @@ -398,11 +398,11 @@ fn add_feature_in(s: &mut Session, p: &Value, kind: FeatureKind, world: bool) -> let state = s.model.state(); let body = state.body(&face.body).ok_or_else(|| EngineError::Other("selected cylinder body no longer exists".into()))?; let frame = face.body_frame(&s.doc, body).map_err(|e| EngineError::Other(e.to_string()))?; - let inverse = solvecraft_doc::mat_inverse(&frame).ok_or_else(|| EngineError::Other("invalid cylinder placement".into()))?; - let at = solvecraft_doc::apply_point(&solvecraft_doc::mat_mul(&inverse, &s.active_component_frame()), face.at); - let mut scoped = solvecraft_doc::ModelState::default(); + let inverse = solidcraft_doc::mat_inverse(&frame).ok_or_else(|| EngineError::Other("invalid cylinder placement".into()))?; + let at = solidcraft_doc::apply_point(&solidcraft_doc::mat_mul(&inverse, &s.active_component_frame()), face.at); + let mut scoped = solidcraft_doc::ModelState::default(); scoped.bodies.push(body.clone()); - let names = solvecraft_doc::naming::face_names_at(&scoped, &[at]); + let names = solidcraft_doc::naming::face_names_at(&scoped, &[at]); if names.len() != 1 || names.first().is_none_or(|name| name.is_empty()) { return Err(EngineError::Other("selected cylinder face has no persistent identity".into())); } @@ -410,7 +410,7 @@ fn add_feature_in(s: &mut Session, p: &Value, kind: FeatureKind, world: bool) -> } else if pts.is_empty() { Vec::new() } else { - solvecraft_doc::naming::face_names_at(&s.model.state(), &pts) + solidcraft_doc::naming::face_names_at(&s.model.state(), &pts) } }; // Primitives have no `targets` of their own: their `targets` are the bodies taking part (#259). @@ -578,7 +578,7 @@ fn revolve(s: &mut Session, p: &Value) -> Result { let symmetric = matches!(str_(p, "direction").map(str::to_ascii_lowercase).as_deref(), Some("symmetric")) || bool_(p, "symmetric").unwrap_or(false); let convention = match p.get("convention") { - None => solvecraft_doc::RevolveConvention::SketchNormal, + None => solidcraft_doc::RevolveConvention::SketchNormal, Some(value) => serde_json::from_value(value.clone()).map_err(|_| bad(cmd, "`convention` must be sketch_normal or legacy_axis_start"))?, }; let kind = FeatureKind::Revolve { @@ -729,7 +729,7 @@ fn press_pull(s: &mut Session, p: &Value) -> Result { if let Some(fp) = vec3(face) && super::face::face_is_planar(s, fp) == Some(false) { - let name = solvecraft_doc::naming::face_names_at(&s.model.state(), &[fp]).into_iter().next().unwrap_or_default(); + let name = solidcraft_doc::naming::face_names_at(&s.model.state(), &[fp]).into_iter().next().unwrap_or_default(); let fid = name.strip_prefix('F').and_then(|r| r.split(':').next()).and_then(|x| x.parse::().ok()); if name.contains(":blend") { let f = fid.and_then(|id| s.doc.feature(id)).cloned().ok_or_else(|| bad(cmd, "the blend's feature is gone"))?; @@ -757,8 +757,8 @@ fn fillet(s: &mut Session, p: &Value) -> Result { let radius = req_expr(cmd, p, "radius")?; check_expr(s, &radius, Kind::Length, cmd, "radius")?; let style = match str_(p, "type").map(str::to_ascii_lowercase).as_deref() { - None | Some("constant") | Some("rolling_ball") => solvecraft_doc::FilletStyle::Constant, - Some("chord") | Some("chord_length") => solvecraft_doc::FilletStyle::Chord, + None | Some("constant") | Some("rolling_ball") => solidcraft_doc::FilletStyle::Constant, + Some("chord") | Some("chord_length") => solidcraft_doc::FilletStyle::Chord, Some("variable") => { let radius2 = req_expr(cmd, p, "radius2")?; check_expr(s, &radius2, Kind::Length, cmd, "radius2")?; @@ -781,13 +781,13 @@ fn fillet(s: &mut Session, p: &Value) -> Result { mid.push((t, r)); } let smooth = p.get("smooth").and_then(Value::as_bool).unwrap_or(false); - solvecraft_doc::FilletStyle::Variable { radius2, start, mid, smooth } + solidcraft_doc::FilletStyle::Variable { radius2, start, mid, smooth } } Some(o) => return Err(bad(cmd, format!("unknown fillet type `{o}` (constant, chord or variable)"))), }; let fit = match str_(p, "fit").map(str::to_ascii_lowercase).as_deref() { - None | Some("exact") => solvecraft_doc::FilletFit::Exact, - Some("reduce") => solvecraft_doc::FilletFit::Reduce, + None | Some("exact") => solidcraft_doc::FilletFit::Exact, + Some("reduce") => solidcraft_doc::FilletFit::Reduce, Some(o) => return Err(bad(cmd, format!("unknown fit `{o}` (exact or reduce)"))), }; let mut out = add_feature(s, p, FeatureKind::Fillet { edges, radius, body: str_(p, "body").map(str::to_string), style, fit })?; @@ -909,19 +909,19 @@ fn axis_line(p: &Value, cmd: &str) -> Result<(Vec3, Vec3)> { } /// A pattern feature, or for `components`, new occurrences of them at each placement. -fn finish_pattern(s: &mut Session, p: &Value, features: Vec, pattern: solvecraft_doc::PatternKind, bodies: Vec) -> Result { +fn finish_pattern(s: &mut Session, p: &Value, features: Vec, pattern: solidcraft_doc::PatternKind, bodies: Vec) -> Result { let comps = string_list(p, "components"); if comps.is_empty() { return add_feature(s, p, FeatureKind::Pattern { features, pattern, bodies }); } let cmd = "pattern (components)"; - let mats = solvecraft_doc::pattern_matrices(&s.doc, &s.model.state(), &pattern)?; + let mats = solidcraft_doc::pattern_matrices(&s.doc, &s.model.state(), &pattern)?; let mut made = Vec::new(); for c in &comps { let id = s.doc.find_component(c).filter(|id| *id != 0).ok_or_else(|| bad(cmd, format!("no component `{c}`")))?; let src = s.doc.occurrence_of(id).cloned().ok_or_else(|| bad(cmd, format!("component `{c}` has no occurrence")))?; for m in &mats { - made.push(s.doc_mut().add_occurrence(id, src.parent, solvecraft_doc::mat_mul(m, &src.transform))?); + made.push(s.doc_mut().add_occurrence(id, src.parent, solidcraft_doc::mat_mul(m, &src.transform))?); } } Ok(json!({"occurrences": made})) @@ -960,7 +960,7 @@ fn pattern_rect(s: &mut Session, p: &Value) -> Result { if let Some(c) = &spacing2 { check_expr(s, c, Kind::Length, cmd, "spacing2")?; } - let pattern = solvecraft_doc::PatternKind::Rectangular { dir1, count1, spacing1, dir2, count2, spacing2 }; + let pattern = solidcraft_doc::PatternKind::Rectangular { dir1, count1, spacing1, dir2, count2, spacing2 }; finish_pattern(s, p, features, pattern, bodies) } @@ -972,7 +972,7 @@ fn pattern_circ(s: &mut Session, p: &Value) -> Result { check_expr(s, &count, Kind::Unitless, cmd, "count")?; let angle = expr(p, "angle").unwrap_or_else(|| "360 deg".into()); check_expr(s, &angle, Kind::Angle, cmd, "angle")?; - let pattern = solvecraft_doc::PatternKind::Circular { origin, axis, count, angle }; + let pattern = solidcraft_doc::PatternKind::Circular { origin, axis, count, angle }; finish_pattern(s, p, features, pattern, bodies) } @@ -998,7 +998,7 @@ fn pattern_path(s: &mut Session, p: &Value) -> Result { Some(o) => return Err(bad(cmd, format!("unknown orientation `{o}` (identical or path)"))), }; let flip = bool_(p, "flip").unwrap_or(false); - let pattern = solvecraft_doc::PatternKind::Path { path_sketch, path, count, spacing, extent, flip, orient }; + let pattern = solidcraft_doc::PatternKind::Path { path_sketch, path, count, spacing, extent, flip, orient }; finish_pattern(s, p, features, pattern, bodies) } @@ -1027,7 +1027,7 @@ pub(super) fn origin_plane_alias(name: &str) -> Option<&'static str> { /// A construction plane may not take an origin plane's name (`XY`, `Top`...): a plane /// parameter could then mean either. pub(super) fn check_plane_name(name: &str, cmd: &str) -> Result<()> { - if solvecraft_geom::Plane::named(name.trim()).is_some() { + if solidcraft_geom::Plane::named(name.trim()).is_some() { return Err(bad( cmd, format!("`{}` is reserved for an origin plane (XY, XZ, YZ, Top, Front, Right); give the construction plane another name", name.trim()), @@ -1040,8 +1040,8 @@ pub(super) fn check_plane_name(name: &str, cmd: &str) -> Result<()> { /// feature id; kept by name, so the feature follows the plane when it changes), or an /// explicit plane `{origin, x_dir, y_dir}` / `{origin, normal}`. Every command that takes a /// plane reads it here. -pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result { - use solvecraft_doc::PlaneRef; +pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result { + use solidcraft_doc::PlaneRef; const WANT: &str = "XY, XZ, YZ, a construction plane name or id, or {origin, normal}"; let construction = |r: &str| match s.doc.find_feature(r) { Some(f) if matches!(f.kind, FeatureKind::ConstructionPlane { .. }) => Ok(PlaneRef::Construction { name: f.name.clone() }), @@ -1049,7 +1049,7 @@ pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result Err(bad(cmd, format!("no plane `{r}` (a plane is {WANT})"))), }; match v { - Some(Value::String(n)) if solvecraft_geom::Plane::named(n.trim()).is_some() => { + Some(Value::String(n)) if solidcraft_geom::Plane::named(n.trim()).is_some() => { if let Some(f) = s.doc.find_feature(n.trim()) && matches!(f.kind, FeatureKind::ConstructionPlane { .. }) { @@ -1076,8 +1076,8 @@ pub(super) fn plane_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result vec3(x).ok_or_else(|| bad(cmd, "the plane's `origin` must be a point [x, y, z]"))?, }; let pl = match (o.get("x_dir").and_then(vec3), o.get("y_dir").and_then(vec3), o.get("normal").and_then(vec3)) { - (Some(x), Some(y), _) => solvecraft_geom::Plane::new(origin, x, y), - (_, _, Some(n)) => solvecraft_geom::Plane::from_normal(origin, n), + (Some(x), Some(y), _) => solidcraft_geom::Plane::new(origin, x, y), + (_, _, Some(n)) => solidcraft_geom::Plane::from_normal(origin, n), _ => None, }; Ok(PlaneRef::Custom { plane: pl.ok_or_else(|| bad(cmd, "plane needs x_dir and y_dir, or normal"))? }) @@ -1094,12 +1094,12 @@ fn plane_offset(s: &mut Session, p: &Value) -> Result { if let Some(face) = p.get("base").filter(|b| b.get("face").is_some()) { let base = super::sketch::face::capture(s, face, cmd)?; super::sketch::face::capture_positions(s, &base)?; - let kind = FeatureKind::ConstructionPlane { plane: solvecraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } }; + let kind = FeatureKind::ConstructionPlane { plane: solidcraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } }; // The capture is already in the authoring frame. return add_feature_in(s, p, kind, false); } let base = plane_param(s, p.get("base"), cmd)?; - add_feature(s, p, FeatureKind::ConstructionPlane { plane: solvecraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } }) + add_feature(s, p, FeatureKind::ConstructionPlane { plane: solidcraft_doc::PlaneRef::Offset { base: Box::new(base), distance: d } }) } fn plane_angle(s: &mut Session, p: &Value) -> Result { @@ -1111,7 +1111,7 @@ fn plane_angle(s: &mut Session, p: &Value) -> Result { add_feature( s, p, - FeatureKind::ConstructionPlane { plane: solvecraft_doc::PlaneRef::AtAngle { base: Box::new(base), axis_origin, axis_dir, angle } }, + FeatureKind::ConstructionPlane { plane: solidcraft_doc::PlaneRef::AtAngle { base: Box::new(base), axis_origin, axis_dir, angle } }, ) } @@ -1119,11 +1119,11 @@ fn split_face(s: &mut Session, p: &Value) -> Result { let cmd = "solid.split_face"; let faces = face_points(p, cmd)?; let body = str_(p, "body").map(str::to_string); - let xy = || solvecraft_doc::PlaneRef::Origin { name: "XY".into() }; + let xy = || solidcraft_doc::PlaneRef::Origin { name: "XY".into() }; let kind = if let Some(t) = p.get("tool").filter(|t| !t.is_null()) { let tb = t.get("body").and_then(Value::as_str).ok_or_else(|| bad(cmd, "`tool.body` must name a body"))?.to_string(); let point = t.get("point").and_then(crate::params::vec3).ok_or_else(|| bad(cmd, "`tool.point` must be [x, y, z] on the tool face"))?; - FeatureKind::SplitFace { faces, body, plane: xy(), tool: Some(solvecraft_doc::FaceAt { body: tb, point }), sketch: None } + FeatureKind::SplitFace { faces, body, plane: xy(), tool: Some(solidcraft_doc::FaceAt { body: tb, point }), sketch: None } } else if p.get("sketch").is_some_and(|v| !v.is_null()) { let sketch = sketch_id(s, p.get("sketch"), cmd, "sketch")?; FeatureKind::SplitFace { faces, body, plane: xy(), tool: None, sketch: Some(sketch) } @@ -1146,7 +1146,7 @@ fn split_body(s: &mut Session, p: &Value) -> Result { let point = t.get("point").and_then(crate::params::vec3).ok_or_else(|| bad(cmd, "`tool.point` must be [x, y, z] on the tool face"))?; let st = s.model.state(); let tbody = st.body(&tb).ok_or_else(|| bad(cmd, format!("no body `{tb}`")))?; - if solvecraft_doc::appearance::face_index_at(tbody, point).is_none() { + if solidcraft_doc::appearance::face_index_at(tbody, point).is_none() { return Err(bad(cmd, "`tool.point` is not on a face of the tool body")); } return add_feature( @@ -1154,8 +1154,8 @@ fn split_body(s: &mut Session, p: &Value) -> Result { p, FeatureKind::Split { body, - plane: solvecraft_doc::PlaneRef::Origin { name: "XY".into() }, - tool: Some(solvecraft_doc::FaceAt { body: tb, point }), + plane: solidcraft_doc::PlaneRef::Origin { name: "XY".into() }, + tool: Some(solidcraft_doc::FaceAt { body: tb, point }), }, ); } @@ -1221,13 +1221,13 @@ fn hole(s: &mut Session, p: &Value) -> Result { let n = ss.plane.normal(); let first = ids.first().and_then(|id| ss.sketch.points.iter().find(|q| &q.id == id)).map(|q| ss.plane.to_world(q.pos)).unwrap_or_default(); - Some((solvecraft_doc::SketchPoints { sketch, ids }, first, -n)) + Some((solidcraft_doc::SketchPoints { sketch, ids }, first, -n)) } _ => None, }; let thread = str_(p, "thread").map(|t| t.trim().to_string()).filter(|t| !t.is_empty()); if let Some(t) = &thread - && solvecraft_doc::parse_metric_thread(t).is_none() + && solidcraft_doc::parse_metric_thread(t).is_none() { return Err(bad(cmd, format!("unknown thread `{t}` (ISO metric, like M6 or M6x0.75)"))); } @@ -1268,7 +1268,7 @@ fn hole(s: &mut Session, p: &Value) -> Result { format!("{d} mm") } (None, Some(t), None) => { - let (major, pitch) = solvecraft_doc::parse_metric_thread(t).ok_or_else(|| bad(cmd, "thread"))?; + let (major, pitch) = solidcraft_doc::parse_metric_thread(t).ok_or_else(|| bad(cmd, "thread"))?; format!("{} mm", ((major - pitch) * 1000.0).round() / 1000.0) } (None, None, None) => return Err(bad(cmd, "needs `diameter`, `thread` (a tapped hole) or `clearance` (a bolt size)")), @@ -1284,12 +1284,12 @@ fn hole(s: &mut Session, p: &Value) -> Result { }; let kind = match str_(p, "type").unwrap_or("simple") { "simple" => match expr(p, "tip_angle") { - Some(t) if depth.is_some() => solvecraft_doc::HoleKind::Drilled { tip_angle: t }, - _ => solvecraft_doc::HoleKind::Simple, + Some(t) if depth.is_some() => solidcraft_doc::HoleKind::Drilled { tip_angle: t }, + _ => solidcraft_doc::HoleKind::Simple, }, - "drilled" => solvecraft_doc::HoleKind::Drilled { tip_angle: expr(p, "tip_angle").unwrap_or_else(|| "118 deg".into()) }, - "counterbore" => solvecraft_doc::HoleKind::Counterbore { cb_diameter: get("cb_diameter")?, cb_depth: get("cb_depth")? }, - "countersink" => solvecraft_doc::HoleKind::Countersink { + "drilled" => solidcraft_doc::HoleKind::Drilled { tip_angle: expr(p, "tip_angle").unwrap_or_else(|| "118 deg".into()) }, + "counterbore" => solidcraft_doc::HoleKind::Counterbore { cb_diameter: get("cb_diameter")?, cb_depth: get("cb_depth")? }, + "countersink" => solidcraft_doc::HoleKind::Countersink { cs_diameter: get("cs_diameter")?, cs_angle: expr(p, "cs_angle").unwrap_or_else(|| "90 deg".into()), }, @@ -1309,11 +1309,11 @@ fn sketch_hole_points(s: &Session, sketch: u64) -> Vec { let mut ends: Vec = Vec::new(); for c in &ss.sketch.curves { match &c.kind { - solvecraft_sketch::CurveKind::Circle { .. } => {} - solvecraft_sketch::CurveKind::Arc { a, b, .. } | solvecraft_sketch::CurveKind::Line { a, b } => ends.extend([*a, *b]), - solvecraft_sketch::CurveKind::Ellipse { m, .. } => ends.push(*m), - solvecraft_sketch::CurveKind::Spline { pts, .. } => ends.extend(pts.iter().copied()), - solvecraft_sketch::CurveKind::Conic { a, b, apex, .. } => ends.extend([*a, *b, *apex]), + solidcraft_sketch::CurveKind::Circle { .. } => {} + solidcraft_sketch::CurveKind::Arc { a, b, .. } | solidcraft_sketch::CurveKind::Line { a, b } => ends.extend([*a, *b]), + solidcraft_sketch::CurveKind::Ellipse { m, .. } => ends.push(*m), + solidcraft_sketch::CurveKind::Spline { pts, .. } => ends.extend(pts.iter().copied()), + solidcraft_sketch::CurveKind::Conic { a, b, apex, .. } => ends.extend([*a, *b, *apex]), } } ss.sketch.points.iter().enumerate().filter(|(i, p)| p.id != "origin" && !ends.contains(i)).map(|(_, p)| p.id.clone()).collect() @@ -1410,10 +1410,10 @@ fn loft(s: &mut Session, p: &Value) -> Result { if !st.sketch(sketch).is_some_and(|ss| ss.sketch.points.iter().any(|q| q.id == pt)) { return Err(bad(cmd, format!("no point `{pt}` in the section's sketch"))); } - sections.push(solvecraft_doc::LoftSection { sketch, profiles: solvecraft_doc::ProfileSel::All, point: Some(pt.to_string()) }); + sections.push(solidcraft_doc::LoftSection { sketch, profiles: solidcraft_doc::ProfileSel::All, point: Some(pt.to_string()) }); continue; } - sections.push(solvecraft_doc::LoftSection { sketch, profiles: profiles(sec, cmd)?, point: None }); + sections.push(solidcraft_doc::LoftSection { sketch, profiles: profiles(sec, cmd)?, point: None }); } add_feature(s, p, FeatureKind::Loft { sections, operation: operation(p, cmd)?, targets: string_list(p, "targets") }) } @@ -1426,7 +1426,7 @@ fn thread(s: &mut Session, p: &Value) -> Result { .ok_or_else(|| bad(cmd, "`face` must be a point [x, y, z] on a cylindrical face"))?; let designation = str_(p, "designation").map(|t| t.trim().to_string()).filter(|t| !t.is_empty()); if let Some(t) = &designation - && solvecraft_doc::parse_metric_thread(t).is_none() + && solidcraft_doc::parse_metric_thread(t).is_none() { return Err(bad(cmd, format!("unknown thread `{t}` (ISO metric, like M8 or M8x1)"))); } diff --git a/crates/engine/src/cmd/features_more.rs b/crates/engine/src/cmd/features_more.rs index b7971ab3..d7985102 100644 --- a/crates/engine/src/cmd/features_more.rs +++ b/crates/engine/src/cmd/features_more.rs @@ -1,9 +1,9 @@ //! Rib, Web, Emboss, Replace Face, Align and Remove. use serde_json::Value; -use solvecraft_doc::FeatureKind; -use solvecraft_doc::expr::Kind; -use solvecraft_geom::Vec3; +use solidcraft_doc::FeatureKind; +use solidcraft_doc::expr::Kind; +use solidcraft_geom::Vec3; use super::CommandSpec; use super::features::{add_feature, axis_dir_param, check_expr, feature_sketch, plane_param, point_param, profiles}; @@ -43,7 +43,7 @@ pub static COMMANDS: &[CommandSpec] = &[ fn coil(s: &mut Session, p: &Value) -> Result { let cmd = "solid.coil"; - let base = point_param(s, p, "base", cmd)?.unwrap_or_else(|| solvecraft_doc::point_expr(Vec3::ZERO)); + let base = point_param(s, p, "base", cmd)?.unwrap_or_else(|| solidcraft_doc::point_expr(Vec3::ZERO)); let axis = axis_dir_param(p, cmd)?; let diameter = req_expr(cmd, p, "diameter")?; check_expr(s, &diameter, Kind::Length, cmd, "diameter")?; @@ -60,8 +60,8 @@ fn coil(s: &mut Session, p: &Value) -> Result { let section_size = req_expr(cmd, p, "section_size")?; check_expr(s, §ion_size, Kind::Length, cmd, "section_size")?; let section = match str_(p, "section").map(str::to_ascii_lowercase).as_deref() { - None | Some("circular" | "circle") => solvecraft_doc::CoilSection::Circular, - Some("square") => solvecraft_doc::CoilSection::Square, + None | Some("circular" | "circle") => solidcraft_doc::CoilSection::Circular, + Some("square") => solidcraft_doc::CoilSection::Square, Some(o) => return Err(bad(cmd, format!("unknown section `{o}` (circular or square)"))), }; let position = match str_(p, "section_position").map(str::to_ascii_lowercase).as_deref() { @@ -146,7 +146,7 @@ fn emboss(s: &mut Session, p: &Value) -> Result { let mut targets = string_list(p, "targets"); let face = if let Some(value) = p.get("face") { let local = vec3(value).ok_or_else(|| bad(cmd, "`face` must be a finite point [x,y,z]"))?; - let at = solvecraft_doc::apply_point(&s.active_component_frame(), local); + let at = solidcraft_doc::apply_point(&s.active_component_frame(), local); let state = s.world_state(); let mut candidates = Vec::new(); for body in &state.bodies { @@ -179,19 +179,19 @@ fn emboss(s: &mut Session, p: &Value) -> Result { let (selected, index) = candidates.first().copied().ok_or_else(|| bad(cmd, "no target face"))?; let name = selected.definition_name(); let body = &selected.body; - match solvecraft_kernel::face_surface(body, index) { - Some(solvecraft_kernel::FaceSurface::Plane { .. }) => { + match solidcraft_kernel::face_surface(body, index) { + Some(solidcraft_kernel::FaceSurface::Plane { .. }) => { targets = vec![name.to_owned()]; None } - Some(solvecraft_kernel::FaceSurface::Cylinder { .. }) => { + Some(solidcraft_kernel::FaceSurface::Cylinder { .. }) => { if targets.len() > 1 { return Err(bad(cmd, "cylinder emboss needs one selected target body")); } // Viewport picks lie on tessellated chords. Keep their axial and // angular position, and certify their distance before storing an // exact surface anchor for later replay. - let at = solvecraft_kernel::cylinder_emboss_anchor(body, index, at).map_err(|e| bad(cmd, e.to_string()))?; + let at = solidcraft_kernel::cylinder_emboss_anchor(body, index, at).map_err(|e| bad(cmd, e.to_string()))?; let face = emboss_selection(s, selected, at)?; check_emboss_positions(s, sketch, selected, &face)?; targets = vec![name.to_owned()]; @@ -209,7 +209,7 @@ fn emboss(s: &mut Session, p: &Value) -> Result { add_feature(s, p, FeatureKind::Emboss { sketch, profiles, depth, deboss, targets, face }) } -fn emboss_selection(s: &Session, selected: &solvecraft_doc::ModelBody, at: Vec3) -> Result { +fn emboss_selection(s: &Session, selected: &solidcraft_doc::ModelBody, at: Vec3) -> Result { let cmd = "solid.emboss"; let name = selected.definition_name(); let original = s.model.state(); @@ -229,11 +229,11 @@ fn emboss_selection(s: &Session, selected: &solvecraft_doc::ModelBody, at: Vec3) if path.len() > 64 || author_path.len() > 64 { return Err(bad(cmd, "cylinder placement is too deep")); } - let inverse = solvecraft_doc::mat_inverse(&s.active_component_frame()).ok_or_else(|| bad(cmd, "invalid active frame"))?; - Ok(solvecraft_doc::EmbossFace { body: name.to_owned(), at: solvecraft_doc::apply_point(&inverse, at), path, author_path }) + let inverse = solidcraft_doc::mat_inverse(&s.active_component_frame()).ok_or_else(|| bad(cmd, "invalid active frame"))?; + Ok(solidcraft_doc::EmbossFace { body: name.to_owned(), at: solidcraft_doc::apply_point(&inverse, at), path, author_path }) } -fn check_emboss_positions(s: &Session, sketch: u64, body: &solvecraft_doc::ModelBody, face: &solvecraft_doc::EmbossFace) -> Result<()> { +fn check_emboss_positions(s: &Session, sketch: u64, body: &solidcraft_doc::ModelBody, face: &solidcraft_doc::EmbossFace) -> Result<()> { let cmd = "solid.emboss"; let owner = s.doc.model_body_component(body); let sketch_owner = s.doc.feature(sketch).map(|f| f.component).ok_or_else(|| bad(cmd, "profile sketch no longer exists"))?; @@ -253,15 +253,15 @@ fn check_emboss_positions(s: &Session, sketch: u64, body: &solvecraft_doc::Model fn infer_emboss_selection( s: &Session, sketch: u64, - profiles: &solvecraft_doc::ProfileSel, + profiles: &solidcraft_doc::ProfileSel, depth: &str, deboss: bool, targets: &[String], -) -> Result> { +) -> Result> { let cmd = "solid.emboss"; let original = s.model.state(); let ss = original.sketch(sketch).ok_or_else(|| bad(cmd, "the sketch is not evaluated"))?; - let regions = solvecraft_doc::select_profiles(ss, profiles).map_err(|e| bad(cmd, e.to_string()))?; + let regions = solidcraft_doc::select_profiles(ss, profiles).map_err(|e| bad(cmd, e.to_string()))?; let sketch_owner = s.doc.feature(sketch).map(|f| f.component).ok_or_else(|| bad(cmd, "profile sketch no longer exists"))?; let d = s.doc.eval(&s.doc.with_design_unit(depth, Kind::Length), Kind::Length).map_err(|e| bad(cmd, e.to_string()))?; let world = s.world_state(); @@ -278,26 +278,26 @@ fn infer_emboss_selection( if faces > 50_000 { return Err(bad(cmd, "cylinder target search exceeds face work limit")); } - if !solvecraft_kernel::has_cylinder_emboss_wall(&raw.body).map_err(|e| bad(cmd, e.to_string()))? { + if !solidcraft_kernel::has_cylinder_emboss_wall(&raw.body).map_err(|e| bad(cmd, e.to_string()))? { continue; } let mut face = emboss_selection(s, selected, Vec3::ZERO)?; let body_frame = face.body_frame(&placed, raw).map_err(|e| bad(cmd, e.to_string()))?; - let inverse = solvecraft_doc::mat_inverse(&body_frame).ok_or_else(|| bad(cmd, "invalid cylinder body frame"))?; + let inverse = solidcraft_doc::mat_inverse(&body_frame).ok_or_else(|| bad(cmd, "invalid cylinder body frame"))?; let displayed = world.sketch(sketch).ok_or_else(|| bad(cmd, "the profile sketch is not displayed"))?; - let plane = solvecraft_geom::Plane::new( - solvecraft_doc::apply_point(&inverse, displayed.plane.origin), - solvecraft_doc::apply_vector(&inverse, displayed.plane.x), - solvecraft_doc::apply_vector(&inverse, displayed.plane.y), + let plane = solidcraft_geom::Plane::new( + solidcraft_doc::apply_point(&inverse, displayed.plane.origin), + solidcraft_doc::apply_vector(&inverse, displayed.plane.x), + solidcraft_doc::apply_vector(&inverse, displayed.plane.y), ) .ok_or_else(|| bad(cmd, "invalid cylinder sketch frame"))?; if let Some((_, at)) = - solvecraft_kernel::infer_cylinder_emboss(&raw.body, &plane, ®ions, if deboss { -d } else { d }).map_err(|e| bad(cmd, e.to_string()))? + solidcraft_kernel::infer_cylinder_emboss(&raw.body, &plane, ®ions, if deboss { -d } else { d }).map_err(|e| bad(cmd, e.to_string()))? { face.sketch_frame(&placed, s.active_component, sketch_owner, raw).map_err(|e| bad(cmd, e.to_string()))?; check_emboss_positions(s, sketch, selected, &face)?; - let author_inverse = solvecraft_doc::mat_inverse(&s.active_component_frame()).ok_or_else(|| bad(cmd, "invalid author frame"))?; - face.at = solvecraft_doc::apply_point(&solvecraft_doc::mat_mul(&author_inverse, &body_frame), at); + let author_inverse = solidcraft_doc::mat_inverse(&s.active_component_frame()).ok_or_else(|| bad(cmd, "invalid author frame"))?; + face.at = solidcraft_doc::apply_point(&solidcraft_doc::mat_mul(&author_inverse, &body_frame), at); if chosen.is_some() { return Err(bad(cmd, "multiple tangent cylinders match the selected profiles; select a target face or body")); } @@ -325,8 +325,8 @@ fn replace_face(s: &mut Session, p: &Value) -> Result { let target = match p.get("target_face").and_then(vec3) { Some(tp) => { let n = super::face::face_normal(s, tp).ok_or_else(|| bad(cmd, "the target face must be planar"))?; - let plane = solvecraft_geom::Plane::from_normal(tp, n).ok_or_else(|| bad(cmd, "the target face's plane"))?; - solvecraft_doc::PlaneRef::Custom { plane } + let plane = solidcraft_geom::Plane::from_normal(tp, n).ok_or_else(|| bad(cmd, "the target face's plane"))?; + solidcraft_doc::PlaneRef::Custom { plane } } None => plane_param(s, p.get("target"), cmd)?, }; @@ -399,7 +399,7 @@ pub(super) fn snap_point(s: &Session, kind: &str, at: Vec3) -> Option { .collect(), ), "edge_mid" => { - let d = |e: &solvecraft_doc::kernel::EdgeInfo| e.points.windows(2).map(|w| at.dist_to_segment(w[0], w[1])).fold(f64::MAX, f64::min); + let d = |e: &solidcraft_doc::kernel::EdgeInfo| e.points.windows(2).map(|w| at.dist_to_segment(w[0], w[1])).fold(f64::MAX, f64::min); st.bodies .iter() .filter_map(|b| b.body.edges((b.body.size() * 2e-3).max(1e-3)).ok()) @@ -408,11 +408,11 @@ pub(super) fn snap_point(s: &Session, kind: &str, at: Vec3) -> Option { .map(|e| e.mid) } "face_center" => st.bodies.iter().find_map(|b| { - let i = solvecraft_doc::appearance::face_index_at(b, at)?; + let i = solidcraft_doc::appearance::face_index_at(b, at)?; b.body.faces((b.body.size() * 2e-3).max(1e-3)).ok()?.into_iter().find(|f| f.index == i).map(|f| f.centroid) }), "circle_center" => st.bodies.iter().find_map(|b| { - let c = solvecraft_doc::kernel::cylinder_face_at(&b.body, at)?; + let c = solidcraft_doc::kernel::cylinder_face_at(&b.body, at)?; let along = (at - c.axis_point).dot(c.axis); let end = if (along - c.start).abs() <= (c.end - along).abs() { c.start } else { c.end }; Some(c.axis_point + c.axis * end) diff --git a/crates/engine/src/cmd/features_more_tests.rs b/crates/engine/src/cmd/features_more_tests.rs index 6c15454d..7656e345 100644 --- a/crates/engine/src/cmd/features_more_tests.rs +++ b/crates/engine/src/cmd/features_more_tests.rs @@ -108,7 +108,7 @@ fn exact_cylinder_volume(s: &Session) -> f64 { #[test] fn cylinder_emboss_persists_selection_and_replays_radial_depth() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let mut s = cylinder_badge(); let before = exact_cylinder_volume(&s); run( @@ -124,10 +124,10 @@ fn cylinder_emboss_persists_selection_and_replays_radial_depth() { assert!(mesh.contains(Vec3::new(-10.5 * 0.15_f64.sin(), 10.5 * 0.15_f64.cos(), 7.5))); let feature = s.doc.find_feature("Raised Badge").unwrap(); assert!( - matches!(&feature.kind,solvecraft_doc::FeatureKind::Emboss{face:Some(face),..} if face.body=="Sleeve"&&face.at.dist(Vec3::new(0.,10.,7.5))<1e-10) + matches!(&feature.kind,solidcraft_doc::FeatureKind::Emboss{face:Some(face),..} if face.body=="Sleeve"&&face.at.dist(Vec3::new(0.,10.,7.5))<1e-10) ); let saved = s.doc.to_json(); - let mut loaded = Session { doc: solvecraft_doc::Document::from_json(&saved).unwrap().into(), ..Default::default() }; + let mut loaded = Session { doc: solidcraft_doc::Document::from_json(&saved).unwrap().into(), ..Default::default() }; loaded.refresh(); assert!(loaded.model.results.iter().all(|f| f.error.is_none()), "{:?}", loaded.model.results); assert!((exact_cylinder_volume(&loaded) - before - 21.0).abs() < 0.02); @@ -171,7 +171,7 @@ fn cylinder_small_chord_pick_is_saved_on_the_exact_surface() { .unwrap(); assert!(((pick.x * pick.x + pick.y * pick.y).sqrt() - 0.1).abs() > 1e-7); run(&mut s, "solid.emboss", json!({"sketch":"Badge","profiles":"all","face":[pick.x,pick.y,pick.z],"depth":"0.01mm","targets":["Tiny"]})); - let solvecraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &s.doc.features.last().unwrap().kind else { + let solidcraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &s.doc.features.last().unwrap().kind else { panic!("missing face"); }; assert!(((face.at.x * face.at.x + face.at.y * face.at.y).sqrt() - 0.1).abs() < 1e-12); @@ -186,7 +186,7 @@ fn cylinder_cross_component_emboss_keeps_source_owner_and_placements() { let a = run(&mut s, "component.create", json!({"name":"Source"}))["component"].as_u64().unwrap(); run(&mut s, "solid.cylinder", json!({"radius":10,"height":20,"body_name":"Sleeve"})); let source_feature = s.model.state().bodies.first().unwrap().feature; - let source_names = solvecraft_doc::naming::face_names(s.model.state().body("Sleeve").unwrap()); + let source_names = solidcraft_doc::naming::face_names(s.model.state().body("Sleeve").unwrap()); let before = exact_cylinder_volume(&s); run(&mut s, "occurrence.move", json!({"occurrence":"Source:1","axis":[0,0,1],"angle":"90 deg","translate":[50,0,0]})); let second = run(&mut s, "occurrence.copy", json!({"component":a,"translate":[80,0,0]}))["occurrence"].as_u64().unwrap(); @@ -204,7 +204,7 @@ fn cylinder_cross_component_emboss_keeps_source_owner_and_placements() { .world_state() .bodies .iter() - .find(|b| b.body.tessellate_with(0.001, true).unwrap().contains(solvecraft_geom::Vec3::new(40.5 + pick_offset, 0., 7.5))) + .find(|b| b.body.tessellate_with(0.001, true).unwrap().contains(solidcraft_geom::Vec3::new(40.5 + pick_offset, 0., 7.5))) .unwrap() .name .clone(); @@ -213,7 +213,7 @@ fn cylinder_cross_component_emboss_keeps_source_owner_and_placements() { "solid.emboss", json!({"sketch":"Badge","profiles":"all","face":[40.+pick_offset,0,7.5],"depth":"badge_depth","targets":[target.clone()]}), ); - let solvecraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &s.doc.features.last().unwrap().kind else { + let solidcraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &s.doc.features.last().unwrap().kind else { panic!("missing selection"); }; assert_eq!(face.path, if pick_offset == 0. { s.doc.first_path(a) } else { vec![second] }); @@ -228,9 +228,9 @@ fn cylinder_cross_component_emboss_keeps_source_owner_and_placements() { assert!((exact_cylinder_volume(&s) - before - 21.).abs() < 0.02); let world = s.world_state(); for x in [39.5, 119.5] { - assert!(world.bodies.iter().any(|b| b.body.tessellate_with(0.001, true).unwrap().contains(solvecraft_geom::Vec3::new(x, 0., 7.5)))); + assert!(world.bodies.iter().any(|b| b.body.tessellate_with(0.001, true).unwrap().contains(solidcraft_geom::Vec3::new(x, 0., 7.5)))); } - let mut loaded = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Default::default() }; + let mut loaded = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Default::default() }; loaded.refresh(); assert!(loaded.model.results.iter().all(|r| r.error.is_none()), "{:?}", loaded.model.results); run(&mut loaded, "parameters.change", json!({"name":"badge_depth","expression":"2mm"})); @@ -254,7 +254,7 @@ fn cylinder_cross_component_emboss_keeps_source_owner_and_placements() { assert!((exact_cylinder_volume(&loaded) - before - 69.).abs() < 0.02); let mut broken = loaded.doc.as_ref().clone(); let id = broken.features.last().unwrap().id; - let solvecraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &mut broken.feature_mut(id).unwrap().kind else { + let solidcraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &mut broken.feature_mut(id).unwrap().kind else { panic!("missing selection"); }; face.path = vec![u64::MAX]; @@ -283,12 +283,12 @@ fn check_active_second_source(auto: bool) { run(&mut s, "sketch.finish", json!({})); run(&mut s, "solid.emboss", json!({"sketch":"Badge","profiles":"all","face":[80,10,7.5],"depth":1})); assert!((exact_cylinder_volume(&s) - before - 21.).abs() < 0.02); - let solvecraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &s.doc.features.last().unwrap().kind else { + let solidcraft_doc::FeatureKind::Emboss { face: Some(face), .. } = &s.doc.features.last().unwrap().kind else { panic!("missing selection"); }; assert_eq!(face.path, vec![second]); assert_eq!(face.author_path, vec![second]); - let mut loaded = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Default::default() }; + let mut loaded = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Default::default() }; loaded.refresh(); assert!(loaded.model.results.iter().all(|r| r.error.is_none()), "{:?}", loaded.model.results); assert!((exact_cylinder_volume(&loaded) - before - 21.).abs() < 0.02); @@ -376,7 +376,7 @@ fn check_body_offset(auto: bool) { assert!((exact_cylinder_volume(&s) - before - 21.).abs() < 0.02); assert_eq!(s.doc.body_offsets["Sleeve"], offset); assert_eq!(s.doc.body_component("Sleeve", s.model.state().body("Sleeve").unwrap().feature), holder); - assert!(s.world_state().body("Sleeve").unwrap().body.tessellate_with(0.001, true).unwrap().contains(solvecraft_geom::Vec3::new(0., 10.5, 7.5))); + assert!(s.world_state().body("Sleeve").unwrap().body.tessellate_with(0.001, true).unwrap().contains(solidcraft_geom::Vec3::new(0., 10.5, 7.5))); } #[test] @@ -486,14 +486,14 @@ fn loft_cut_through_a_shelled_basin_in_either_order() { #[test] fn cylinder_auto_inference_saves_a_profile_anchor_and_replays_both_depth_signs() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; for mode in ["emboss", "deboss"] { let mut s = cylinder_badge(); let before = exact_cylinder_volume(&s); let initial = s.doc.to_json(); run(&mut s, "solid.emboss", json!({"sketch":"Badge","profiles":"all","depth":"badge_depth","mode":mode,"name":"Auto Badge"})); let feature = s.doc.find_feature("Auto Badge").unwrap().clone(); - let solvecraft_doc::FeatureKind::Emboss { face: Some(face), targets, .. } = &feature.kind else { + let solidcraft_doc::FeatureKind::Emboss { face: Some(face), targets, .. } = &feature.kind else { panic!("missing inferred association"); }; assert_eq!(targets, &["Sleeve"]); @@ -514,7 +514,7 @@ fn cylinder_auto_inference_saves_a_profile_anchor_and_replays_both_depth_signs() assert_eq!(s.doc.to_json(), initial); run(&mut s, "edit.redo", json!({})); assert_eq!(s.doc.find_feature("Auto Badge").unwrap().kind, feature.kind); - let mut loaded = Session { doc: solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Default::default() }; + let mut loaded = Session { doc: solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap().into(), ..Default::default() }; loaded.refresh(); assert!(loaded.model.results.iter().all(|r| r.error.is_none()), "{:?}", loaded.model.results); run( @@ -551,7 +551,7 @@ fn cylinder_auto_inference_ignores_remote_finite_tangent_walls_in_each_body_orde #[test] fn cylinder_auto_inference_retains_selected_holes_and_nested_islands() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; for mode in ["emboss", "deboss"] { let mut s = cylinder_badge(); run(&mut s, "sketch.edit", json!({"sketch":"Badge"})); @@ -584,7 +584,7 @@ fn cylinder_auto_inference_refuses_ambiguity_curves_and_seam_crossing_atomically assert!(error.contains("multiple tangent cylinders"), "{error}"); assert_eq!(s.doc.to_json(), before); run(&mut s, "solid.emboss", json!({"sketch":"Badge","depth":1,"targets":["Sleeve"]})); - assert!(matches!(&s.doc.features.last().unwrap().kind,solvecraft_doc::FeatureKind::Emboss{face:Some(face),..} if face.body=="Sleeve")); + assert!(matches!(&s.doc.features.last().unwrap().kind,solidcraft_doc::FeatureKind::Emboss{face:Some(face),..} if face.body=="Sleeve")); for circle in [false, true] { let mut s = cylinder_badge(); run(&mut s, "sketch.create", json!({"plane":{"origin":[0,10,0],"x_dir":[-1,0,0],"y_dir":[0,0,1]},"name":"Unsupported"})); diff --git a/crates/engine/src/cmd/file.rs b/crates/engine/src/cmd/file.rs index a3ada5e7..9d3d5ac1 100644 --- a/crates/engine/src/cmd/file.rs +++ b/crates/engine/src/cmd/file.rs @@ -1,8 +1,8 @@ //! Documents and files: new, open (designs and STEP), insert STEP, save, export. use serde_json::{Value, json}; -use solvecraft_doc::Document; -use solvecraft_io::Format; +use solidcraft_doc::Document; +use solidcraft_io::Format; use super::CommandSpec; use crate::params::{bad, bool_, str_, string_list}; @@ -14,7 +14,7 @@ pub static COMMANDS: &[CommandSpec] = &[ .icon("open") .key("Ctrl+O") .noundo() - .params("path: .solvecraft design, or a .step/.stp file (opens as a new design)"), + .params("path: .solidcraft design, or a .step/.stp file (opens as a new design)"), CommandSpec::new("file.insert_step", "Insert STEP", insert_step) .at("SOLID", "INSERT") .icon("import") @@ -50,9 +50,9 @@ fn path_arg<'a>(p: &'a Value, cmd: &str) -> Result<&'a str> { } /// Read a STEP file as an Import feature. -fn read_step(path: &str) -> Result { - let bytes = solvecraft_io::vfs::read(path, solvecraft_io::MAX_STEP_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; - solvecraft_io::step_import_feature(&bytes, path).map_err(|e| EngineError::Other(format!("{path}: {e}"))) +fn read_step(path: &str) -> Result { + let bytes = solidcraft_io::vfs::read(path, solidcraft_io::MAX_STEP_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::step_import_feature(&bytes, path).map_err(|e| EngineError::Other(format!("{path}: {e}"))) } /// Add an Import feature for a STEP file to the session's design. @@ -76,8 +76,8 @@ fn add_step(s: &mut Session, path: &str, name: Option<&str>) -> Result { /// Add a MeshImport feature for a 3MF or STL file. fn add_mesh(s: &mut Session, path: &str, name: Option<&str>) -> Result { - let bytes = solvecraft_io::vfs::read(path, solvecraft_io::MAX_3MF_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; - let mf = solvecraft_io::mesh_import_feature(&bytes, path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + let bytes = solidcraft_io::vfs::read(path, solidcraft_io::MAX_3MF_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + let mf = solidcraft_io::mesh_import_feature(&bytes, path).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; let fname = name.filter(|n| !n.trim().is_empty()).unwrap_or(&mf.name).to_string(); let id = s.doc_mut().add_feature(mf.kind, Some(&fname))?; if let Some(f) = s.doc_mut().feature_mut(id) { @@ -97,7 +97,7 @@ fn add_mesh(s: &mut Session, path: &str, name: Option<&str>) -> Result { fn insert_mesh(s: &mut Session, p: &Value) -> Result { let cmd = "file.insert_mesh"; let path = path_arg(p, cmd)?; - if !solvecraft_io::is_mesh_path(path) { + if !solidcraft_io::is_mesh_path(path) { return Err(bad(cmd, "only 3MF and STL files (.3mf, .stl) can be inserted as meshes")); } add_mesh(s, path, str_(p, "name")) @@ -106,7 +106,7 @@ fn insert_mesh(s: &mut Session, p: &Value) -> Result { fn insert_step(s: &mut Session, p: &Value) -> Result { let cmd = "file.insert_step"; let path = path_arg(p, cmd)?; - if !solvecraft_io::is_step_path(path) { + if !solidcraft_io::is_step_path(path) { return Err(bad(cmd, "only STEP files (.step, .stp) can be inserted")); } add_step(s, path, str_(p, "name")) @@ -114,18 +114,18 @@ fn insert_step(s: &mut Session, p: &Value) -> Result { fn open(s: &mut Session, p: &Value) -> Result { let path = path_arg(p, "doc.open")?; - if solvecraft_io::is_step_path(path) || solvecraft_io::is_mesh_path(path) { + if solidcraft_io::is_step_path(path) || solidcraft_io::is_mesh_path(path) { // A new, unsaved design holding the file's bodies. let stem = std::path::Path::new(path).file_stem().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| "Untitled".into()); let mut fresh = Session::new(Document::new(&stem)); - let r = if solvecraft_io::is_step_path(path) { add_step(&mut fresh, path, None)? } else { add_mesh(&mut fresh, path, None)? }; + let r = if solidcraft_io::is_step_path(path) { add_step(&mut fresh, path, None)? } else { add_mesh(&mut fresh, path, None)? }; fresh.undo.clear(); *s = fresh; let errors = s.model.results.iter().filter(|r| r.error.is_some()).count(); return Ok(json!({"path": path, "features": s.doc.features.len(), "errors": errors, "bodies": r["bodies"], "warnings": r["warnings"]})); } - let bytes = solvecraft_io::vfs::read(path, solvecraft_io::MAX_DESIGN_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; - let doc = solvecraft_io::read_design(&bytes)?; + let bytes = solidcraft_io::vfs::read(path, solidcraft_io::MAX_DESIGN_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + let doc = solidcraft_io::read_design(&bytes)?; *s = Session::new(doc); s.path = Some(path.to_string()); let errors = s.model.results.iter().filter(|r| r.error.is_some()).count(); @@ -133,9 +133,9 @@ fn open(s: &mut Session, p: &Value) -> Result { } fn write(s: &mut Session, path: &str) -> Result { - let bytes = solvecraft_io::write_design(&s.doc); + let bytes = solidcraft_io::write_design(&s.doc); // Atomic, keeping the version being replaced as `.bak`. - solvecraft_io::write_atomic(std::path::Path::new(path), &bytes, true).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::write_atomic(std::path::Path::new(path), &bytes, true).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; s.path = Some(path.to_string()); s.mark_saved(); Ok(json!({"path": path, "bytes": bytes.len()})) @@ -158,12 +158,12 @@ fn export_to(s: &Session, path: &str, format: Format, bodies: &[String]) -> Resu let name = std::path::Path::new(path).file_stem().map(|x| x.to_string_lossy().to_string()).unwrap_or_else(|| s.doc.name.clone()); // A design with components exports as a STEP assembly; otherwise bodies as placed. let bytes = if format == Format::Step && bodies.is_empty() && !s.doc.occurrences.is_empty() { - solvecraft_io::step_assembly(&s.doc, &s.doc.painted(&s.model.state()), &name)? + solidcraft_io::step_assembly(&s.doc, &s.doc.painted(&s.model.state()), &name)? } else { // Bodies carry the colour they are shown in (appearance, material or imported). - solvecraft_io::export(&s.doc.painted(&s.world_state()), bodies, format, &name)? + solidcraft_io::export(&s.doc.painted(&s.world_state()), bodies, format, &name)? }; - solvecraft_io::vfs::write(path, &bytes).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::vfs::write(path, &bytes).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; Ok(json!({"path": path, "bytes": bytes.len(), "format": format!("{format:?}")})) } diff --git a/crates/engine/src/cmd/inspect.rs b/crates/engine/src/cmd/inspect.rs index d50339b5..3fb72bb2 100644 --- a/crates/engine/src/cmd/inspect.rs +++ b/crates/engine/src/cmd/inspect.rs @@ -1,7 +1,7 @@ //! Inspection: measure bodies, describe the document, sketches and model topology. use serde_json::{Value, json}; -use solvecraft_sketch::CurveKind; +use solidcraft_sketch::CurveKind; use super::CommandSpec; use crate::params::{bad, bool_, str_, string_list}; @@ -38,8 +38,8 @@ pub static COMMANDS: &[CommandSpec] = &[ CommandSpec::new("engine.commands", "List Commands", commands).noundo(), ]; -pub fn measure_json(b: &solvecraft_doc::ModelBody) -> Value { - match solvecraft_kernel::measure(&b.body) { +pub fn measure_json(b: &solidcraft_doc::ModelBody) -> Value { + match solidcraft_kernel::measure(&b.body) { Ok(m) => json!({ "name": b.name, "volume_mm3": m.volume, @@ -234,7 +234,7 @@ fn inspect(s: &mut Session, p: &Value) -> Result { })) } -fn body_of<'a>(st: &'a solvecraft_doc::ModelState, p: &Value, cmd: &str) -> Result<&'a solvecraft_doc::ModelBody> { +fn body_of<'a>(st: &'a solidcraft_doc::ModelState, p: &Value, cmd: &str) -> Result<&'a solidcraft_doc::ModelBody> { let n = str_(p, "body").ok_or_else(|| bad(cmd, "`body` is required"))?; st.body(n).ok_or_else(|| bad(cmd, format!("no body `{n}`"))) } @@ -243,7 +243,7 @@ fn model_edges(s: &mut Session, p: &Value) -> Result { let st = s.world_state(); let b = body_of(&st, p, "model.edges")?; let tol = (b.body.size() * 2e-3).max(1e-3); - let names = solvecraft_doc::naming::edge_names(b); + let names = solidcraft_doc::naming::edge_names(b); let edges: Vec = b .body .edges(tol)? @@ -256,14 +256,14 @@ fn model_edges(s: &mut Session, p: &Value) -> Result { fn model_faces(s: &mut Session, p: &Value) -> Result { let st = s.world_state(); let b = body_of(&st, p, "model.faces")?; - let names = solvecraft_doc::naming::face_names(b); + let names = solidcraft_doc::naming::face_names(b); let tol = (b.body.size() * 2e-3).max(1e-3); let faces: Vec = b .body .faces(tol)? .iter() .map(|f| { - json!({"index": f.index, "name": names.get(f.index), "exact": solvecraft_doc::naming::face_name_exact(b, f.index), + json!({"index": f.index, "name": names.get(f.index), "exact": solidcraft_doc::naming::face_name_exact(b, f.index), "area": f.area, "centroid": f.centroid, "plane_normal": f.plane_normal}) }) .collect(); @@ -283,7 +283,7 @@ fn physical_material(s: &mut Session, p: &Value) -> Result { let cmd = "material.assign"; let bodies = string_list(p, "bodies"); let material = str_(p, "material").ok_or_else(|| bad(cmd, "`material` must be a material name"))?; - let canonical = solvecraft_doc::MATERIALS + let canonical = solidcraft_doc::MATERIALS .iter() .find(|(n, _)| n.eq_ignore_ascii_case(material.trim())) .map(|(n, _)| n.to_string()) @@ -318,7 +318,7 @@ fn color_param(v: &Value) -> Option<[u8; 3]> { } fn appearance(s: &mut Session, p: &Value) -> Result { - use solvecraft_doc::appearance::{FaceLook, Look, library}; + use solidcraft_doc::appearance::{FaceLook, Look, library}; let cmd = "appearance.assign"; let look: Option = match (str_(p, "appearance"), p.get("color")) { // A colour with a name: that colour, called that (a name not in the library is fine). @@ -352,14 +352,14 @@ fn appearance(s: &mut Session, p: &Value) -> Result { return Err(bad(cmd, "`bodies` must list existing bodies")); } // Faces: a point on each, on the named body or the nearest one. - let mut faces: Vec<(String, solvecraft_geom::Vec3)> = Vec::new(); + let mut faces: Vec<(String, solidcraft_geom::Vec3)> = Vec::new(); if let Some(list) = p.get("faces") { let list = list.as_array().filter(|a| a.len() <= 10_000).ok_or_else(|| bad(cmd, "`faces` must list points [x, y, z]"))?; for v in list { let pt = crate::params::vec3(v) .or_else(|| v.get("point").and_then(crate::params::vec3)) .ok_or_else(|| bad(cmd, "`faces` must list points [x, y, z]"))?; - let on = |b: &solvecraft_doc::ModelBody| solvecraft_doc::appearance::face_index_at(b, pt).is_some(); + let on = |b: &solidcraft_doc::ModelBody| solidcraft_doc::appearance::face_index_at(b, pt).is_some(); let body = match v.get("body").and_then(Value::as_str).or_else(|| str_(p, "body")) { Some(n) => st.body(n).filter(|b| on(b)).map(|b| b.name.clone()), None => st.bodies.iter().filter(|b| on(b)).min_by(|a, b| dist_to(a, pt).total_cmp(&dist_to(b, pt))).map(|b| b.name.clone()), @@ -381,11 +381,11 @@ fn appearance(s: &mut Session, p: &Value) -> Result { return Err(bad(cmd, "give `bodies`, `faces` or `components`")); } // Persistent names of the picked faces. - let st_names: Vec<(String, solvecraft_geom::Vec3, Option)> = faces + let st_names: Vec<(String, solidcraft_geom::Vec3, Option)> = faces .iter() .map(|(b, pt)| { let n = st.body(b).and_then(|mb| { - solvecraft_doc::appearance::face_index_at(mb, *pt).and_then(|i| solvecraft_doc::naming::face_names(mb).get(i).cloned()) + solidcraft_doc::appearance::face_index_at(mb, *pt).and_then(|i| solidcraft_doc::naming::face_names(mb).get(i).cloned()) }); (b.clone(), *pt, n) }) @@ -423,20 +423,20 @@ fn appearance(s: &mut Session, p: &Value) -> Result { .map(|(b, pt)| { ( b.clone(), - st.body(b).and_then(|mb| solvecraft_doc::appearance::face_index_at(mb, *pt)), + st.body(b).and_then(|mb| solidcraft_doc::appearance::face_index_at(mb, *pt)), st_names.iter().find(|(name, p, _)| name == b && p.dist(*pt) < 1e-12).and_then(|(_, _, name)| name.clone()), ) }) .collect(); doc.appearances.faces.retain(|f| { - let idx = st.body(&f.body).and_then(|mb| solvecraft_doc::appearance::face_index_at(mb, f.point)); + let idx = st.body(&f.body).and_then(|mb| solidcraft_doc::appearance::face_index_at(mb, f.point)); !picked.iter().any(|(b, i, name)| { *b == f.body && (f .name .as_ref() .zip(name.as_ref()) - .is_some_and(|(a, b)| solvecraft_doc::naming::strip_piece(a) == solvecraft_doc::naming::strip_piece(b)) + .is_some_and(|(a, b)| solidcraft_doc::naming::strip_piece(a) == solidcraft_doc::naming::strip_piece(b)) || (!f.strict_name && i.is_some() && *i == idx)) }) }); @@ -449,18 +449,18 @@ fn appearance(s: &mut Session, p: &Value) -> Result { })) } -fn dist_to(b: &solvecraft_doc::ModelBody, p: solvecraft_geom::Vec3) -> f64 { +fn dist_to(b: &solidcraft_doc::ModelBody, p: solidcraft_geom::Vec3) -> f64 { let bb = b.mesh().bounds(); - let c = solvecraft_geom::Vec3::new(p.x.clamp(bb.min.x, bb.max.x), p.y.clamp(bb.min.y, bb.max.y), p.z.clamp(bb.min.z, bb.max.z)); + let c = solidcraft_geom::Vec3::new(p.x.clamp(bb.min.x, bb.max.x), p.y.clamp(bb.min.y, bb.max.y), p.z.clamp(bb.min.z, bb.max.z)); c.dist(p) } -fn look_json(l: &solvecraft_doc::appearance::Look) -> Value { +fn look_json(l: &solidcraft_doc::appearance::Look) -> Value { json!({"name": l.name, "color": l.hex(), "opacity": l.opacity}) } fn appearance_library(_s: &mut Session, _p: &Value) -> Result { - let list: Vec = solvecraft_doc::appearance::LIBRARY + let list: Vec = solidcraft_doc::appearance::LIBRARY .iter() .map(|(n, c, o)| { let category = n.split(" - ").next().unwrap_or(n); @@ -473,8 +473,8 @@ fn appearance_library(_s: &mut Session, _p: &Value) -> Result { fn appearance_list(s: &mut Session, p: &Value) -> Result { let a = &s.doc.appearances; // "In this design": each look used, with where. - let mut used: Vec<(solvecraft_doc::appearance::Look, Vec, Vec, Vec)> = Vec::new(); - fn slot(used: &mut Vec<(solvecraft_doc::appearance::Look, Vec, Vec, Vec)>, l: &solvecraft_doc::appearance::Look) -> usize { + let mut used: Vec<(solidcraft_doc::appearance::Look, Vec, Vec, Vec)> = Vec::new(); + fn slot(used: &mut Vec<(solidcraft_doc::appearance::Look, Vec, Vec, Vec)>, l: &solidcraft_doc::appearance::Look) -> usize { match used.iter().position(|(u, ..)| u == l) { Some(i) => i, None => { @@ -525,5 +525,5 @@ fn appearance_list(s: &mut Session, p: &Value) -> Result { } fn material_list(_s: &mut Session, _p: &Value) -> Result { - Ok(json!({"materials": solvecraft_doc::MATERIALS.iter().map(|(n, d)| json!({"name": n, "density_g_cm3": d})).collect::>()})) + Ok(json!({"materials": solidcraft_doc::MATERIALS.iter().map(|(n, d)| json!({"name": n, "density_g_cm3": d})).collect::>()})) } diff --git a/crates/engine/src/cmd/joints.rs b/crates/engine/src/cmd/joints.rs index 6a857611..b3fd4593 100644 --- a/crates/engine/src/cmd/joints.rs +++ b/crates/engine/src/cmd/joints.rs @@ -1,12 +1,12 @@ -//! Joints between occurrences (`solvecraft_doc::joints`): Joint Origin, Joint, As-built Joint, +//! Joints between occurrences (`solidcraft_doc::joints`): Joint Origin, Joint, As-built Joint, //! Rigid Group, Drive Joints, Motion Link, limits, and Interference. Every change re-solves the //! joint graph and moves the occurrences it places; the session re-solves after any edit too, //! so joints follow the geometry their origins snap to. use serde_json::{Value, json}; -use solvecraft_doc::expr::Kind; -use solvecraft_doc::joints::{self, Joint, JointKind, JointOrigin, MotionLink, NamedOrigin, Snap}; -use solvecraft_geom::Vec3; +use solidcraft_doc::expr::Kind; +use solidcraft_doc::joints::{self, Joint, JointKind, JointOrigin, MotionLink, NamedOrigin, Snap}; +use solidcraft_geom::Vec3; use super::CommandSpec; use crate::params::{bad, bool_, num, str_, string_list, vec3}; @@ -88,9 +88,9 @@ pub(crate) fn occurrence_at(s: &Session, p: Vec3) -> Option { let comp = s.doc.model_body_component(b); let occs: Vec = if comp == 0 { vec![0] } else { s.doc.occurrences.iter().filter(|o| o.component == comp).map(|o| o.id).collect() }; for occ in occs { - let t = if occ == 0 { solvecraft_doc::IDENTITY } else { joints::occurrence_world(&s.doc, occ) }; - let inv = solvecraft_doc::mat_inverse(&t)?; - let q = solvecraft_doc::apply_point(&inv, p); + let t = if occ == 0 { solidcraft_doc::IDENTITY } else { joints::occurrence_world(&s.doc, occ) }; + let inv = solidcraft_doc::mat_inverse(&t)?; + let q = solidcraft_doc::apply_point(&inv, p); let bb = b.mesh().bounds(); let c = Vec3::new(q.x.clamp(bb.min.x, bb.max.x), q.y.clamp(bb.min.y, bb.max.y), q.z.clamp(bb.min.z, bb.max.z)); let d = c.dist(q); @@ -126,10 +126,10 @@ fn origin_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result occurrence_at(s, world).ok_or_else(|| bad(cmd, "no body there"))?, }; // World → the component's own coordinates. - let t = if occurrence == 0 { solvecraft_doc::IDENTITY } else { joints::occurrence_world(&s.doc, occurrence) }; - let inv = solvecraft_doc::mat_inverse(&t).ok_or_else(|| bad(cmd, "occurrence transform"))?; - let local = |p: Vec3| solvecraft_doc::apply_point(&inv, p); - let local_dir = |d: Vec3| solvecraft_doc::apply_vector(&inv, d); + let t = if occurrence == 0 { solidcraft_doc::IDENTITY } else { joints::occurrence_world(&s.doc, occurrence) }; + let inv = solidcraft_doc::mat_inverse(&t).ok_or_else(|| bad(cmd, "occurrence transform"))?; + let local = |p: Vec3| solidcraft_doc::apply_point(&inv, p); + let local_dir = |d: Vec3| solidcraft_doc::apply_vector(&inv, d); let snap = match what { "face" | "circle" => { let pick = local(world); @@ -139,7 +139,7 @@ fn origin_param(s: &Session, v: Option<&Value>, cmd: &str) -> Result Snap::Point { point: local(world), z: v.get("z").and_then(vec3).map(local_dir) }, @@ -526,7 +526,7 @@ fn interference(s: &mut Session, p: &Value) -> Result { let cmd = "inspect.interference"; let want = string_list(p, "bodies"); let st = s.world_state(); - let bodies: Vec<&solvecraft_doc::ModelBody> = + let bodies: Vec<&solidcraft_doc::ModelBody> = st.bodies.iter().filter(|b| want.is_empty() || want.contains(&b.name)).filter(|b| !b.body.is_mesh()).collect(); if bodies.len() > 200 { return Err(bad(cmd, "too many bodies (200 at most)")); @@ -544,8 +544,8 @@ fn interference(s: &mut Session, p: &Value) -> Result { if apart { continue; } - let Ok(Some(o)) = solvecraft_kernel::boolean(&a.body, &b.body, solvecraft_kernel::BoolOp::Intersect) else { continue }; - let Ok(m) = solvecraft_kernel::measure(&o) else { continue }; + let Ok(Some(o)) = solidcraft_kernel::boolean(&a.body, &b.body, solidcraft_kernel::BoolOp::Intersect) else { continue }; + let Ok(m) = solidcraft_kernel::measure(&o) else { continue }; if m.volume > 1e-9 * a.body.size().max(1.0).powi(3) { hits.push(json!({"a": a.name, "b": b.name, "volume_mm3": m.volume, "area_mm2": m.area, "center": m.centroid, "bbox": {"min": m.bbox.min, "max": m.bbox.max}})); } @@ -559,8 +559,8 @@ fn interference(s: &mut Session, p: &Value) -> Result { mod tests; /// A point in the middle of the cylindrical face at `pick` (on its wall, halfway along). -fn cylinder_wall_point(b: &solvecraft_doc::kernel::Body, pick: Vec3) -> Option { - let c = solvecraft_doc::kernel::cylinder_face_at(b, pick)?; +fn cylinder_wall_point(b: &solidcraft_doc::kernel::Body, pick: Vec3) -> Option { + let c = solidcraft_doc::kernel::cylinder_face_at(b, pick)?; let v = pick - c.axis_point; let along = v.dot(c.axis); let radial = (v - c.axis * along).normalized()?; diff --git a/crates/engine/src/cmd/joints_tests.rs b/crates/engine/src/cmd/joints_tests.rs index 0e46e79b..11a905d3 100644 --- a/crates/engine/src/cmd/joints_tests.rs +++ b/crates/engine/src/cmd/joints_tests.rs @@ -1,7 +1,7 @@ use std::f64::consts::FRAC_PI_2; use serde_json::{Value, json}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use crate::Session; @@ -18,7 +18,7 @@ fn occ(s: &Session, component: &str) -> u64 { } fn place(s: &Session, component: &str, p: Vec3) -> Vec3 { - solvecraft_doc::apply_point(&solvecraft_doc::joints::occurrence_world(&s.doc, occ(s, component)), p) + solidcraft_doc::apply_point(&solidcraft_doc::joints::occurrence_world(&s.doc, occ(s, component)), p) } fn near(a: Vec3, b: Vec3) -> bool { @@ -55,9 +55,9 @@ fn revolute_hinge_turns_about_the_knuckle() { assert_eq!(dof, 1, "{l}"); // Drive 90°: B turns about the knuckle axis (through (0, 33, 4), along the frame's z = −X). run(&mut s, "joint.drive", json!({"joint": "Hinge", "value": 90})); - let want = solvecraft_doc::rigid(Vec3::ZERO, Vec3::new(0.0, 33.0, 4.0), Vec3::new(-1.0, 0.0, 0.0), FRAC_PI_2); + let want = solidcraft_doc::rigid(Vec3::ZERO, Vec3::new(0.0, 33.0, 4.0), Vec3::new(-1.0, 0.0, 0.0), FRAC_PI_2); let p = Vec3::new(20.0, 0.0, 0.0); - assert!(near(place(&s, "LeafB", p), solvecraft_doc::apply_point(&want, p)), "{:?}", place(&s, "LeafB", p)); + assert!(near(place(&s, "LeafB", p), solidcraft_doc::apply_point(&want, p)), "{:?}", place(&s, "LeafB", p)); assert!(near(place(&s, "LeafB", p), Vec3::new(20.0, 29.0, 37.0))); // LeafA did not move; the knuckle axis is fixed. assert!(near(place(&s, "LeafA", p), p)); @@ -108,7 +108,7 @@ fn slider_limits_clamp() { .doc .features .iter() - .find(|f| matches!(f.kind, solvecraft_doc::FeatureKind::Box { .. })) + .find(|f| matches!(f.kind, solidcraft_doc::FeatureKind::Box { .. })) .and_then(|f| f.param_names.get(2).cloned()) .unwrap_or_default(); run(&mut s, "parameters.change", json!({"name": h, "expression": "15"})); @@ -176,7 +176,7 @@ fn motion_links_and_interference() { run(&mut s, "joint.drive", json!({"joint": "Screw", "values": [0, 3]})); assert!(near(place(&s, "D", Vec3::new(65.0, 5.0, 0.0)), Vec3::new(5.0, 5.0, 16.0)), "{:?}", place(&s, "D", Vec3::new(65.0, 5.0, 0.0))); // Saved and reopened, joints come back. - let back = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); + let back = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); assert_eq!(back.assembly, s.doc.assembly); let mut t = Session::new(back); let l = run(&mut t, "joint.list", json!({})); diff --git a/crates/engine/src/cmd/measure_sel.rs b/crates/engine/src/cmd/measure_sel.rs index 9c978dfc..4ad82745 100644 --- a/crates/engine/src/cmd/measure_sel.rs +++ b/crates/engine/src/cmd/measure_sel.rs @@ -3,7 +3,7 @@ //! points) and, where both have a direction (planar faces, straight edges), the angle. use serde_json::{Value, json}; -use solvecraft_geom::{Mesh, Vec3}; +use solidcraft_geom::{Mesh, Vec3}; use crate::params::bad; use crate::{Result, Sel, Session}; diff --git a/crates/engine/src/cmd/mesh.rs b/crates/engine/src/cmd/mesh.rs index 1e72e889..71361b65 100644 --- a/crates/engine/src/cmd/mesh.rs +++ b/crates/engine/src/cmd/mesh.rs @@ -1,8 +1,8 @@ //! Parametric reconstruction of imported mechanical mesh bodies. use serde_json::Value; -use solvecraft_doc::FeatureKind; -use solvecraft_doc::expr::Kind; +use solidcraft_doc::FeatureKind; +use solidcraft_doc::expr::Kind; use super::{ CommandSpec, diff --git a/crates/engine/src/cmd/mod.rs b/crates/engine/src/cmd/mod.rs index 3a892ae3..797972da 100644 --- a/crates/engine/src/cmd/mod.rs +++ b/crates/engine/src/cmd/mod.rs @@ -1,4 +1,4 @@ -//! The command registry. Ids are SolveCraft's own dotted ids (`solid.extrude`, +//! The command registry. Ids are SolidCraft's own dotted ids (`solid.extrude`, //! `sketch.rectangle.two_point`); the toolbar placement (`tab`, `panel`) follows the familiar //! Design workspace layout. diff --git a/crates/engine/src/cmd/motion.rs b/crates/engine/src/cmd/motion.rs index 367e942e..ab9b42f1 100644 --- a/crates/engine/src/cmd/motion.rs +++ b/crates/engine/src/cmd/motion.rs @@ -1,11 +1,11 @@ //! Contact Sets, Motion Study and Exploded views. use serde_json::{Value, json}; -use solvecraft_doc::Mat; -use solvecraft_doc::joints::{self, JointKind}; -use solvecraft_doc::motion::{ContactSet, ExplodedView, MotionStudy, StudyKey}; -use solvecraft_geom::Vec3; -use solvecraft_kernel::Body; +use solidcraft_doc::Mat; +use solidcraft_doc::joints::{self, JointKind}; +use solidcraft_doc::motion::{ContactSet, ExplodedView, MotionStudy, StudyKey}; +use solidcraft_geom::Vec3; +use solidcraft_kernel::Body; use super::CommandSpec; use super::joints::{joint_id, occurrence_id, resolve, value}; @@ -93,8 +93,8 @@ fn placed(s: &Session) -> Vec<(Vec, Body)> { // Moving a body into another component preserves its world placement using an // offset from its authoring frame. Contact and explode centres must use that // same offset as display and physical properties. - let frame = s.doc.model_body_offset(b).map(|offset| solvecraft_doc::mat_mul(&m, offset)).unwrap_or(m); - if let Ok(t) = solvecraft_kernel::transform_matrix(&b.body, frame) { + let frame = s.doc.model_body_offset(b).map(|offset| solidcraft_doc::mat_mul(&m, offset)).unwrap_or(m); + if let Ok(t) = solidcraft_kernel::transform_matrix(&b.body, frame) { out.push((path.clone(), t)); } } @@ -141,7 +141,7 @@ fn overlap(a: &Body, b: &Body) -> Option { mesh_overlap(&ma, &mb, 1e-4 * a.size().max(b.size()).max(1.0)) } -fn mesh_overlap(ma: &solvecraft_geom::Mesh, mb: &solvecraft_geom::Mesh, tol: f64) -> Option { +fn mesh_overlap(ma: &solidcraft_geom::Mesh, mb: &solidcraft_geom::Mesh, tol: f64) -> Option { let (ba, bb) = (ma.bounds(), mb.bounds()); if ba.max.x < bb.min.x + tol || bb.max.x < ba.min.x + tol @@ -156,12 +156,12 @@ fn mesh_overlap(ma: &solvecraft_geom::Mesh, mb: &solvecraft_geom::Mesh, tol: f64 (depth > tol).then_some(depth) } -fn tris(m: &solvecraft_geom::Mesh) -> Vec<[Vec3; 3]> { +fn tris(m: &solidcraft_geom::Mesh) -> Vec<[Vec3; 3]> { m.triangles.iter().filter_map(|t| m.tri(t)).collect() } /// The deepest any sample point of `a` lies inside `b` (0 if none). -fn deepest(a: &solvecraft_geom::Mesh, b: &solvecraft_geom::Mesh, tol: f64) -> f64 { +fn deepest(a: &solidcraft_geom::Mesh, b: &solidcraft_geom::Mesh, tol: f64) -> f64 { let tb = tris(b); let bx = b.bounds(); let mut pts: Vec = a.positions.clone(); @@ -589,7 +589,7 @@ fn export(s: &mut Session, p: &Value) -> Result { } else { serde_json::to_string_pretty(&out).unwrap_or_default() }; - solvecraft_io::write_atomic(std::path::Path::new(path), text.as_bytes(), false).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::write_atomic(std::path::Path::new(path), text.as_bytes(), false).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; return Ok(json!({"path": path, "samples": rows.len()})); } Ok(out) @@ -715,7 +715,7 @@ fn explode_delete(s: &mut Session, p: &Value) -> Result { /// World position of a point of an occurrence (tests). #[cfg(test)] pub fn world_point(s: &Session, occurrence: u64, p: Vec3) -> Vec3 { - solvecraft_doc::apply_point(&joints::occurrence_world(&s.doc, occurrence), p) + solidcraft_doc::apply_point(&joints::occurrence_world(&s.doc, occurrence), p) } #[cfg(test)] diff --git a/crates/engine/src/cmd/motion_tests.rs b/crates/engine/src/cmd/motion_tests.rs index 866adbe7..449448b1 100644 --- a/crates/engine/src/cmd/motion_tests.rs +++ b/crates/engine/src/cmd/motion_tests.rs @@ -1,5 +1,5 @@ use serde_json::{Value, json}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use crate::Session; @@ -130,7 +130,7 @@ fn motion_study_plays_and_exports() { // The B occurrence's origin moves by the slider value (it starts at 0 offset with value 0 at 10 mm… so 10 → −5). assert!((bx(&rows[1]) - bx(&rows[0]) + 3.75).abs() < 1e-6, "{e}"); assert!(rows[4]["stopped_by_contact"].is_object()); - let dir = std::env::temp_dir().join(format!("solvecraft-motion-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("solidcraft-motion-{}", std::process::id())); std::fs::create_dir_all(&dir).unwrap(); let csv = dir.join("push.csv"); run(&mut s, "motion.export", json!({"study": "Push", "samples": 3, "path": csv.to_string_lossy()})); diff --git a/crates/engine/src/cmd/occurrence_tests.rs b/crates/engine/src/cmd/occurrence_tests.rs index 515c9cb9..530d4ae5 100644 --- a/crates/engine/src/cmd/occurrence_tests.rs +++ b/crates/engine/src/cmd/occurrence_tests.rs @@ -190,8 +190,8 @@ fn duplicate_nested_labels_use_full_path_and_reopen_undo_preserve_placements() { let (parent_component, parent) = created(&mut s, "Parent"); let (_, child) = created(&mut s, "Child"); run(&mut s, "solid.box", json!({"length":1,"width":1,"height":1})); - let parent2 = s.doc_mut().add_occurrence(parent_component, 0, solvecraft_doc::IDENTITY).unwrap(); - let parent3 = s.doc_mut().add_occurrence(parent_component, 0, solvecraft_doc::IDENTITY).unwrap(); + let parent2 = s.doc_mut().add_occurrence(parent_component, 0, solidcraft_doc::IDENTITY).unwrap(); + let parent3 = s.doc_mut().add_occurrence(parent_component, 0, solidcraft_doc::IDENTITY).unwrap(); for o in s.doc_mut().occurrences.iter_mut().filter(|o| o.id == parent2 || o.id == parent3) { o.name = "Same".into(); } @@ -199,7 +199,7 @@ fn duplicate_nested_labels_use_full_path_and_reopen_undo_preserve_placements() { let a = s.occurrence_bodies(&[parent2, child], false).unwrap()[0].clone(); let b = s.occurrence_bodies(&[parent3, child], false).unwrap()[0].clone(); assert_ne!(a, b); - let mut reopened = Session::new(solvecraft_doc::format::read(&s.doc.to_json()).unwrap()); + let mut reopened = Session::new(solidcraft_doc::format::read(&s.doc.to_json()).unwrap()); assert_eq!(reopened.occurrence_bodies(&[parent2, child], false).unwrap()[0], a); assert_eq!(reopened.occurrence_bodies(&[parent3, child], false).unwrap()[0], b); run(&mut reopened, "selection.delete", json!({"occurrences":[parent3]})); @@ -218,7 +218,7 @@ fn compact_duplicated_assembly_exact_resolution_and_expansion_limit() { let component = s.doc_mut().add_component(Some(&format!("Level{i}")), parent).unwrap(); let occurrence = s.doc.occurrence_of(component).unwrap().id; path.push(occurrence); - s.doc_mut().add_occurrence(component, parent, solvecraft_doc::IDENTITY).unwrap(); + s.doc_mut().add_occurrence(component, parent, solidcraft_doc::IDENTITY).unwrap(); parent = component; } assert!(s.occurrence_placement(&path).is_ok()); @@ -326,7 +326,7 @@ fn isolate_nested_child_hides_ancestor_material_and_sibling() { .add_occurrence( child_component, parent_component, - solvecraft_doc::rigid(crate::geom::Vec3::new(20.0, 0.0, 0.0), crate::geom::Vec3::ZERO, crate::geom::Vec3::Z, 0.0), + solidcraft_doc::rigid(crate::geom::Vec3::new(20.0, 0.0, 0.0), crate::geom::Vec3::ZERO, crate::geom::Vec3::Z, 0.0), ) .unwrap(); s.refresh(); @@ -412,14 +412,14 @@ fn instance_styles_follow_source_identity_after_collision_rename_and_pending_mov assert!((measured["bodies"][0]["mass_g"].as_f64().unwrap() - 0.068).abs() < 1e-8); } let definition = s.model.state(); - assert_eq!(solvecraft_kernel::measure(&definition.body("Shared Renamed").unwrap().body).unwrap().bbox.min, crate::geom::Vec3::ZERO); + assert_eq!(solidcraft_kernel::measure(&definition.body("Shared Renamed").unwrap().body).unwrap().bbox.min, crate::geom::Vec3::ZERO); let painted = s.doc.painted(&world); let secondname = s.occurrence_bodies(&[second], false).unwrap()[0].clone(); let body = painted.body(&secondname).unwrap(); assert_eq!(body.body.color(), Some([1.0, 0.0, 0.0])); assert!(body.body.paint().unwrap().faces.iter().any(|f| f.color == [0.0, 1.0, 0.0])); - let bytes = solvecraft_io::export(&painted, &[secondname], solvecraft_io::Format::Step, "InstanceStyles").unwrap(); - let imported = solvecraft_kernel::step_import(&String::from_utf8(bytes).unwrap()).unwrap(); + let bytes = solidcraft_io::export(&painted, &[secondname], solidcraft_io::Format::Step, "InstanceStyles").unwrap(); + let imported = solidcraft_kernel::step_import(&String::from_utf8(bytes).unwrap()).unwrap(); assert_eq!(imported.bodies.len(), 1); assert_eq!(imported.bodies[0].body.color(), Some([1.0, 0.0, 0.0])); assert!(imported.bodies[0].body.paint().unwrap().faces.iter().any(|f| f.color == [0.0, 1.0, 0.0])); diff --git a/crates/engine/src/cmd/offset_edit_guard_tests.rs b/crates/engine/src/cmd/offset_edit_guard_tests.rs index ec535c09..87187cb6 100644 --- a/crates/engine/src/cmd/offset_edit_guard_tests.rs +++ b/crates/engine/src/cmd/offset_edit_guard_tests.rs @@ -1,8 +1,8 @@ #![allow(clippy::unwrap_used, clippy::panic)] use serde_json::{Value, json}; -use solvecraft_doc::{Document, FeatureKind, ProfileSel}; -use solvecraft_geom::Vec2; -use solvecraft_sketch::{CurveKind, Sketch}; +use solidcraft_doc::{Document, FeatureKind, ProfileSel}; +use solidcraft_geom::Vec2; +use solidcraft_sketch::{CurveKind, Sketch}; use crate::{Session, Snapshot}; diff --git a/crates/engine/src/cmd/offset_identity_tests.rs b/crates/engine/src/cmd/offset_identity_tests.rs index 90be0ab9..df7aba63 100644 --- a/crates/engine/src/cmd/offset_identity_tests.rs +++ b/crates/engine/src/cmd/offset_identity_tests.rs @@ -1,6 +1,6 @@ #![allow(clippy::unwrap_used, clippy::panic)] use serde_json::{Value, json}; -use solvecraft_doc::{Document, FeatureKind, ProfileSel}; +use solidcraft_doc::{Document, FeatureKind, ProfileSel}; use crate::Session; @@ -26,7 +26,7 @@ fn rounded_offset(radius: f64) -> (Session, u64, String, String) { fn volume(session: &Session, feature: u64) -> f64 { let state = session.model.state(); let body = state.bodies.iter().find(|body| body.feature == feature).unwrap(); - solvecraft_kernel::measure(&body.body).unwrap().volume + solidcraft_kernel::measure(&body.body).unwrap().volume } fn healthy(session: &Session) { @@ -41,13 +41,13 @@ fn legacy_offset_capture_persists_reserved_curves_without_a_transient_associatio let reserved = raw.offsets[0].outputs.clone(); assert_eq!(reserved.len(), 8); raw.offsets[0].topology = None; - document.format = "solvecraft/11".into(); + document.format = "solidcraft/11".into(); let mut session = Session::new(Document::from_json(&document.to_json()).unwrap()); assert!(session.doc.sketch(sketch).unwrap().offsets[0].topology.is_none()); let state = session.model.state(); let solved = state.sketch(sketch).unwrap(); assert!(solved.sketch.offsets[0].topology.is_some(), "legacy upgrade belongs only to the evaluated copy"); - let index = solvecraft_doc::profile_selection_indices(solved, &ProfileSel::Curves { loops: vec![reserved.clone()] }).unwrap()[0]; + let index = solidcraft_doc::profile_selection_indices(solved, &ProfileSel::Curves { loops: vec![reserved.clone()] }).unwrap()[0]; let extrusion = run(&mut session, "solid.extrude", json!({"sketch":sketch,"profiles":[index],"distance":2,"name":"Legacy inner"}))["feature"] .as_u64() .unwrap(); diff --git a/crates/engine/src/cmd/offset_plane_face_tests.rs b/crates/engine/src/cmd/offset_plane_face_tests.rs index 148c62ff..7985ce64 100644 --- a/crates/engine/src/cmd/offset_plane_face_tests.rs +++ b/crates/engine/src/cmd/offset_plane_face_tests.rs @@ -1,9 +1,9 @@ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)] use crate::Session; use serde_json::{Value, json}; -use solvecraft_doc::construct::ConstructGeom; -use solvecraft_doc::{Document, FeatureKind, PlaneRef}; -use solvecraft_geom::Plane; +use solidcraft_doc::construct::ConstructGeom; +use solidcraft_doc::{Document, FeatureKind, PlaneRef}; +use solidcraft_geom::Plane; fn run(s: &mut Session, command: &str, params: Value) -> Value { s.execute(command, ¶ms).unwrap_or_else(|e| panic!("{command} {params}: {e}")) @@ -18,7 +18,7 @@ fn plane(s: &Session, name: &str) -> Plane { } fn volume(s: &Session) -> f64 { - s.model.state().bodies.iter().map(|b| solvecraft_kernel::measure(&b.body).unwrap().volume).sum() + s.model.state().bodies.iter().map(|b| solidcraft_kernel::measure(&b.body).unwrap().volume).sum() } #[test] diff --git a/crates/engine/src/cmd/offset_topology_tests.rs b/crates/engine/src/cmd/offset_topology_tests.rs index e1f01eed..1d418e1f 100644 --- a/crates/engine/src/cmd/offset_topology_tests.rs +++ b/crates/engine/src/cmd/offset_topology_tests.rs @@ -1,7 +1,7 @@ #![allow(clippy::unwrap_used, clippy::panic)] use serde_json::{Value, json}; -use solvecraft_doc::{FeatureKind, ProfileSel}; -use solvecraft_geom::Vec3; +use solidcraft_doc::{FeatureKind, ProfileSel}; +use solidcraft_geom::Vec3; use crate::Session; @@ -37,7 +37,7 @@ fn breaking_offset_preserves_downstream_material_history_and_source_independence run(&mut session, "parameters.change", json!({"name":radius,"expression":"2 mm"})); assert!(session.model.results.iter().all(|result| result.error.is_none())); assert!((session.world_state().bodies.last().unwrap().mesh().measure().volume.abs() - before_volume).abs() < 1e-6); - let reopened = Session::new(solvecraft_doc::Document::from_json(&session.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&session.doc.to_json()).unwrap()); assert!(reopened.model.results.iter().all(|result| result.error.is_none())); assert!((reopened.world_state().bodies.last().unwrap().mesh().measure().volume.abs() - before_volume).abs() < 1e-6); } @@ -87,7 +87,7 @@ fn inset_past_corner_radius_creates_a_real_boundary_and_saved_profile_identity() }; check(&session); let saved = session.doc.to_json(); - let reopened = Session::new(solvecraft_doc::Document::from_json(&saved).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&saved).unwrap()); check(&reopened); assert_eq!(reopened.doc.to_json(), saved); let undo = session.undo.len(); diff --git a/crates/engine/src/cmd/parameters.rs b/crates/engine/src/cmd/parameters.rs index 69381cb8..aa3c2147 100644 --- a/crates/engine/src/cmd/parameters.rs +++ b/crates/engine/src/cmd/parameters.rs @@ -3,7 +3,7 @@ //! graph, CSV/JSON import and export, expression evaluation and autocomplete. use serde_json::{Value, json}; -use solvecraft_doc::expr::{self, Kind}; +use solidcraft_doc::expr::{self, Kind}; use super::CommandSpec; use crate::params::{bad, bool_, str_}; @@ -123,7 +123,7 @@ fn list(s: &mut Session, p: &Value) -> Result { (_, Some(f)) => f.to_ascii_lowercase(), _ => "all".to_string(), }; - let mut rows: Vec = s + let mut rows: Vec = s .doc .param_rows() .into_iter() @@ -136,7 +136,7 @@ fn list(s: &mut Session, p: &Value) -> Result { .collect(); // Favourites first; otherwise the table order (user, sketch, feature). rows.sort_by_key(|r| !r.favorite); - let cycles = solvecraft_doc::cycles(&s.doc.all_param_exprs()); + let cycles = solidcraft_doc::cycles(&s.doc.all_param_exprs()); Ok(json!({"parameters": rows, "count": rows.len(), "cycles": cycles})) } @@ -203,7 +203,7 @@ fn graph(s: &mut Session, _p: &Value) -> Result { let (_, errs) = s.doc.param_values(); let nodes: Vec = s.doc.param_defs().into_iter().map(|d| d.name).collect(); let edges: Vec<[String; 2]> = s.doc.param_graph().into_iter().map(|(a, b)| [a, b]).collect(); - let cycles = solvecraft_doc::cycles(&s.doc.all_param_exprs()); + let cycles = solidcraft_doc::cycles(&s.doc.all_param_exprs()); Ok(json!({"nodes": nodes, "edges": edges, "cycles": cycles, "errors": errs})) } @@ -229,7 +229,7 @@ fn export(s: &mut Session, p: &Value) -> Result { other => return Err(bad(cmd, format!("unknown format `{other}` (csv or json)"))), }; if let Some(path) = path { - solvecraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; } Ok(json!({"format": format, "path": path, "text": text})) } @@ -242,7 +242,7 @@ fn import(s: &mut Session, p: &Value) -> Result { let text = match (str_(p, "text"), str_(p, "path")) { (Some(t), _) => t.to_string(), (None, Some(path)) => { - solvecraft_io::vfs::read_to_string(path, MAX_PARAMETER_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))? + solidcraft_io::vfs::read_to_string(path, MAX_PARAMETER_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))? } _ => return Err(bad(cmd, "give `text` or `path`")), }; @@ -272,10 +272,10 @@ fn evaluate(s: &mut Session, p: &Value) -> Result { let (vals, _) = s.doc.param_values(); let defaults = expr::Defaults::for_unit(unit.unwrap_or(""), 1.0); let refs = expr::references(&e); - let lookup = |n: &str| vals.get(n).copied().ok_or_else(|| solvecraft_doc::DocError::Expr(format!("unknown parameter `{n}`"))); + let lookup = |n: &str| vals.get(n).copied().ok_or_else(|| solidcraft_doc::DocError::Expr(format!("unknown parameter `{n}`"))); let r = expr::eval_with_defaults(&e, &lookup, defaults).and_then(|v| match kind { Some(k) => v.to_kind_in(k, defaults).map(|x| (x, k)), - None => v.kind().map(|k| (v.v, k)).ok_or_else(|| solvecraft_doc::DocError::Expr(format!("the result is {}", v.describe()))), + None => v.kind().map(|k| (v.v, k)).ok_or_else(|| solidcraft_doc::DocError::Expr(format!("the result is {}", v.describe()))), }); Ok(match r { Ok((v, k)) => { @@ -301,7 +301,7 @@ fn complete(s: &mut Session, p: &Value) -> Result { let word: String = text.chars().rev().take_while(|c| c.is_alphanumeric() || *c == '_').collect::>().into_iter().rev().collect(); let starts = |n: &str| word.is_empty() || n.to_ascii_lowercase().starts_with(&word.to_ascii_lowercase()); let mut out: Vec = Vec::new(); - let mut rows: Vec = s.doc.param_rows().into_iter().filter(|r| starts(&r.name)).collect(); + let mut rows: Vec = s.doc.param_rows().into_iter().filter(|r| starts(&r.name)).collect(); // Favourites, then user parameters, then the rest. rows.sort_by_key(|r| (!r.favorite, r.source != "user")); for r in rows.into_iter().take(200) { diff --git a/crates/engine/src/cmd/parameters_tests.rs b/crates/engine/src/cmd/parameters_tests.rs index 9eaa66da..a4b54aee 100644 --- a/crates/engine/src/cmd/parameters_tests.rs +++ b/crates/engine/src/cmd/parameters_tests.rs @@ -157,7 +157,7 @@ fn rename_updates_every_reference() { assert!(rel(volume(&mut s), 40.0 * 40.0 * 10.0) < 1e-9); // Model and feature parameters can be renamed too: dimensions and features follow. let dim = s.doc.features.first().and_then(|f| match &f.kind { - solvecraft_doc::FeatureKind::Sketch { sketch, .. } => sketch.constraints.iter().filter_map(|c| c.param.clone()).next(), + solidcraft_doc::FeatureKind::Sketch { sketch, .. } => sketch.constraints.iter().filter_map(|c| c.param.clone()).next(), _ => None, }); let dim = dim.unwrap_or_default(); @@ -235,7 +235,7 @@ fn csv_and_json_round_trip() { assert_eq!(s.doc.param("len").map(|p| p.expr.as_str()), Some("80 mm")); assert!(matches!(s.execute("parameters.import", &json!({})), Err(EngineError::BadParams { .. }))); - let dir = std::env::temp_dir().join(format!("solvecraft-params-{}", std::process::id())); + let dir = std::env::temp_dir().join(format!("solidcraft-params-{}", std::process::id())); let _ = std::fs::create_dir_all(&dir); let path = dir.join("p.json"); let r = run(&mut s, "parameters.export", json!({"path": path.to_string_lossy()})); @@ -307,7 +307,7 @@ fn dimension_edits_of_a_triconnected_linkage_continue_and_store_the_solved_sketc let solved = s.model.state().sketch(feature).unwrap().report.clone(); assert!(solved.ok(), "{solved:?}"); assert_eq!(linkage_turns(&s, feature), before); - let mut fresh = solvecraft_doc::Model::default(); + let mut fresh = solidcraft_doc::Model::default(); fresh.evaluate(&s.doc); let again = fresh.state().sketch(feature).unwrap().report.clone(); assert!(again.ok() && again.continuations == 0, "{again:?}"); @@ -348,7 +348,7 @@ fn a_failed_edit_keeps_the_stored_sketch_solved_for_its_values() { run(&mut s, "sketch.point", json!({"point": [200, 200]})); let stored = s.doc.sketch(feature).unwrap(); for c in stored.constraints.iter().filter(|c| c.param.is_some()) { - let measured = solvecraft_sketch::measure_dimension(stored, &c.kind).unwrap(); + let measured = solidcraft_sketch::measure_dimension(stored, &c.kind).unwrap(); assert!((measured - c.kind.value().unwrap()).abs() < 1e-6, "{}: {measured} vs {:?}", c.id, c.kind.value()); } } @@ -396,7 +396,7 @@ fn a_design_stored_with_other_values_still_rebuilds() { run(&mut s, "sketch.finish", json!({})); run(&mut s, "solid.extrude", json!({"sketch": "Linkage", "distance": 5})); set_params(&mut s, ¶ms, &FLIPPING); - let s = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let s = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); let state = s.model.state(); let solved = state.sketch(feature).unwrap(); assert!(solved.report.ok() && !solved.report.folded.is_empty(), "{:?}", solved.report); @@ -450,7 +450,7 @@ fn a_failed_auto_constrain_keeps_the_stored_sketch_consistent() { let consistent = |s: &Session, feature: u64| { let stored = s.doc.sketch(feature).unwrap(); stored.constraints.iter().filter(|c| c.param.is_some()).all(|c| { - let measured = solvecraft_sketch::measure_dimension(stored, &c.kind).unwrap(); + let measured = solidcraft_sketch::measure_dimension(stored, &c.kind).unwrap(); (measured - c.kind.value().unwrap()).abs() < 1e-6 }) }; @@ -473,7 +473,7 @@ fn a_failed_auto_constrain_keeps_the_stored_sketch_consistent() { } /// The stored sketch's points, by id. -fn stored_points(s: &Session, feature: u64) -> Vec { +fn stored_points(s: &Session, feature: u64) -> Vec { s.doc.sketch(feature).unwrap().points.iter().map(|p| p.pos).collect() } @@ -504,7 +504,7 @@ fn an_unsolvable_value_edit_is_refused_and_changes_nothing() { fn value_edits_continue_from_the_last_solved_state() { // A → B → C, each a small edit solved in one jump; C is solved from B (the stored state), // and setting both values back reproduces A (the linkage is fully constrained). - let close = |a: &[solvecraft_geom::Vec2], b: &[solvecraft_geom::Vec2], tol: f64| a.iter().zip(b).all(|(p, q)| p.dist(*q) < tol); + let close = |a: &[solidcraft_geom::Vec2], b: &[solidcraft_geom::Vec2], tol: f64| a.iter().zip(b).all(|(p, q)| p.dist(*q) < tol); let (mut s, feature, params) = linkage_session(); run(&mut s, "sketch.finish", json!({})); let a = stored_points(&s, feature); @@ -520,7 +520,7 @@ fn value_edits_continue_from_the_last_solved_state() { let stored = from_b.driving_values(); let (vals, _) = s.doc.param_values(); s.doc.apply_dimension_values(&vals, &mut from_b).unwrap(); - assert!(solvecraft_sketch::solve_after_value_edit(&mut from_b, &stored).ok()); + assert!(solidcraft_sketch::solve_after_value_edit(&mut from_b, &stored).ok()); assert!(close(&c, &from_b.points.iter().map(|p| p.pos).collect::>(), 1e-6) && !close(&c, &b, 1e-3)); run(&mut s, "parameters.change", json!({"name": params[7], "expression": p7})); run(&mut s, "parameters.change", json!({"name": params[8], "expression": p8})); diff --git a/crates/engine/src/cmd/parts.rs b/crates/engine/src/cmd/parts.rs index 92cc7529..b0a60e4b 100644 --- a/crates/engine/src/cmd/parts.rs +++ b/crates/engine/src/cmd/parts.rs @@ -8,7 +8,7 @@ //! washer, the top of a set screw or pin); the shank runs down −z. use serde_json::{Value, json}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use super::CommandSpec; use crate::params::{bad, num, str_, vec3}; @@ -355,7 +355,7 @@ fn insert(s: &mut Session, p: &Value) -> Result { let c = run(s, "component.create", json!({"name": name}))?; let (comp, occ) = (c["component"].as_u64().unwrap_or(0), c["occurrence"].as_u64().unwrap_or(0)); build(s, &sh)?; - s.doc_mut().parts.insert(comp, solvecraft_doc::StandardPart { family: family.clone(), size: size.clone(), length: shape_len(&sh) }); + s.doc_mut().parts.insert(comp, solidcraft_doc::StandardPart { family: family.clone(), size: size.clone(), length: shape_len(&sh) }); run(s, "component.activate", json!({"component": active}))?; let mut out = json!({"component": comp, "occurrence": occ, "name": name}); if let (Some(a), Some(h)) = (at, host) { @@ -370,7 +370,7 @@ fn insert(s: &mut Session, p: &Value) -> Result { } else if let Some(pt) = p.get("point").and_then(vec3) { // Placed at a point, its +z along `direction`. let dir = p.get("direction").and_then(vec3).and_then(|d| d.normalized()).unwrap_or(Vec3::Z); - let m = solvecraft_doc::rigid( + let m = solidcraft_doc::rigid( Vec3::ZERO, Vec3::ZERO, Vec3::Z.cross(dir).normalized().unwrap_or(Vec3::X), @@ -393,7 +393,7 @@ fn hole_wall(s: &Session, a: Vec3) -> Option { for b in &st.bodies { for d in dirs { let q = a + d * step; - if let Some(c) = solvecraft_doc::kernel::cylinder_face_at(&b.body, q) { + if let Some(c) = solidcraft_doc::kernel::cylinder_face_at(&b.body, q) { let v = q - c.axis_point; let along = v.dot(c.axis); let on_wall = ((v - c.axis * along).len() - c.radius).abs() < 1e-3 && along > c.start && along < c.end; @@ -435,7 +435,7 @@ fn resize(s: &mut Session, p: &Value) -> Result { run(s, "component.activate", json!({"component": comp}))?; build(s, &sh)?; let name = part_name(&part.family, &size, &sh); - s.doc_mut().parts.insert(comp, solvecraft_doc::StandardPart { family: part.family.clone(), size: size.clone(), length: shape_len(&sh) }); + s.doc_mut().parts.insert(comp, solidcraft_doc::StandardPart { family: part.family.clone(), size: size.clone(), length: shape_len(&sh) }); if let Some(c) = s.doc_mut().components.iter_mut().find(|c| c.id == comp) { c.name = name.clone(); } @@ -460,14 +460,14 @@ mod tests { } } - fn bbox(s: &Session, prefix: &str) -> (solvecraft_geom::Vec3, solvecraft_geom::Vec3) { + fn bbox(s: &Session, prefix: &str) -> (solidcraft_geom::Vec3, solidcraft_geom::Vec3) { let st = s.world_state(); let comp = s.doc.components.iter().find(|c| c.name.starts_with(prefix)).map(|c| c.id).unwrap_or(0); - let (mut lo, mut hi) = (solvecraft_geom::Vec3::new(f64::MAX, f64::MAX, f64::MAX), solvecraft_geom::Vec3::new(f64::MIN, f64::MIN, f64::MIN)); + let (mut lo, mut hi) = (solidcraft_geom::Vec3::new(f64::MAX, f64::MAX, f64::MAX), solidcraft_geom::Vec3::new(f64::MIN, f64::MIN, f64::MIN)); for b in st.bodies.iter().filter(|b| s.doc.model_body_component(b) == comp) { let m = b.mesh().bounds(); - lo = solvecraft_geom::Vec3::new(lo.x.min(m.min.x), lo.y.min(m.min.y), lo.z.min(m.min.z)); - hi = solvecraft_geom::Vec3::new(hi.x.max(m.max.x), hi.y.max(m.max.y), hi.z.max(m.max.z)); + lo = solidcraft_geom::Vec3::new(lo.x.min(m.min.x), lo.y.min(m.min.y), lo.z.min(m.min.z)); + hi = solidcraft_geom::Vec3::new(hi.x.max(m.max.x), hi.y.max(m.max.y), hi.z.max(m.max.z)); } (lo, hi) } diff --git a/crates/engine/src/cmd/patterns_tests.rs b/crates/engine/src/cmd/patterns_tests.rs index 33a94461..c94ffbd7 100644 --- a/crates/engine/src/cmd/patterns_tests.rs +++ b/crates/engine/src/cmd/patterns_tests.rs @@ -85,7 +85,7 @@ fn circular_numeric_axis_origin_preserves_hole_geometry_and_reopens() { assert_eq!(s.doc.to_json(), before); run(&mut s, "edit.redo", json!({})); assert_eq!(s.doc.to_json(), saved); - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&saved).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&saved).unwrap()); assert!(rel(volume(&mut reopened), expected) < 1e-4); } // The documented omission of origin still means zero. @@ -94,7 +94,7 @@ fn circular_numeric_axis_origin_preserves_hole_geometry_and_reopens() { let before = s.doc.to_json(); run(&mut s, "solid.pattern.circular", json!({"bodies":["Cube"],"axis":{"dir":[0,0,1]},"count":4})); let implicit = s.doc.to_json(); - let mut explicit = Session::new(solvecraft_doc::Document::from_json(&before).unwrap()); + let mut explicit = Session::new(solidcraft_doc::Document::from_json(&before).unwrap()); run(&mut explicit, "solid.pattern.circular", json!({"bodies":["Cube"],"axis":{"origin":[0,0,0],"dir":[0,0,1]},"count":4})); assert_eq!(implicit, explicit.doc.to_json()); } diff --git a/crates/engine/src/cmd/plastic.rs b/crates/engine/src/cmd/plastic.rs index 86e333e1..74470aae 100644 --- a/crates/engine/src/cmd/plastic.rs +++ b/crates/engine/src/cmd/plastic.rs @@ -1,10 +1,10 @@ //! Plastic (enclosure) features: Boss, Lip / Groove, Snap Fit and Rest, and plastic rules. use serde_json::{Value, json}; -use solvecraft_doc::FeatureKind; -use solvecraft_doc::expr::Kind; -use solvecraft_doc::plastic::PlasticRule; -use solvecraft_geom::Vec3; +use solidcraft_doc::FeatureKind; +use solidcraft_doc::expr::Kind; +use solidcraft_doc::plastic::PlasticRule; +use solidcraft_geom::Vec3; use super::CommandSpec; use super::features::{add_feature, check_expr}; @@ -267,7 +267,7 @@ fn snap_fit(s: &mut Session, p: &Value) -> Result { #[cfg(test)] mod tests { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; use std::f64::consts::PI; use serde_json::{Value, json}; @@ -362,7 +362,7 @@ mod tests { run(&mut s, "parameters.change", json!({"name":"board_d","expression":"50 mm"})); let before = s.model.state_before(feature.id); let name = feature.face_names.first().unwrap(); - let point = solvecraft_doc::naming::point_on_face(&before, name, Vec3::new(0., -28., 20.)) + let point = solidcraft_doc::naming::point_on_face(&before, name, Vec3::new(0., -28., 20.)) .expect("the original wall identity survives merged pieces") .0; assert!((point - Vec3::new(0., -33., 20.)).len() < 1e-8, "{point:?}"); @@ -372,7 +372,7 @@ mod tests { let after = &session.model.result(feature.id).unwrap().state; let old = before.body("Case").unwrap(); let body = after.body("Case").unwrap(); - let added = solvecraft_kernel::measure(&body.body).unwrap().volume - solvecraft_kernel::measure(&old.body).unwrap().volume; + let added = solidcraft_kernel::measure(&body.body).unwrap().volume - solidcraft_kernel::measure(&old.body).unwrap().volume; // Rectangular arm + triangular catch; the small attachment overlap lies inside the wall. assert!((added - (8. * 1.2 + 0.8 * 1.5 / 2.) * 6.).abs() < 0.05, "{added}"); let mesh = body.mesh(); @@ -382,7 +382,7 @@ mod tests { assert!(!mesh.contains(Vec3::new(0., -24., 19.4)), "the arm ends at its specified length"); }; check(&s); - let cold = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let cold = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(cold.model.results.iter().all(|result| result.error.is_none())); check(&cold); } diff --git a/crates/engine/src/cmd/revolve_convention_tests.rs b/crates/engine/src/cmd/revolve_convention_tests.rs index 55cf400c..e2f40d67 100644 --- a/crates/engine/src/cmd/revolve_convention_tests.rs +++ b/crates/engine/src/cmd/revolve_convention_tests.rs @@ -2,8 +2,8 @@ use crate::Session; use serde_json::{Value, json}; -use solvecraft_doc::{Document, FeatureKind, RevolveConvention}; -use solvecraft_geom::Vec3; +use solidcraft_doc::{Document, FeatureKind, RevolveConvention}; +use solidcraft_geom::Vec3; use std::f64::consts::{FRAC_PI_2, PI, SQRT_2}; fn run(s: &mut Session, cmd: &str, p: Value) -> Value { @@ -28,7 +28,7 @@ fn turn(s: &mut Session, sign: f64, settings: Value) { fn check_centered(s: &Session, opposed: bool) { let state = s.model.state(); let expected = 30.0 * PI * if opposed { 2.0 } else { 1.0 }; - let volume: f64 = state.bodies.iter().map(|mb| solvecraft_kernel::measure(&mb.body).unwrap().volume).sum(); + let volume: f64 = state.bodies.iter().map(|mb| solidcraft_kernel::measure(&mb.body).unwrap().volume).sum(); assert!((volume - expected).abs() < 0.03, "{volume} vs {expected}"); for sign in if opposed { vec![1.0, -1.0] } else { vec![1.0] } { let body = &state.bodies.iter().find(|mb| mb.body.contains(Vec3::new(sign * 3.0, 5.0, 0.0))).unwrap().body; @@ -40,7 +40,7 @@ fn check_centered(s: &Session, opposed: bool) { assert!((bounds.max.z - 2.0 * SQRT_2).abs() < 1e-6, "{bounds:?}"); let inner_x = if sign > 0.0 { bounds.min.x } else { -bounds.max.x }; assert!((inner_x - SQRT_2).abs() < 1e-6, "{bounds:?}"); - let centroid = solvecraft_kernel::measure(body).unwrap().centroid; + let centroid = solidcraft_kernel::measure(body).unwrap().centroid; let radial = (2.0 / 3.0) * (64.0 - 8.0) / (16.0 - 4.0); let want_x = radial * (PI / 4.0).sin() / (PI / 4.0); assert!((centroid.x - sign * want_x).abs() < 0.005, "{centroid:?}"); @@ -87,7 +87,7 @@ fn two_sided_revolves_use_the_canonical_negative_and_positive_limits() { assert!((bounds.max.z - 2.0 * 3.0f64.sqrt()).abs() < 1e-6); assert!(body.contains(Vec3::new(3.0, 5.0, 0.0))); assert!(!body.contains(Vec3::new(0.0, 5.0, 3.0))); - assert!((solvecraft_kernel::measure(body).unwrap().volume - 30.0 * PI).abs() < 0.03); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 30.0 * PI).abs() < 0.03); } } @@ -97,7 +97,7 @@ fn legacy_missing_convention_keeps_geometry_through_edit_redefine_undo_and_reope profile(&mut s, false); turn(&mut s, -1.0, json!({"convention":"legacy_axis_start"})); let mut saved: Value = serde_json::from_str(&s.doc.to_json()).unwrap(); - saved["format"] = json!("solvecraft/10"); + saved["format"] = json!("solidcraft/10"); for f in saved["features"].as_array_mut().unwrap() { f.as_object_mut().unwrap().remove("convention"); } @@ -149,7 +149,7 @@ fn extent_handle_angles_match_new_and_legacy_material() { let ss = &state.sketches[0]; let regions = vec![ss.profiles[0].region.clone()]; for convention in [RevolveConvention::SketchNormal, RevolveConvention::LegacyAxisStart] { - let extent = solvecraft_doc::revolve_extent_with_convention( + let extent = solidcraft_doc::revolve_extent_with_convention( &state, &ss.plane, ®ions, @@ -180,7 +180,7 @@ fn symmetric_revolve_preserves_a_translated_rotated_sketch_frame() { assert!(bounds.max.dist(Vec3::new(20.0, 20.0 + 2.0 * SQRT_2, 34.0)) < 1e-4, "{bounds:?}"); assert!(body.contains(Vec3::new(15.0, 20.0, 33.0))); assert!(!body.contains(Vec3::new(15.0, 23.0, 30.0))); - assert!((solvecraft_kernel::measure(body).unwrap().volume - 30.0 * PI).abs() < 0.03); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 30.0 * PI).abs() < 0.03); } #[test] @@ -217,7 +217,7 @@ fn centered_coplanar_cut_uses_the_actual_angular_interval() { // Removed sector clipped by x>=2: 10∫[-π/4,π/4](16−4sec²θ)/2 dθ. // The sector crosses the whole stock at z=0, leaving two disconnected solids. let expected = 200.0 - 40.0 * PI; - let volumes: Vec<_> = state.bodies.iter().map(|mb| solvecraft_kernel::measure(&mb.body).unwrap().volume).collect(); + let volumes: Vec<_> = state.bodies.iter().map(|mb| solidcraft_kernel::measure(&mb.body).unwrap().volume).collect(); let total: f64 = volumes.iter().sum(); assert!((total - expected).abs() < 0.05, "{volumes:?}: total{total}, expected{expected}"); assert_eq!(state.bodies.len(), 2); @@ -241,7 +241,7 @@ fn one_sided_and_full_revolves_keep_the_existing_material() { assert!(body.contains(Vec3::new(3.0 / SQRT_2, 5.0, 3.0 / SQRT_2))); assert_eq!(body.contains(Vec3::new(-3.0 / SQRT_2, 5.0, -3.0 / SQRT_2)), angle == "360 deg"); let expected = if angle == "360 deg" { 120.0 * PI } else { 30.0 * PI }; - assert!((solvecraft_kernel::measure(body).unwrap().volume - expected).abs() < 0.08); + assert!((solidcraft_kernel::measure(body).unwrap().volume - expected).abs() < 0.08); } } } @@ -256,7 +256,7 @@ fn point_targets_use_each_profiles_canonical_axis_for_both_axis_signs() { turn(&mut s, sign, json!({"to":[0,5,3]})); let state = s.model.state(); let expected = 30.0 * PI * if opposed { 2.0 } else { 1.0 }; - let volume: f64 = state.bodies.iter().map(|mb| solvecraft_kernel::measure(&mb.body).unwrap().volume).sum(); + let volume: f64 = state.bodies.iter().map(|mb| solidcraft_kernel::measure(&mb.body).unwrap().volume).sum(); assert!((volume - expected).abs() < 0.05, "{volume}"); assert!(state.bodies.iter().any(|mb| mb.body.contains(Vec3::new(3.0 / SQRT_2, 5.0, 3.0 / SQRT_2)))); assert!(!state.bodies.iter().any(|mb| mb.body.contains(Vec3::new(3.0 / SQRT_2, 5.0, -3.0 / SQRT_2)))); @@ -299,10 +299,10 @@ fn curved_targets_keep_only_the_material_before_the_post_for_both_axis_signs() { assert!(!arc.contains(Vec3::new(-side * 12.0 * (PI / 6.0).cos(), 6.0, -2.5))); assert!(!arc.contains(Vec3::new(side * 12.0 * (PI / 6.0).cos(), -6.0, -2.5))); let full = PI * (225.0 - 100.0) * 5.0; - let angle = solvecraft_kernel::measure(arc).unwrap().volume / full * 360.0; + let angle = solidcraft_kernel::measure(arc).unwrap().volume / full * 360.0; assert!(angle > 60.0 && angle < 90.0, "{angle}"); - let overlap = solvecraft_kernel::boolean(arc, post, solvecraft_kernel::BoolOp::Intersect).unwrap(); - assert!(overlap.is_none_or(|b| solvecraft_kernel::measure(&b).unwrap().volume < 1e-3)); + let overlap = solidcraft_kernel::boolean(arc, post, solidcraft_kernel::BoolOp::Intersect).unwrap(); + assert!(overlap.is_none_or(|b| solidcraft_kernel::measure(&b).unwrap().volume < 1e-3)); } } } diff --git a/crates/engine/src/cmd/section.rs b/crates/engine/src/cmd/section.rs index a65f7ee4..60e13628 100644 --- a/crates/engine/src/cmd/section.rs +++ b/crates/engine/src/cmd/section.rs @@ -2,7 +2,7 @@ //! positive side is hidden). It changes no geometry and is not an undo step. use serde_json::{Value, json}; -use solvecraft_geom::{Plane, Vec3}; +use solidcraft_geom::{Plane, Vec3}; use super::CommandSpec; use crate::params::{bad, bool_, expr, vec3}; @@ -35,13 +35,13 @@ fn section(s: &mut Session, p: &Value) -> Result { // A construction plane (name or id): where it is now. Some(v @ (Value::String(_) | Value::Number(_))) => { let r = super::features::plane_param(s, Some(v), cmd)?; - let solvecraft_doc::PlaneRef::Construction { name } = &r else { return Err(bad(cmd, "`plane` must be a plane")) }; + let solidcraft_doc::PlaneRef::Construction { name } = &r else { return Err(bad(cmd, "`plane` must be a plane")) }; let st = s.model.state(); let pl = st .construct .iter() .find(|c| &c.name == name) - .and_then(|c| if let solvecraft_doc::construct::ConstructGeom::Plane(pl) = &c.geom { Some(*pl) } else { None }) + .and_then(|c| if let solidcraft_doc::construct::ConstructGeom::Plane(pl) = &c.geom { Some(*pl) } else { None }) .ok_or_else(|| bad(cmd, format!("construction plane `{name}` is not built (rolled back, suppressed or failed)")))?; (pl.origin, pl.normal(), None) } @@ -54,7 +54,7 @@ fn section(s: &mut Session, p: &Value) -> Result { }; let length = |key: &str| -> Result> { match expr(p, key) { - Some(e) => s.doc.eval(&e, solvecraft_doc::expr::Kind::Length).map(Some).map_err(|e| bad(cmd, format!("{key}: {e}"))), + Some(e) => s.doc.eval(&e, solidcraft_doc::expr::Kind::Length).map(Some).map_err(|e| bad(cmd, format!("{key}: {e}"))), None => Ok(None), } }; diff --git a/crates/engine/src/cmd/sheet.rs b/crates/engine/src/cmd/sheet.rs index 58c8d273..20c478a3 100644 --- a/crates/engine/src/cmd/sheet.rs +++ b/crates/engine/src/cmd/sheet.rs @@ -2,10 +2,10 @@ //! the flat pattern (outline, bend lines, bend table) and its DXF export. use serde_json::{Value, json}; -use solvecraft_doc::FeatureKind; -use solvecraft_doc::expr::Kind; -use solvecraft_doc::sheet::{SheetBody, SheetRule}; -use solvecraft_geom::{Vec2, Vec3}; +use solidcraft_doc::FeatureKind; +use solidcraft_doc::expr::Kind; +use solidcraft_doc::sheet::{SheetBody, SheetRule}; +use solidcraft_geom::{Vec2, Vec3}; use super::CommandSpec; use super::features::{add_feature, check_expr, feature_sketch, profiles, sketch_id}; @@ -157,7 +157,7 @@ fn fold(s: &mut Session, p: &Value) -> Result { let ss = st.sketch(sk).ok_or_else(|| bad(cmd, "no such sketch"))?; let ci = ss.sketch.curve_index(&c).ok_or_else(|| bad(cmd, format!("no curve `{c}` in the sketch")))?; let (a, b) = match ss.sketch.segs(ci).as_slice() { - [solvecraft_geom::Seg2::Line { a, b }] => (ss.plane.to_world(*a), ss.plane.to_world(*b)), + [solidcraft_geom::Seg2::Line { a, b }] => (ss.plane.to_world(*a), ss.plane.to_world(*b)), _ => return Err(bad(cmd, "the fold line must be a sketch line")), }; (Some(sk), Some(c), a, b) @@ -323,7 +323,7 @@ fn flat_pattern(s: &mut Session, p: &Value) -> Result { pub fn flat_dxf(sh: &SheetBody) -> Result { let regions = sh.flat_regions()?; let pockets = sh.machining()?; - let pocket_layer = |p: &solvecraft_doc::sheet::SheetPocket| { + let pocket_layer = |p: &solidcraft_doc::sheet::SheetPocket| { let side = if p.z0 == 0.0 { "BOTTOM" } else if p.z1 == sh.t { @@ -384,9 +384,9 @@ pub fn flat_dxf(sh: &SheetBody) -> Result { for (h, layer) in loops { // A circle is often represented by two semicircles. Keep the whole analytic // boundary as one CIRCLE rather than approximating outer boundaries with chords. - if let Some(solvecraft_geom::Seg2::Arc { center, radius, sweep, .. }) = h.segs.first() { - let total: f64 = h.segs.iter().filter_map(|s| if let solvecraft_geom::Seg2::Arc { sweep, .. } = s { Some(*sweep) } else { None }).sum(); - let circular = h.segs.iter().all(|s| matches!(s, solvecraft_geom::Seg2::Arc { center: c, radius: r, sweep: a, .. } if c.dist(*center) < 1e-9 && (*r-*radius).abs() < 1e-9 && a.signum() == sweep.signum())); + if let Some(solidcraft_geom::Seg2::Arc { center, radius, sweep, .. }) = h.segs.first() { + let total: f64 = h.segs.iter().filter_map(|s| if let solidcraft_geom::Seg2::Arc { sweep, .. } = s { Some(*sweep) } else { None }).sum(); + let circular = h.segs.iter().all(|s| matches!(s, solidcraft_geom::Seg2::Arc { center: c, radius: r, sweep: a, .. } if c.dist(*center) < 1e-9 && (*r-*radius).abs() < 1e-9 && a.signum() == sweep.signum())); if circular && (total.abs() - std::f64::consts::TAU).abs() < 1e-9 { w(0, "CIRCLE"); w(8, &layer); @@ -398,7 +398,7 @@ pub fn flat_dxf(sh: &SheetBody) -> Result { } for seg in &h.segs { match *seg { - solvecraft_geom::Seg2::Arc { center, radius, start, sweep } => { + solidcraft_geom::Seg2::Arc { center, radius, start, sweep } => { if (sweep.abs() - std::f64::consts::TAU).abs() < 1e-9 { w(0, "CIRCLE"); w(8, &layer); @@ -438,7 +438,7 @@ fn export_dxf(s: &mut Session, p: &Value) -> Result { let sh = sheet_of(s, p, cmd)?; let text = flat_dxf(&sh)?; if let Some(path) = str_(p, "path").filter(|x| !x.trim().is_empty()) { - solvecraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; return Ok(json!({"path": path, "bends": sh.bends().len()})); } Ok(json!({"dxf": text, "bends": sh.bends().len()})) @@ -482,7 +482,7 @@ fn export_bend_table(s: &mut Session, p: &Value) -> Result { csv }; if let Some(path) = str_(p, "path").filter(|x| !x.trim().is_empty()) { - solvecraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; return Ok(json!({"path": path, "format": format, "bends": sh.bends().len()})); } Ok(json!({"format": format, "text": text, "bends": sh.bends().len()})) diff --git a/crates/engine/src/cmd/sheet_tests.rs b/crates/engine/src/cmd/sheet_tests.rs index 54d87cc0..bf1eafb3 100644 --- a/crates/engine/src/cmd/sheet_tests.rs +++ b/crates/engine/src/cmd/sheet_tests.rs @@ -50,7 +50,7 @@ fn shallow_sheet_pocket_preserves_material_depth_export_undo_and_reopen() { assert_eq!(f["pockets"][0]["depth_mm"], 1.0); let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); assert!(dxf.contains("POCKET_BOTTOM_Z0.000000_TO_1.000000_MM")); - assert_eq!(solvecraft_io::read_dxf(&dxf).unwrap().len(), 8); + assert_eq!(solidcraft_io::read_dxf(&dxf).unwrap().len(), 8); run(&mut s, "edit.undo", json!({})); assert!((volume(&mut s) - 15000.0).abs() < 1e-6); run(&mut s, "edit.redo", json!({})); @@ -59,10 +59,10 @@ fn shallow_sheet_pocket_preserves_material_depth_export_undo_and_reopen() { assert!((volume(&mut s) - 14900.0).abs() < 1e-6); let state = s.model.state(); let body = &state.bodies[0].body; - assert!(!body.contains(solvecraft_geom::Vec3::new(25.0, 25.0, 0.5))); - assert!(body.contains(solvecraft_geom::Vec3::new(25.0, 25.0, 1.5))); + assert!(!body.contains(solidcraft_geom::Vec3::new(25.0, 25.0, 0.5))); + assert!(body.contains(solidcraft_geom::Vec3::new(25.0, 25.0, 1.5))); } - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!((volume(&mut reopened) - 14900.0).abs() < 1e-6); assert_eq!(flat(&mut reopened)["pockets"], f["pockets"]); } @@ -81,7 +81,7 @@ fn planar_sheet_intersection_retains_exact_outline_and_islands() { assert_eq!(flat(&mut s)["flat_size_mm"], json!([10.0, 10.0, 2.5])); } let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); - assert_eq!(solvecraft_io::read_dxf(&dxf).unwrap().len(), 4); + assert_eq!(solidcraft_io::read_dxf(&dxf).unwrap().len(), 4); let mut imported = Session::default(); run(&mut imported, "sketch.insert_dxf", json!({"name":"Retained","text":dxf})); run(&mut imported, "sketch.finish", json!({})); @@ -212,26 +212,26 @@ fn intersect_preserves_both_material_regions_of_a_logical_ring_sheet() { let area: f64 = regions.iter().map(|r| r.area()).sum(); assert!((area - (900.0 - PI * 75.0)).abs() < 1e-8, "area={area}"); let body = &state.bodies[0].body; - assert!(body.contains(solvecraft_geom::Vec3::new(50.0, 30.0, 1.0))); - assert!(!body.contains(solvecraft_geom::Vec3::new(57.0, 30.0, 1.0))); - assert!(body.contains(solvecraft_geom::Vec3::new(64.0, 30.0, 1.0))); - assert!(!body.contains(solvecraft_geom::Vec3::new(70.0, 30.0, 1.0))); + assert!(body.contains(solidcraft_geom::Vec3::new(50.0, 30.0, 1.0))); + assert!(!body.contains(solidcraft_geom::Vec3::new(57.0, 30.0, 1.0))); + assert!(body.contains(solidcraft_geom::Vec3::new(64.0, 30.0, 1.0))); + assert!(!body.contains(solidcraft_geom::Vec3::new(70.0, 30.0, 1.0))); assert!((volume(&mut s) - (900.0 - PI * 75.0) * 2.5).abs() < 0.2); } let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); - assert_eq!(solvecraft_io::read_dxf(&dxf).unwrap().len(), 6); + assert_eq!(solidcraft_io::read_dxf(&dxf).unwrap().len(), 6); } #[test] fn machining_a_folded_parallel_panel_preserves_its_pocket_on_unfold() { - use solvecraft_doc::{apply_point, apply_vector}; - use solvecraft_geom::Vec3; + use solidcraft_doc::{apply_point, apply_vector}; + use solidcraft_geom::Vec3; let mut s = Session::default(); base(&mut s, 100.0, 60.0); run(&mut s, "sheet.flange", json!({"edges":[[50,0,2.5]],"height":20})); let sh = s.model.state().sheets[0].clone(); let points = sh.panel(&sh.flanges[0]).outer.polyline(1e-3); - let c = points.iter().copied().fold(solvecraft_geom::Vec2::ZERO, |sum, p| sum + p) / (points.len() as f64); + let c = points.iter().copied().fold(solidcraft_geom::Vec2::ZERO, |sum, p| sum + p) / (points.len() as f64); let w = sh.world(Some(0)); let origin = apply_point(&w, Vec3::ZERO); let x = apply_vector(&w, Vec3::X); @@ -268,8 +268,8 @@ fn flipped_sheet_pocket_keeps_depth_in_the_local_thickness_direction() { } assert!((volume(&mut s) - 14900.0).abs() < 1e-6); let state = s.model.state(); - assert!(!state.bodies[0].body.contains(solvecraft_geom::Vec3::new(25.0, 25.0, -0.5))); - assert!(state.bodies[0].body.contains(solvecraft_geom::Vec3::new(25.0, 25.0, -1.5))); + assert!(!state.bodies[0].body.contains(solidcraft_geom::Vec3::new(25.0, 25.0, -0.5))); + assert!(state.bodies[0].body.contains(solidcraft_geom::Vec3::new(25.0, 25.0, -1.5))); } } @@ -300,10 +300,10 @@ fn multiple_profile_intersect_keeps_tool_union_for_solids_and_sheet_groups() { } let state = s.model.state(); for x in [25.0, 65.0] { - assert!(state.bodies.iter().any(|b| b.body.contains(solvecraft_geom::Vec3::new(x, 25.0, 1.0)))); + assert!(state.bodies.iter().any(|b| b.body.contains(solidcraft_geom::Vec3::new(x, 25.0, 1.0)))); } - assert!(!state.bodies.iter().any(|b| b.body.contains(solvecraft_geom::Vec3::new(50.0, 25.0, 1.0)))); - assert!(state.bodies.iter().any(|b| b.name == "Control" && b.body.contains(solvecraft_geom::Vec3::new(205.0, 5.0, 5.0)))); + assert!(!state.bodies.iter().any(|b| b.body.contains(solidcraft_geom::Vec3::new(50.0, 25.0, 1.0)))); + assert!(state.bodies.iter().any(|b| b.name == "Control" && b.body.contains(solidcraft_geom::Vec3::new(205.0, 5.0, 5.0)))); } } @@ -322,14 +322,14 @@ fn deleting_sheet_material_prunes_cosmetic_threads_and_undo_restores_them() { assert!(s.model.state().bodies.is_empty()); assert!(s.model.state().sheets.is_empty()); assert!(s.model.state().threads.is_empty()); - assert!(solvecraft_doc::world_state(&s.doc, &s.model.state()).threads.is_empty()); + assert!(solidcraft_doc::world_state(&s.doc, &s.model.state()).threads.is_empty()); run(&mut s, "edit.undo", json!({})); assert_eq!(s.model.state().threads.len(), 1); } #[test] fn sheet_pocket_transfer_keeps_local_depth_world_pose_and_history() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; fn check(s: &Session, name: &str, owner: u64, local_origin: Vec3, local_y: Vec3, transferred: bool) { let state = s.model.state(); let mb = state.body(name).unwrap(); @@ -341,8 +341,8 @@ fn sheet_pocket_transfer_keeps_local_depth_world_pose_and_history() { assert!(sh.frame.y.dist(local_y) < 1e-8); assert_eq!(sh.pockets.len(), 1); assert_eq!((sh.pockets[0].z0, sh.pockets[0].z1), (0.0, 1.0)); - let cut = sh.frame.to_world(solvecraft_geom::Vec2::new(25.0, 25.0)) + sh.frame.normal() * 0.5; - let floor = sh.frame.to_world(solvecraft_geom::Vec2::new(25.0, 25.0)) + sh.frame.normal() * 1.5; + let cut = sh.frame.to_world(solidcraft_geom::Vec2::new(25.0, 25.0)) + sh.frame.normal() * 0.5; + let floor = sh.frame.to_world(solidcraft_geom::Vec2::new(25.0, 25.0)) + sh.frame.normal() * 1.5; assert!(!mb.body.contains(cut)); assert!(mb.body.contains(floor)); let world = s.world_state(); @@ -352,7 +352,7 @@ fn sheet_pocket_transfer_keeps_local_depth_world_pose_and_history() { assert!(bounds.max.dist(Vec3::new(100.0, 60.0, 2.5)) < 1e-6, "{bounds:?}"); assert!(!body.contains(Vec3::new(25.0, 25.0, 0.5))); assert!(body.contains(Vec3::new(25.0, 25.0, 1.5))); - assert!((solvecraft_kernel::measure(body).unwrap().volume - 14900.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 14900.0).abs() < 1e-6); } let mut s = Session::default(); base(&mut s, 100.0, 60.0); @@ -381,7 +381,7 @@ fn sheet_pocket_transfer_keeps_local_depth_world_pose_and_history() { assert_eq!(f["pockets"][0]["side"], "bottom"); assert_eq!(f["pockets"][0]["depth_mm"], 1.0); } - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); check(&reopened, &name, component, origin, y, true); run(&mut reopened, "component.activate", json!({"component":component})); run(&mut reopened, "sheet.unfold", json!({"body":name})); @@ -789,7 +789,7 @@ fn malformed_fold_points_are_rejected_atomically() { #[test] fn flat_dxf_preserves_exact_cutout_entities() { - use solvecraft_geom::{Loop2, Region2, Seg2, Vec2}; + use solidcraft_geom::{Loop2, Region2, Seg2, Vec2}; let mut s = Session::default(); base(&mut s, 100.0, 60.0); let mut sh = s.model.state().sheets[0].clone(); @@ -800,9 +800,9 @@ fn flat_dxf_preserves_exact_cutout_entities() { let dxf = super::flat_dxf(&sh).unwrap(); assert_eq!(dxf.matches("0\nCIRCLE\n").count(), 1, "{dxf}"); assert!(!dxf.contains("0\nARC\n"), "a full circle must not become a zero-length ARC"); - let read = solvecraft_io::read_dxf(&dxf).unwrap(); + let read = solidcraft_io::read_dxf(&dxf).unwrap(); assert_eq!(read.len(), 5); - assert!(read.iter().any(|g| matches!(g, solvecraft_io::Geom2::Circle(c, r) if *c == Vec2::new(40.0, 30.0) && (*r - 5.0).abs() < 1e-9))); + assert!(read.iter().any(|g| matches!(g, solidcraft_io::Geom2::Circle(c, r) if *c == Vec2::new(40.0, 30.0) && (*r - 5.0).abs() < 1e-9))); sh.holes[0].outer = Loop2 { segs: vec![ Seg2::Line { a: Vec2::new(10.0, 10.0), b: Vec2::new(30.0, 10.0) }, @@ -813,9 +813,9 @@ fn flat_dxf_preserves_exact_cutout_entities() { assert_eq!(dxf.matches("0\nLINE\n").count(), 5, "four outline lines and one exact cutout line"); assert_eq!(dxf.matches("0\nARC\n").count(), 1); assert!(dxf.contains("50\n0.000000\n51\n180.000000\n")); - let read = solvecraft_io::read_dxf(&dxf).unwrap(); + let read = solidcraft_io::read_dxf(&dxf).unwrap(); assert_eq!(read.len(), 6); - assert!(read.iter().any(|g| matches!(g, solvecraft_io::Geom2::Arc { c, a, b } if *c == Vec2::new(20.0, 10.0) && a.dist(Vec2::new(30.0, 10.0)) < 1e-9 && b.dist(Vec2::new(10.0, 10.0)) < 1e-9))); + assert!(read.iter().any(|g| matches!(g, solidcraft_io::Geom2::Arc { c, a, b } if *c == Vec2::new(20.0, 10.0) && a.dist(Vec2::new(30.0, 10.0)) < 1e-9 && b.dist(Vec2::new(10.0, 10.0)) < 1e-9))); } // A profile chosen by a point outside its inner loop must keep that inner loop through @@ -833,7 +833,7 @@ fn inherited_base_profile_holes_survive_every_sheet_state() { let f = flat(&mut s); assert_eq!(f["cutouts"].as_array().map(Vec::len), Some(1)); let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); - assert_eq!(solvecraft_io::read_dxf(&dxf).unwrap().len(), 8); + assert_eq!(solidcraft_io::read_dxf(&dxf).unwrap().len(), 8); let mut reopened = Session::default(); run(&mut reopened, "sketch.insert_dxf", json!({"text": dxf, "name": "Blank"})); run(&mut reopened, "sketch.finish", json!({})); @@ -861,7 +861,7 @@ fn edge_notch_uses_the_same_material_boundary_in_solid_flat_and_dxf() { assert_eq!(f["outline"][0].as_array().map(Vec::len), Some(8)); assert_eq!(f["cutouts"].as_array().map(Vec::len), Some(0)); let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); - assert_eq!(solvecraft_io::read_dxf(&dxf).unwrap().len(), 8); + assert_eq!(solidcraft_io::read_dxf(&dxf).unwrap().len(), 8); let mut reopened = Session::default(); run(&mut reopened, "sketch.insert_dxf", json!({"text": dxf, "name": "Blank"})); run(&mut reopened, "sketch.finish", json!({})); @@ -890,8 +890,8 @@ fn inherited_round_hole_survives_flange_and_unfold_in_flipped_frames() { let sh = s.model.state().sheets[0].clone(); let regions = sh.flat_regions().unwrap(); assert_eq!(regions[0].holes[0].segs.len(), 2); - assert!(regions[0].holes[0].segs.iter().all(|s| matches!(s, solvecraft_geom::Seg2::Arc { radius, .. } if (*radius - 5.0).abs() < 1e-9))); - let expected = regions.iter().map(solvecraft_geom::Region2::area).sum::() * 2.5; + assert!(regions[0].holes[0].segs.iter().all(|s| matches!(s, solidcraft_geom::Seg2::Arc { radius, .. } if (*radius - 5.0).abs() < 1e-9))); + let expected = regions.iter().map(solidcraft_geom::Region2::area).sum::() * 2.5; run(&mut s, "sheet.unfold", json!({})); assert!((volume(&mut s) - expected).abs() < 0.1); run(&mut s, "sheet.refold", json!({})); @@ -1028,7 +1028,7 @@ fn tiny_cut_inside_exact_circle_survives_unfold_refold_and_dxf() { assert!((regions[0].holes[0].signed_area().abs() - 2.5e-9).abs() < 1e-13); assert_eq!(s.model.state().bodies.len(), 1); let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); - assert_eq!(solvecraft_io::read_dxf(&dxf).unwrap().len(), 5); + assert_eq!(solidcraft_io::read_dxf(&dxf).unwrap().len(), 5); if stage == "sheet.unfold" && initially_flat { run(&mut s, "sheet.refold", json!({})); } @@ -1070,8 +1070,8 @@ fn unrelated_box_cut_preserves_logical_ring_sheet_compound() { assert_eq!(s.model.state().bodies.len(), 2); let state = s.model.state(); let body = &state.bodies.iter().find(|b| b.name == sheet_name).unwrap().body; - assert!(body.contains(solvecraft_geom::Vec3::new(50.0, 30.0, 1.25))); - assert!(!body.contains(solvecraft_geom::Vec3::new(57.0, 30.0, 1.25))); + assert!(body.contains(solidcraft_geom::Vec3::new(50.0, 30.0, 1.25))); + assert!(!body.contains(solidcraft_geom::Vec3::new(57.0, 30.0, 1.25))); } } @@ -1106,9 +1106,9 @@ fn specialized_sheet_cuts_respect_component_and_explicit_participants() { fn ring_cut_preserves_retained_island_through_unfold_and_refold() { fn material_probes(s: &Session) { let body = &s.model.state().bodies[0].body; - assert!(body.contains(solvecraft_geom::Vec3::new(50.0, 30.0, 1.25))); - assert!(!body.contains(solvecraft_geom::Vec3::new(57.0, 30.0, 1.25))); - assert!(body.contains(solvecraft_geom::Vec3::new(65.0, 30.0, 1.25))); + assert!(body.contains(solidcraft_geom::Vec3::new(50.0, 30.0, 1.25))); + assert!(!body.contains(solidcraft_geom::Vec3::new(57.0, 30.0, 1.25))); + assert!(body.contains(solidcraft_geom::Vec3::new(65.0, 30.0, 1.25))); } let mut s = Session::default(); base(&mut s, 100.0, 60.0); @@ -1126,9 +1126,9 @@ fn ring_cut_preserves_retained_island_through_unfold_and_refold() { material_probes(&s); assert_eq!(flat(&mut s)["outline"].as_array().map(Vec::len), Some(2)); let regions = s.model.state().sheets[0].flat_regions().unwrap(); - assert!(regions.iter().any(|r| r.contains(solvecraft_geom::Vec2::new(50.0, 30.0)))); - assert!(!regions.iter().any(|r| r.contains(solvecraft_geom::Vec2::new(57.0, 30.0)))); - assert!(regions.iter().any(|r| r.contains(solvecraft_geom::Vec2::new(65.0, 30.0)))); + assert!(regions.iter().any(|r| r.contains(solidcraft_geom::Vec2::new(50.0, 30.0)))); + assert!(!regions.iter().any(|r| r.contains(solidcraft_geom::Vec2::new(57.0, 30.0)))); + assert!(regions.iter().any(|r| r.contains(solidcraft_geom::Vec2::new(65.0, 30.0)))); let dxf = run(&mut s, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); let mut reopened = Session::default(); run(&mut reopened, "sketch.insert_dxf", json!({"text": dxf, "name": "RingBlank"})); @@ -1144,7 +1144,7 @@ fn ring_cut_preserves_retained_island_through_unfold_and_refold() { } fn retained_depth_check(s: &mut Session, want: f64, z0: f64, z1: f64) { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; assert!((volume(s) - want).abs() < 1e-6, "{} vs {want}", volume(s)); let state = s.model.state(); assert_eq!(state.sheets.len(), 1); @@ -1157,8 +1157,8 @@ fn retained_depth_check(s: &mut Session, want: f64, z0: f64, z1: f64) { assert!((bounds.min.z - z0).abs() < 1e-8 && (bounds.max.z - z1).abs() < 1e-8, "{bounds:?}"); } -fn depth_step_reopen(s: &mut Session, suffix: &str, want: f64, inside: solvecraft_geom::Vec3, outside: solvecraft_geom::Vec3) { - let path = std::env::temp_dir().join(format!("solvecraft-depth-intersect-{suffix}-{}.step", std::process::id())); +fn depth_step_reopen(s: &mut Session, suffix: &str, want: f64, inside: solidcraft_geom::Vec3, outside: solidcraft_geom::Vec3) { + let path = std::env::temp_dir().join(format!("solidcraft-depth-intersect-{suffix}-{}.step", std::process::id())); run(s, "file.export", json!({"path":path.to_string_lossy()})); let mut imported = Session::default(); run(&mut imported, "doc.open", json!({"path":path.to_string_lossy()})); @@ -1192,7 +1192,7 @@ fn dxf_outline_only(text: &str) -> String { #[test] fn partial_depth_intersect_retains_bottom_top_and_middle_material_through_history_and_exports() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; for (offset, z0, z1, layers) in [ (0.0, 0.0, 1.0, vec![("top", 1.0, 2.5)]), (1.5, 1.5, 2.5, vec![("bottom", 0.0, 1.5)]), @@ -1218,7 +1218,7 @@ fn partial_depth_intersect_retains_bottom_top_and_middle_material_through_histor assert!(dxf.contains(&format!("POCKET_{}_Z{a:.6}_TO_{b:.6}_MM", side.to_uppercase()))); } let outline = dxf_outline_only(&dxf); - assert_eq!(solvecraft_io::read_dxf(&outline).unwrap().len(), 4); + assert_eq!(solidcraft_io::read_dxf(&outline).unwrap().len(), 4); let mut outline_import = Session::default(); run(&mut outline_import, "sketch.insert_dxf", json!({"text":outline,"name":"Outline"})); run(&mut outline_import, "sketch.finish", json!({})); @@ -1232,7 +1232,7 @@ fn partial_depth_intersect_retains_bottom_top_and_middle_material_through_histor retained_depth_check(&mut s, 100.0, z0, z1); assert_eq!(flat(&mut s)["pockets"], exported["pockets"]); } - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); retained_depth_check(&mut reopened, 100.0, z0, z1); depth_step_reopen(&mut s, &format!("offset{offset}"), 100.0, Vec3::new(25.0, 25.0, (z0 + z1) * 0.5), Vec3::new(25.0, 25.0, z1 + 0.1)); } @@ -1240,7 +1240,7 @@ fn partial_depth_intersect_retains_bottom_top_and_middle_material_through_histor #[test] fn partial_depth_intersect_preserves_prior_pockets_and_actual_interval_intersections() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let mut s = Session::default(); base(&mut s, 100.0, 60.0); rectangle_tool(&mut s, "OldPocket", [20.0, 20.0], [25.0, 30.0]); @@ -1267,14 +1267,14 @@ fn partial_depth_intersect_preserves_prior_pockets_and_actual_interval_intersect assert!((volume(&mut s) - 25.0).abs() < 1e-6); run(&mut s, "sheet.unfold", json!({})); assert!((volume(&mut s) - 25.0).abs() < 1e-6); - let mut cold = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut cold = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!((volume(&mut cold) - 25.0).abs() < 1e-6); depth_step_reopen(&mut s, "prior", 25.0, Vec3::new(22.0, 25.0, 1.125), Vec3::new(22.0, 25.0, 0.75)); } #[test] fn partial_depth_intersect_keeps_disconnected_tool_islands_and_annular_curves() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let mut s = Session::default(); base(&mut s, 100.0, 60.0); rectangle_tool(&mut s, "Islands", [20.0, 20.0], [30.0, 30.0]); @@ -1316,7 +1316,7 @@ fn partial_depth_intersect_keeps_disconnected_tool_islands_and_annular_curves() } let dxf = run(&mut ring, "sheet.export_dxf", json!({}))["dxf"].as_str().unwrap().to_string(); assert!(dxf.contains("POCKET_TOP_Z1.000000_TO_2.500000_MM")); - assert_eq!(solvecraft_io::read_dxf(&dxf_outline_only(&dxf)).unwrap().len(), 2); + assert_eq!(solidcraft_io::read_dxf(&dxf_outline_only(&dxf)).unwrap().len(), 2); } #[test] @@ -1336,7 +1336,7 @@ fn partial_depth_intersect_deletes_material_already_removed_by_a_pocket_atomical #[test] fn partial_depth_intersect_flipped_tool_and_transferred_sheet_keep_depth_side_and_world_pose() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let mut flipped = Session::default(); base(&mut flipped, 100.0, 60.0); run(&mut flipped, "sketch.create", json!({"name":"FlippedTool","plane":{"origin":[0,0,2.5],"x_dir":[1,0,0],"y_dir":[0,-1,0]}})); @@ -1376,24 +1376,24 @@ fn partial_depth_intersect_flipped_tool_and_transferred_sheet_keep_depth_side_an assert!(!body.contains(Vec3::new(25.0, 25.0, 2.0))); let bounds = world.body(&name).unwrap().mesh().bounds(); assert!(before.min.dist(bounds.min) < 1e-8 && before.max.dist(bounds.max) < 1e-8); - assert!((solvecraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-6); } - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); let reopened_world = reopened.world_state(); let body = &reopened_world.body(&name).unwrap().body; assert!(body.contains(Vec3::new(25.0, 25.0, 1.0))); - assert!((solvecraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-6); run(&mut reopened, "timeline.edit", json!({"feature":"RetainSlab","set":{"extent":{"distance":"1.5"}}})); let world = reopened.world_state(); let body = &world.body(&name).unwrap().body; - assert!((solvecraft_kernel::measure(body).unwrap().volume - 150.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 150.0).abs() < 1e-6); assert!(body.contains(Vec3::new(25.0, 25.0, 1.75))); assert!(!body.contains(Vec3::new(25.0, 25.0, 2.25))); } #[test] fn partial_depth_intersect_full_gauge_in_rotated_frames_does_not_invent_pockets() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; for (angle, translate) in [(37.0, [30.0, 20.0, 10.0]), (121.0, [12345.678, 98765.432, 4321.123])] { let mut s = Session::default(); base(&mut s, 100.0, 60.0); @@ -1410,7 +1410,7 @@ fn partial_depth_intersect_full_gauge_in_rotated_frames_does_not_invent_pockets( run(&mut s, "sketch.create", json!({"name":"WorldTool","plane":{"origin":[0,0,0],"x_dir":[1,0,0],"y_dir":[0,1,0]}})); run(&mut s, "sketch.rectangle.two_point", json!({"p0":[20,20],"p1":[30,30]})); run(&mut s, "sketch.finish", json!({})); - let mut tool_session = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut tool_session = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); run(&mut tool_session, "solid.extrude", json!({"sketch":"WorldTool","distance":2.5,"body_name":"CapWitness"})); let witness_state = tool_session.model.state(); let witness = &witness_state.body("CapWitness").unwrap().body; @@ -1419,7 +1419,7 @@ fn partial_depth_intersect_full_gauge_in_rotated_frames_does_not_invent_pockets( assert_eq!(sources.len(), witness.face_count()); let mut cap_count = 0; for (index, source) in sources.iter().enumerate() { - if let Some(solvecraft_kernel::FaceSource::CertifiedExtrudeCap { high, plane, .. }) = source { + if let Some(solidcraft_kernel::FaceSource::CertifiedExtrudeCap { high, plane, .. }) = source { cap_count += 1; let current = witness.planar_face(index).unwrap(); assert!(current.normal().cross(tool_plane.normal()).len() < 1e-10); @@ -1436,12 +1436,12 @@ fn partial_depth_intersect_full_gauge_in_rotated_frames_does_not_invent_pockets( assert!(s.model.state().sheets[0].pockets.is_empty(), "full gauge must not acquire roundoff-depth pockets"); let world = s.world_state(); let body = &world.body(&name).unwrap().body; - assert!((solvecraft_kernel::measure(body).unwrap().volume - 250.0).abs() < 1e-5); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 250.0).abs() < 1e-5); assert!(body.contains(Vec3::new(25.0, 25.0, 0.1))); assert!(body.contains(Vec3::new(25.0, 25.0, 2.4))); assert!(!body.contains(Vec3::new(10.0, 25.0, 1.0))); } - let mut cold = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut cold = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(cold.model.state().sheets[0].pockets.is_empty()); assert!(flat(&mut cold)["pockets"].as_array().unwrap().is_empty()); run(&mut s, "timeline.edit", json!({"feature":"PostTransferRetain","set":{"extent":{"distance":"1","start_offset":"0.5"}}})); @@ -1452,16 +1452,16 @@ fn partial_depth_intersect_full_gauge_in_rotated_frames_does_not_invent_pockets( assert_eq!(s.model.state().sheets[0].pockets.len(), 2); let world = s.world_state(); let body = &world.body(&name).unwrap().body; - let measured = solvecraft_kernel::measure(body).unwrap().volume; + let measured = solidcraft_kernel::measure(body).unwrap().volume; assert!((measured - 100.0).abs() < 1e-5, "retained volume {measured}"); assert!(body.contains(Vec3::new(25.0, 25.0, 1.0))); assert!(!body.contains(Vec3::new(25.0, 25.0, 0.1))); assert!(!body.contains(Vec3::new(25.0, 25.0, 2.4))); } - let cold = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let cold = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); let world = cold.world_state(); let body = &world.body(&name).unwrap().body; - assert!((solvecraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-5); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-5); assert!(body.contains(Vec3::new(25.0, 25.0, 1.0))); assert!(!body.contains(Vec3::new(25.0, 25.0, 0.1))); } @@ -1469,7 +1469,7 @@ fn partial_depth_intersect_full_gauge_in_rotated_frames_does_not_invent_pockets( #[test] fn partial_depth_intersect_planar_to_object_retains_current_target_depth() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let mut s = Session::default(); base(&mut s, 100.0, 60.0); let name = s.model.state().sheets[0].body.clone(); @@ -1482,15 +1482,15 @@ fn partial_depth_intersect_planar_to_object_retains_current_target_depth() { } let state = s.model.state(); let body = &state.body(&name).unwrap().body; - assert!((solvecraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(body).unwrap().volume - 100.0).abs() < 1e-6); assert!(body.contains(Vec3::new(25.0, 25.0, 1.0))); assert!(!body.contains(Vec3::new(25.0, 25.0, 0.1))); assert!(!body.contains(Vec3::new(25.0, 25.0, 2.0))); assert_eq!(state.sheets[0].pockets.len(), 2); - assert!((solvecraft_kernel::measure(&state.body("Roof").unwrap().body).unwrap().volume - 100.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&state.body("Roof").unwrap().body).unwrap().volume - 100.0).abs() < 1e-6); } - let cold = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); - assert!((solvecraft_kernel::measure(&cold.model.state().body(&name).unwrap().body).unwrap().volume - 100.0).abs() < 1e-6); + let cold = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + assert!((solidcraft_kernel::measure(&cold.model.state().body(&name).unwrap().body).unwrap().volume - 100.0).abs() < 1e-6); } #[test] @@ -1510,14 +1510,14 @@ fn partial_depth_intersect_curved_to_object_refuses_unrepresentable_depth_atomic run(&mut s, command, json!({"body":name})); } let state = s.model.state(); - assert!((solvecraft_kernel::measure(&state.body(&name).unwrap().body).unwrap().volume - 15000.0).abs() < 1e-6); + assert!((solidcraft_kernel::measure(&state.body(&name).unwrap().body).unwrap().volume - 15000.0).abs() < 1e-6); assert!(state.sheets[0].pockets.is_empty()); } } #[test] fn partial_depth_intersect_curved_to_object_preserves_current_trimmed_cap_footprint() { - use solvecraft_geom::Vec3; + use solidcraft_geom::Vec3; let mut s = Session::default(); base(&mut s, 100.0, 60.0); let name = s.model.state().sheets[0].body.clone(); @@ -1525,11 +1525,11 @@ fn partial_depth_intersect_curved_to_object_preserves_current_trimmed_cap_footpr run(&mut s, "solid.box", json!({"corner":[19,19,1],"length":12,"width":12,"height":1,"body_name":"StopPlate"})); run(&mut s, "solid.combine", json!({"target":"CurvedRoof","tools":["StopPlate"],"operation":"join"})); rectangle_tool(&mut s, "Tool", [20.0, 20.0], [30.0, 30.0]); - let mut tool_session = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut tool_session = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); run(&mut tool_session, "solid.extrude", json!({"sketch":"Tool","to":[27,25,10],"body_name":"TrimWitness"})); let ts = tool_session.model.state(); let witness = &ts.body("TrimWitness").unwrap().body; - assert!((solvecraft_kernel::measure(witness).unwrap().volume - (100.0 - 4.0 * std::f64::consts::PI)).abs() < 0.01); + assert!((solidcraft_kernel::measure(witness).unwrap().volume - (100.0 - 4.0 * std::f64::consts::PI)).abs() < 0.01); assert!(witness.contains(Vec3::new(21.0, 21.0, 0.5))); assert!(!witness.contains(Vec3::new(25.0, 25.0, 0.5))); run(&mut s, "solid.extrude", json!({"sketch":"Tool","to":[27,25,10],"operation":"intersect","participants":[name]})); @@ -1539,7 +1539,7 @@ fn partial_depth_intersect_curved_to_object_preserves_current_trimmed_cap_footpr } let state = s.model.state(); let body = &state.body(&name).unwrap().body; - let measured = solvecraft_kernel::measure(body).unwrap().volume; + let measured = solidcraft_kernel::measure(body).unwrap().volume; assert!((measured - (100.0 - 4.0 * std::f64::consts::PI)).abs() < 0.01); let area: f64 = state.sheets[0].flat_regions().unwrap().iter().map(|r| r.area()).sum(); assert!((area - (100.0 - 4.0 * std::f64::consts::PI)).abs() < 1e-8); @@ -1547,19 +1547,19 @@ fn partial_depth_intersect_curved_to_object_preserves_current_trimmed_cap_footpr assert!(!body.contains(Vec3::new(25.0, 25.0, 0.5))); assert!(!body.contains(Vec3::new(21.0, 21.0, 1.5))); } - let cold = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let cold = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(!cold.model.state().body(&name).unwrap().body.contains(Vec3::new(25.0, 25.0, 0.5))); let dxf = run(&mut s, "sheet.export_dxf", json!({"body":name}))["dxf"].as_str().unwrap().to_string(); assert!(dxf.contains("POCKET_TOP_Z1.000000_TO_2.500000_MM")); let outline = dxf_outline_only(&dxf); - assert_eq!(solvecraft_io::read_dxf(&outline).unwrap().len(), 5); + assert_eq!(solidcraft_io::read_dxf(&outline).unwrap().len(), 5); let mut outline_import = Session::default(); run(&mut outline_import, "sketch.insert_dxf", json!({"text":outline,"name":"Outline"})); run(&mut outline_import, "sketch.finish", json!({})); run(&mut outline_import, "solid.extrude", json!({"distance":1,"profiles":[{"point":[21,21]}]})); assert!((volume(&mut outline_import) - (100.0 - 4.0 * std::f64::consts::PI)).abs() < 0.01); assert!(!outline_import.model.state().bodies[0].body.contains(Vec3::new(25.0, 25.0, 0.5))); - let path = std::env::temp_dir().join(format!("solvecraft-current-trim-cap-{}.step", std::process::id())); + let path = std::env::temp_dir().join(format!("solidcraft-current-trim-cap-{}.step", std::process::id())); run(&mut s, "file.export", json!({"path":path.to_string_lossy(),"bodies":[name]})); let mut imported = Session::default(); run(&mut imported, "doc.open", json!({"path":path.to_string_lossy()})); @@ -1594,7 +1594,7 @@ fn partial_depth_intersect_sub_extrusion_resolution_layers_refuse_atomically() { assert!(error.contains("resolution") || error.contains("distance is zero") || error.contains("sheet machining"), "{error}"); assert_eq!(s.doc.to_json(), before); assert!(s.model.state().sheets[0].pockets.is_empty()); - assert!((solvecraft_kernel::measure(&s.world_state().body(&name).unwrap().body).unwrap().volume - 15000.0).abs() < 1e-5); + assert!((solidcraft_kernel::measure(&s.world_state().body(&name).unwrap().body).unwrap().volume - 15000.0).abs() < 1e-5); } } diff --git a/crates/engine/src/cmd/sketch.rs b/crates/engine/src/cmd/sketch.rs index c901995a..6888bd16 100644 --- a/crates/engine/src/cmd/sketch.rs +++ b/crates/engine/src/cmd/sketch.rs @@ -1,9 +1,9 @@ //! Sketch commands: create/edit/finish sketches, draw curves, constraints and dimensions. use serde_json::{Value, json}; -use solvecraft_doc::{FeatureKind, PlaneRef, expr::Kind}; -use solvecraft_geom::{Plane, Vec2, Vec3}; -use solvecraft_sketch::{Analysis, Budget, ConstraintKind, CurveKind, Hold, Sketch, SolveReport, solve, solve_with}; +use solidcraft_doc::{FeatureKind, PlaneRef, expr::Kind}; +use solidcraft_geom::{Plane, Vec2, Vec3}; +use solidcraft_sketch::{Analysis, Budget, ConstraintKind, CurveKind, Hold, Sketch, SolveReport, solve, solve_with}; use super::{CommandSpec, in_sketch}; use crate::params::{bad, bool_, expr, num, req_vec2, str_, string_list, vec2, vec3}; @@ -222,7 +222,7 @@ pub(super) fn edit( p: &Value, cmd: &str, strict: bool, - f: impl FnOnce(&mut Sketch, &mut solvecraft_doc::Document) -> Result, + f: impl FnOnce(&mut Sketch, &mut solidcraft_doc::Document) -> Result, ) -> Result<(T, Value)> { let id = target_sketch(s, p, cmd)?; let (out, rep) = edit_solved(s, id, strict, |sk, doc| Ok((f(sk, doc)?, Left::Unsolved)))?; @@ -262,7 +262,7 @@ pub(super) fn edit_solved( s: &mut Session, id: u64, strict: bool, - f: impl FnOnce(&mut Sketch, &mut solvecraft_doc::Document) -> Result<(T, Left)>, + f: impl FnOnce(&mut Sketch, &mut solidcraft_doc::Document) -> Result<(T, Left)>, ) -> Result<(T, Option)> { let mut doc = (*s.doc).clone(); let mut sk = doc.sketch(id)?.clone(); @@ -333,7 +333,7 @@ pub(super) fn counted_solve( /// Inside an `edit` closure: solve the sketch for the document's parameter values from its /// stored solved state, as the edit's own solve does, for commands that work on the current /// geometry. -pub(super) fn solve_current(sk: &mut Sketch, doc: &solvecraft_doc::Document) -> Result { +pub(super) fn solve_current(sk: &mut Sketch, doc: &solidcraft_doc::Document) -> Result { let stored = sk.driving_values(); let (values, _) = doc.param_values(); Ok(doc.solve_sketch(&values, sk, &stored)?) @@ -345,7 +345,7 @@ pub(super) fn solve_current(sk: &mut Sketch, doc: &solvecraft_doc::Document) -> pub(super) mod solve_counts { use std::cell::Cell; - use solvecraft_sketch::Analysis; + use solidcraft_sketch::Analysis; thread_local! { static COUNTS: Cell<(usize, usize)> = const { Cell::new((0, 0)) }; @@ -395,7 +395,7 @@ pub(super) fn draw( s: &mut Session, p: &Value, cmd: &str, - f: impl FnOnce(&mut Sketch, &mut solvecraft_doc::Document) -> Result<(Vec, Vec)>, + f: impl FnOnce(&mut Sketch, &mut solidcraft_doc::Document) -> Result<(Vec, Vec)>, ) -> Result<((Vec, Vec), Value)> { edit(s, p, cmd, false, |sk, doc| { let first = sk.points.len(); @@ -497,7 +497,7 @@ pub(crate) fn created(before: Option<&Sketch>, after: &Sketch) -> Value { let points: Vec<&str> = after.points.iter().map(|x| x.id.as_str()).filter(new).collect(); let curves: Vec<&str> = after.curves.iter().map(|x| x.id.as_str()).filter(new).collect(); let (dims, cons): (Vec<_>, Vec<_>) = after.constraints.iter().filter(|c| new(&c.id.as_str())).partition(|c| c.kind.is_dimension()); - let ids = |v: Vec<&solvecraft_sketch::Constraint>| v.into_iter().map(|c| c.id.clone()).collect::>(); + let ids = |v: Vec<&solidcraft_sketch::Constraint>| v.into_iter().map(|c| c.id.clone()).collect::>(); let mut out = json!({"points": points, "curves": curves, "constraints": ids(cons), "dimensions": ids(dims)}); let wires: Vec<&str> = after.wires.iter().map(|x| x.id.as_str()).filter(new).collect(); if !wires.is_empty() @@ -515,7 +515,7 @@ pub(super) fn result(sk_out: (Vec, Vec), info: Value) -> Value { // --------------------------------------------------------------------------------------------- // Sketch lifecycle -pub(crate) fn author_path_for_rebind(s: &Session, feature: &solvecraft_doc::Feature, p: &Value, cmd: &str) -> Result> { +pub(crate) fn author_path_for_rebind(s: &Session, feature: &solidcraft_doc::Feature, p: &Value, cmd: &str) -> Result> { let mut saved = None; if let FeatureKind::Sketch { plane, .. } = &feature.kind { let mut plane = plane; @@ -553,7 +553,7 @@ pub(crate) fn author_path_for_rebind(s: &Session, feature: &solvecraft_doc::Feat if feature.component != 0 && path.is_empty() { return Err(bad(cmd, "a component sketch needs an explicit complete author_path")); } - solvecraft_doc::EmbossFace::component_frame(&s.placed_document(), feature.component, &path)?; + solidcraft_doc::EmbossFace::component_frame(&s.placed_document(), feature.component, &path)?; Ok(path) } @@ -581,7 +581,7 @@ pub(super) fn plane_ref(s: &Session, p: &Value, cmd: &str) -> Result { } /// Does triangle set of face `fi` contain point `p` (within `tol` of the face plane)? -pub(super) fn face_contains(m: &solvecraft_geom::Mesh, fi: usize, p: Vec3, tol: f64) -> bool { +pub(super) fn face_contains(m: &solidcraft_geom::Mesh, fi: usize, p: Vec3, tol: f64) -> bool { m.triangles.iter().zip(&m.tri_face).filter(|(_, f)| **f as usize == fi).any(|(t, _)| { let Some([a, b, c]) = m.tri(t) else { return false }; let n = (b - a).cross(c - a); @@ -672,7 +672,7 @@ fn edit_sketch(s: &mut Session, p: &Value) -> Result { _ => None, }; if let Some(path) = authored_path { - solvecraft_doc::EmbossFace::component_frame(&s.placed_document(), component, &path) + solidcraft_doc::EmbossFace::component_frame(&s.placed_document(), component, &path) .map_err(|error| bad("sketch.edit", format!("{error}; explicitly rebind the author placement before editing")))?; s.active_component = component; s.active_occurrence_path = (!path.is_empty()).then_some(path); @@ -1282,7 +1282,7 @@ pub(super) fn apply_in_order(sk: &mut Sketch, rep: &SolveReport, first: &[Ent], const TEMP: &str = "__keep_size"; for (h, extra) in tries { let mut trial = sk.clone(); - trial.constraints.extend(extra.into_iter().map(|kind| solvecraft_sketch::Constraint { + trial.constraints.extend(extra.into_iter().map(|kind| solidcraft_sketch::Constraint { id: TEMP.into(), kind, param: None, @@ -1764,7 +1764,7 @@ fn measured(is_angle: bool, v: f64) -> (f64, &'static str) { fn solved_measure(s: &Session, p: &Value, cmd: &str) -> Option<(f64, &'static str)> { let sk = s.doc.sketch(target_sketch(s, p, cmd).ok()?).ok()?; let k = &sk.constraints.last()?.kind; - Some(measured(k.is_angle(), solvecraft_sketch::measure_dimension(sk, k)?)) + Some(measured(k.is_angle(), solidcraft_sketch::measure_dimension(sk, k)?)) } /// The type of a dimension between `a` and `b` (two points or a line's ends). `auto` takes it @@ -1793,8 +1793,8 @@ fn auto_orientation(ty: &str, a: Vec2, b: Vec2, text_at: Option) -> &str { /// dimension so it follows the geometry. fn place_text(sk: &mut Sketch, id: &str, at: Option) { let Some(i) = sk.constraints.iter().position(|c| c.id == id) else { return }; - let text = match (at, sk.constraints.get(i).and_then(|c| solvecraft_sketch::dim_frame(sk, &c.kind))) { - (Some(at), Some(f)) => Some(solvecraft_sketch::encode_text(&f, at)), + let text = match (at, sk.constraints.get(i).and_then(|c| solidcraft_sketch::dim_frame(sk, &c.kind))) { + (Some(at), Some(f)) => Some(solidcraft_sketch::encode_text(&f, at)), _ => None, }; if let Some(c) = sk.constraints.get_mut(i) { @@ -1827,7 +1827,7 @@ fn diameter_about(sk: &Sketch, l: usize, ty: &str) -> bool { fn arc_len(sk: &Sketch, c: usize) -> Option { match sk.segs(c).first()? { - solvecraft_geom::Seg2::Arc { radius, sweep, .. } if matches!(sk.curves.get(c)?.kind, CurveKind::Arc { .. }) => Some(radius * sweep), + solidcraft_geom::Seg2::Arc { radius, sweep, .. } if matches!(sk.curves.get(c)?.kind, CurveKind::Arc { .. }) => Some(radius * sweep), _ => None, } } @@ -1948,7 +1948,7 @@ fn sketch_delete(s: &mut Session, p: &Value) -> Result { && topology .roles .iter() - .any(|role| !role.retired && role.kind == solvecraft_sketch::OffsetRoleKind::Arc && !removed.contains(role.output.as_str())) + .any(|role| !role.retired && role.kind == solidcraft_sketch::OffsetRoleKind::Arc && !removed.contains(role.output.as_str())) { return Err(bad(cmd, "break the offset association before deleting only part of its curved boundary")); } @@ -2011,7 +2011,7 @@ fn drag_frame(s: &mut Session, p: &Value, live: bool) -> Result<(Value, Option = d.moving.iter().filter_map(|&(q, was)| Some(sk.point(q)? - was)).collect(); let at = from + moves.iter().fold(Vec2::ZERO, |a, m| a + *m) * (1.0 / moves.len().max(1) as f64); - let rep = rep.or_else(|| last.map(|last| SolveReport { status: solvecraft_sketch::SolveStatus::Solved, failing: Vec::new(), ..last })); + let rep = rep.or_else(|| last.map(|last| SolveReport { status: solidcraft_sketch::SolveStatus::Solved, failing: Vec::new(), ..last })); let out = json!({"sketch": rep.as_ref().map(report_json), "reached": d.reached, "at": [at.x, at.y]}); Ok((out, rep.filter(|_| live).map(|report| crate::LiveEdit { sketch: id, report }))) } @@ -2048,7 +2048,7 @@ fn drag_end(s: &mut Session, p: &Value) -> Result { } /// The Sketch Palette options of a sketch, by their palette names (true = shown / on). -fn options_json(v: &solvecraft_sketch::SketchView) -> Value { +fn options_json(v: &solidcraft_sketch::SketchView) -> Value { json!({ "show_profile": !v.hide_profile, "show_points": !v.hide_points, diff --git a/crates/engine/src/cmd/sketch_3d.rs b/crates/engine/src/cmd/sketch_3d.rs index bdd65665..07f865c5 100644 --- a/crates/engine/src/cmd/sketch_3d.rs +++ b/crates/engine/src/cmd/sketch_3d.rs @@ -3,8 +3,8 @@ //! drawn and snapped to, movable point by point, and usable as sweep and pipe paths. use serde_json::{Value, json}; -use solvecraft_geom::Vec3; -use solvecraft_sketch::WireSpace; +use solidcraft_geom::Vec3; +use solidcraft_sketch::WireSpace; use super::sketch::{edit, target_sketch}; use super::{CommandSpec, in_sketch}; @@ -29,26 +29,26 @@ pub static COMMANDS: &[CommandSpec] = &[ fn points(cmd: &str, p: &Value, min: usize) -> Result> { let a = p.get("points").and_then(Value::as_array).ok_or_else(|| bad(cmd, "`points` must list [x,y,z] points"))?; - if a.len() < min || a.len() > solvecraft_sketch::MAX_FIT_POINTS { - return Err(bad(cmd, format!("`points` needs {min} to {} points", solvecraft_sketch::MAX_FIT_POINTS))); + if a.len() < min || a.len() > solidcraft_sketch::MAX_FIT_POINTS { + return Err(bad(cmd, format!("`points` needs {min} to {} points", solidcraft_sketch::MAX_FIT_POINTS))); } a.iter().map(|v| vec3(v).ok_or_else(|| bad(cmd, "a point must be [x, y, z]"))).collect() } -fn needs_3d(sk: &solvecraft_sketch::Sketch, cmd: &str) -> Result<()> { +fn needs_3d(sk: &solidcraft_sketch::Sketch, cmd: &str) -> Result<()> { if sk.view.three_d { Ok(()) } else { Err(bad(cmd, "turn on 3D Sketch (sketch.options sketch_3d: true) to draw off the sketch plane")) } } /// World command inputs belong to the target sketch's owner, which may differ from the /// active component when a script supplies an explicit sketch. -fn inverse_owner_frame(s: &Session, p: &Value, cmd: &str) -> Result { +fn inverse_owner_frame(s: &Session, p: &Value, cmd: &str) -> Result { let id = target_sketch(s, p, cmd)?; let owner = s.doc.feature(id).map(|f| f.component).ok_or_else(|| bad(cmd, "target sketch is missing"))?; if owner != 0 && owner != s.active_component { if s.doc.occurrences.len() > 100_000 { return Err(bad(cmd, "sketch owner placement expansion exceeds supported bounds")); } - let mut children: std::collections::BTreeMap> = std::collections::BTreeMap::new(); + let mut children: std::collections::BTreeMap> = std::collections::BTreeMap::new(); for occurrence in &s.doc.occurrences { children.entry(occurrence.parent).or_default().push(occurrence); } @@ -72,13 +72,13 @@ fn inverse_owner_frame(s: &Session, p: &Value, cmd: &str) -> Result Result { let cmd = "sketch.line3d"; let inverse = inverse_owner_frame(s, p, cmd)?; - let pts: Vec = points(cmd, p, 2)?.into_iter().map(|at| solvecraft_doc::apply_point(&inverse, at)).collect(); + let pts: Vec = points(cmd, p, 2)?.into_iter().map(|at| solidcraft_doc::apply_point(&inverse, at)).collect(); let (ids, info) = edit(s, p, cmd, false, |sk, _| { needs_3d(sk, cmd)?; pts.windows(2).map(|w| sk.add_drawn_wire_in(w, WireSpace::Component).map_err(Into::into)).collect::>>() @@ -89,7 +89,7 @@ fn line3d(s: &mut Session, p: &Value) -> Result { fn spline3d(s: &mut Session, p: &Value) -> Result { let cmd = "sketch.spline3d"; let inverse = inverse_owner_frame(s, p, cmd)?; - let pts: Vec = points(cmd, p, 2)?.into_iter().map(|at| solvecraft_doc::apply_point(&inverse, at)).collect(); + let pts: Vec = points(cmd, p, 2)?.into_iter().map(|at| solidcraft_doc::apply_point(&inverse, at)).collect(); let (id, info) = edit(s, p, cmd, false, |sk, _| { needs_3d(sk, cmd)?; Ok(sk.add_drawn_wire_in(&pts, WireSpace::Component)?) @@ -100,7 +100,7 @@ fn spline3d(s: &mut Session, p: &Value) -> Result { fn point3d(s: &mut Session, p: &Value) -> Result { let cmd = "sketch.point3d"; let inverse = inverse_owner_frame(s, p, cmd)?; - let at = solvecraft_doc::apply_point(&inverse, req_vec3(cmd, p, "point")?); + let at = solidcraft_doc::apply_point(&inverse, req_vec3(cmd, p, "point")?); let (id, info) = edit(s, p, cmd, false, |sk, _| { needs_3d(sk, cmd)?; Ok(sk.add_drawn_wire_in(&[at], WireSpace::Component)?) @@ -116,7 +116,7 @@ fn move3d(s: &mut Session, p: &Value) -> Result { Some(v) => v .as_u64() .and_then(|v| usize::try_from(v).ok()) - .filter(|v| *v < solvecraft_sketch::MAX_FIT_POINTS) + .filter(|v| *v < solidcraft_sketch::MAX_FIT_POINTS) .ok_or_else(|| bad(cmd, "`index` must be a fit point index below 500"))?, }; let parse = |name| p.get(name).map(|v| vec3(v).ok_or_else(|| bad(cmd, format!("`{name}` must be [x,y,z]")))).transpose(); @@ -133,12 +133,12 @@ fn move3d(s: &mut Session, p: &Value) -> Result { let to = match (to, by) { (Some(t), _) => { if drawn.space == WireSpace::Component { - solvecraft_doc::apply_point(&inverse, t) + solidcraft_doc::apply_point(&inverse, t) } else { t } } - (None, Some(d)) => from + if drawn.space == WireSpace::Component { solvecraft_doc::apply_vector(&inverse, d) } else { d }, + (None, Some(d)) => from + if drawn.space == WireSpace::Component { solidcraft_doc::apply_vector(&inverse, d) } else { d }, _ => return Err(bad(cmd, "give `to` or `by`")), }; Ok(sk.move_drawn_point(&wire, index, to)?) diff --git a/crates/engine/src/cmd/sketch_3d_frame_tests.rs b/crates/engine/src/cmd/sketch_3d_frame_tests.rs index 12f2a0dd..1528a36e 100644 --- a/crates/engine/src/cmd/sketch_3d_frame_tests.rs +++ b/crates/engine/src/cmd/sketch_3d_frame_tests.rs @@ -1,7 +1,7 @@ use crate::Session; use serde_json::{Value, json}; -use solvecraft_geom::Vec3; -use solvecraft_sketch::WireSpace; +use solidcraft_geom::Vec3; +use solidcraft_sketch::WireSpace; fn run(s: &mut Session, cmd: &str, params: Value) -> Value { s.execute(cmd, ¶ms).unwrap_or_else(|e| panic!("{cmd}: {e}")) @@ -57,7 +57,7 @@ fn wire_frame_second_occurrence_world_inputs_move_and_reopen() { run(&mut s, "edit.redo", json!({})); run(&mut s, "sketch.move3d", json!({"wire":point,"to":[18,33,46]})); near(s.doc.sketch(sketch).unwrap().wires.iter().find(|w| w.id == point).unwrap().fit[0], Vec3::new(3.0, 2.0, 6.0)); - let restored = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let restored = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); assert_eq!(restored.sketch(sketch).unwrap().wires, s.doc.sketch(sketch).unwrap().wires); // Same definition at the first placement has the local geometry; selected second is exact. run(&mut s, "component.activate", json!({"component":component,"occurrence":first,"path":[first]})); @@ -144,18 +144,18 @@ fn wire_frame_include_other_component_refresh_break_lost_and_reopen() { run(&mut s, "sketch.break_link", json!({"link":link})); assert_eq!(s.doc.sketch(sketch).unwrap().wires[0].space, WireSpace::Component); assert!(s.doc.sketch(sketch).unwrap().wires[0].link.is_none()); - let restored = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let restored = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); assert_eq!(restored.sketch(sketch).unwrap().wires[0], s.doc.sketch(sketch).unwrap().wires[0]); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), before); // Lost references retain the last coordinates and explicit marker, with no guessing. let sk = s.doc_mut().sketch_mut(sketch).unwrap(); - sk.links.iter_mut().find(|l| l.id == link).unwrap().source = solvecraft_sketch::LinkSource::Edge { body: "Missing".into(), at: Vec3::ZERO }; + sk.links.iter_mut().find(|l| l.id == link).unwrap().source = solidcraft_sketch::LinkSource::Edge { body: "Missing".into(), at: Vec3::ZERO }; run(&mut s, "sketch.line", json!({"points":[[2,0],[3,0]]})); let sk = s.doc.sketch(sketch).unwrap(); assert!(sk.link(&link).unwrap().lost); assert_eq!(sk.wires[0], initial); - let restored = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let restored = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); assert_eq!(restored.sketch(sketch).unwrap().wires[0], initial); } @@ -180,7 +180,7 @@ fn wire_frame_pipe_component_and_world_paths_have_analytic_volume_and_position() let body = world.bodies.last().unwrap(); // Property measurement uses fine adaptive tessellation; the viewport mesh alone // undershoots a small circular profile by about 0.64% at its display chord tolerance. - let measure = solvecraft_kernel::measure(&body.body).unwrap(); + let measure = solidcraft_kernel::measure(&body.body).unwrap(); assert!((measure.volume - 40.0 * std::f64::consts::PI).abs() < 0.05, "{}", measure.volume); let bounds = body.mesh().bounds(); near((bounds.min + bounds.max) / 2.0, Vec3::new(17.0, 32.0, 50.0)); @@ -212,17 +212,17 @@ fn wire_frame_wide_owner_graph_has_bounded_indexed_traversal() { // A legal wide assembly used to force one whole occurrence scan for each leaf. let doc = s.doc_mut(); for i in 0..9998u64 { - doc.components.push(solvecraft_doc::Component { id: 10_000 + i, name: format!("Sibling{i}"), parent: 0 }); - doc.occurrences.push(solvecraft_doc::Occurrence { + doc.components.push(solidcraft_doc::Component { id: 10_000 + i, name: format!("Sibling{i}"), parent: 0 }); + doc.occurrences.push(solidcraft_doc::Occurrence { id: 100_000 + i, component: 10_000 + i, parent: 0, name: format!("Sibling{i}"), - transform: solvecraft_doc::IDENTITY, + transform: solidcraft_doc::IDENTITY, grounded: false, }); } - assert_eq!(super::inverse_owner_frame(&s, &json!({"sketch":target}), "sketch.point3d").unwrap(), solvecraft_doc::IDENTITY); + assert_eq!(super::inverse_owner_frame(&s, &json!({"sketch":target}), "sketch.point3d").unwrap(), solidcraft_doc::IDENTITY); } #[test] @@ -241,7 +241,7 @@ fn wire_frame_cross_owner_sweep_follows_world_path_edit_undo_and_reopen() { let check = |s: &Session, length: f64| { let world = s.world_state(); let body = world.bodies.last().unwrap(); - let measure = solvecraft_kernel::measure(&body.body).unwrap(); + let measure = solidcraft_kernel::measure(&body.body).unwrap(); assert!((measure.volume - 4.0 * std::f64::consts::PI * length).abs() < 0.05, "{}", measure.volume); near(measure.centroid, Vec3::new(17.0, 32.0, 45.0 + length / 2.0)); }; @@ -253,7 +253,7 @@ fn wire_frame_cross_owner_sweep_follows_world_path_edit_undo_and_reopen() { run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), saved); check(&s, 10.0); - let mut reopened = Session { doc: solvecraft_doc::format::read(&saved).unwrap().into(), ..Default::default() }; + let mut reopened = Session { doc: solidcraft_doc::format::read(&saved).unwrap().into(), ..Default::default() }; reopened.refresh(); check(&reopened, 10.0); } @@ -284,7 +284,7 @@ fn wire_frame_three_owner_sweep_profile_move_invalidates_warm_cache() { let state = session.world_state(); let body = state.bodies.last().unwrap(); assert_eq!(body.component, consumer["component"].as_u64().unwrap()); - let result = solvecraft_kernel::measure(&body.body).unwrap(); + let result = solidcraft_kernel::measure(&body.body).unwrap(); assert!((result.volume - 40.0 * std::f64::consts::PI).abs() < 0.05, "{}", result.volume); near(result.centroid, Vec3::new(x, 7.0, 10.0)); result @@ -301,7 +301,7 @@ fn wire_frame_three_owner_sweep_profile_move_invalidates_warm_cache() { assert_eq!(s.doc.component_transform(path_owner["component"].as_u64().unwrap()), fixed_path); assert_eq!(s.doc.component_transform(consumer["component"].as_u64().unwrap()), fixed_consumer); let warm = measure(&s, 7.0); - let mut cold = Session { doc: solvecraft_doc::format::read(&s.doc.to_json()).unwrap().into(), ..Default::default() }; + let mut cold = Session { doc: solidcraft_doc::format::read(&s.doc.to_json()).unwrap().into(), ..Default::default() }; cold.refresh(); let reopened = measure(&cold, 7.0); near(warm.centroid, reopened.centroid); diff --git a/crates/engine/src/cmd/sketch_canvas.rs b/crates/engine/src/cmd/sketch_canvas.rs index 3b34abf1..b884e784 100644 --- a/crates/engine/src/cmd/sketch_canvas.rs +++ b/crates/engine/src/cmd/sketch_canvas.rs @@ -1,8 +1,8 @@ //! Canvases (reference images on planes) and decals (images on planar faces). use serde_json::{Value, json}; -use solvecraft_doc::canvas::{Canvas, MAX_CANVAS_BYTES, base64_decode, base64_encode, image_info}; -use solvecraft_geom::Vec2; +use solidcraft_doc::canvas::{Canvas, MAX_CANVAS_BYTES, base64_decode, base64_encode, image_info}; +use solidcraft_geom::Vec2; use super::CommandSpec; use crate::params::{bad, bool_, num, str_, vec2}; @@ -31,7 +31,7 @@ fn image_bytes(p: &Value, cmd: &str) -> Result> { base64_decode(d).ok_or_else(|| bad(cmd, "`data` is not base64"))? } else { let path = str_(p, "path").filter(|x| !x.trim().is_empty() && x.len() < 4096).ok_or_else(|| bad(cmd, "give `path` or `data`"))?; - solvecraft_io::vfs::read(path, MAX_CANVAS_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))? + solidcraft_io::vfs::read(path, MAX_CANVAS_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))? }; if b.len() > MAX_CANVAS_BYTES { return Err(bad(cmd, "the image is too large")); @@ -158,7 +158,7 @@ fn delete_canvas(s: &mut Session, p: &Value) -> Result { #[cfg(test)] mod tests { use serde_json::json; - use solvecraft_doc::canvas::base64_encode; + use solidcraft_doc::canvas::base64_encode; use crate::Session; @@ -183,7 +183,7 @@ mod tests { assert!((s.doc.canvases[0].width - 200.0).abs() < 1e-9); s.execute("canvas.edit", &json!({"canvas": "Canvas1", "opacity": 0.8, "angle": 90})).unwrap(); // Undo-able and saved with the design. - let back = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); + let back = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); assert_eq!(back.canvases, s.doc.canvases); s.execute("canvas.delete", &json!({"canvas": id})).unwrap(); assert!(s.doc.canvases.is_empty()); diff --git a/crates/engine/src/cmd/sketch_constraints.rs b/crates/engine/src/cmd/sketch_constraints.rs index 670f8d09..7acd97f9 100644 --- a/crates/engine/src/cmd/sketch_constraints.rs +++ b/crates/engine/src/cmd/sketch_constraints.rs @@ -2,8 +2,8 @@ //! inference for interactive tools. use serde_json::{Value, json}; -use solvecraft_geom::Vec2; -use solvecraft_sketch::{ConstraintKind, CurveKind, Sketch}; +use solidcraft_geom::Vec2; +use solidcraft_sketch::{ConstraintKind, CurveKind, Sketch}; use super::sketch::{add_c, dof_now, edit, reject_redundant}; use super::{CommandSpec, in_sketch}; @@ -120,7 +120,7 @@ const MAX_TRIALS: usize = 2000; /// Add `k` if it removes a degree of freedom and the sketch still solves; else leave the /// sketch as it was. Returns the new dof when kept. -fn try_add(sk: &mut Sketch, doc: &mut solvecraft_doc::Document, k: ConstraintKind, dof: usize, dim: bool) -> Option { +fn try_add(sk: &mut Sketch, doc: &mut solidcraft_doc::Document, k: ConstraintKind, dof: usize, dim: bool) -> Option { let saved = sk.clone(); let param = if dim { let v = k.value()?; @@ -136,7 +136,7 @@ fn try_add(sk: &mut Sketch, doc: &mut solvecraft_doc::Document, k: ConstraintKin return None; } let mut trial = sk.clone(); - let rep = solvecraft_sketch::solve(&mut trial); + let rep = solidcraft_sketch::solve(&mut trial); if rep.ok() && rep.dof < dof { *sk = trial; return Some(rep.dof); @@ -148,7 +148,7 @@ fn try_add(sk: &mut Sketch, doc: &mut solvecraft_doc::Document, k: ConstraintKin None } -fn run_auto(sk: &mut Sketch, doc: &mut solvecraft_doc::Document, p: &Value, datum: usize) -> (Vec, usize) { +fn run_auto(sk: &mut Sketch, doc: &mut solidcraft_doc::Document, p: &Value, datum: usize) -> (Vec, usize) { use ConstraintKind::*; let tol = num(p, "tolerance").filter(|t| *t > 0.0).unwrap_or(0.01); let atol = num(p, "angle_tolerance").filter(|t| *t > 0.0).unwrap_or(1.0).to_radians(); @@ -156,7 +156,7 @@ fn run_auto(sk: &mut Sketch, doc: &mut solvecraft_doc::Document, p: &Value, datu let n0 = sk.constraints.len(); let mut dof = dof_now(sk); let mut trials = 0; - let mut attempt = |sk: &mut Sketch, doc: &mut solvecraft_doc::Document, k: ConstraintKind, dim: bool, dof: &mut usize| { + let mut attempt = |sk: &mut Sketch, doc: &mut solidcraft_doc::Document, k: ConstraintKind, dim: bool, dof: &mut usize| { if *dof == 0 || trials >= MAX_TRIALS { return; } @@ -284,7 +284,7 @@ fn guess(sk: &Sketch, ents: &[String], cmd: &str) -> Result { sk.resolve_point(r).map(|p| (Some(p), None)).ok_or_else(|| bad(cmd, format!("unknown sketch entity `{r}`"))) }; let line_dir = |c: usize| match sk.shape(c) { - Some(solvecraft_sketch::Shape::Line { a, b }) => (b - a).normalized(), + Some(solidcraft_sketch::Shape::Line { a, b }) => (b - a).normalized(), _ => None, }; let round = |c: usize| matches!(sk.curves.get(c).map(|c| &c.kind), Some(CurveKind::Circle { .. } | CurveKind::Arc { .. })); @@ -313,7 +313,7 @@ fn guess(sk: &Sketch, ents: &[String], cmd: &str) -> Result { if cross < 0.2 { // Parallel; on one line → collinear. let off = match (sk.shape(x), sk.shape(y)) { - (Some(solvecraft_sketch::Shape::Line { a, .. }), Some(solvecraft_sketch::Shape::Line { a: b0, .. })) => { + (Some(solidcraft_sketch::Shape::Line { a, .. }), Some(solidcraft_sketch::Shape::Line { a: b0, .. })) => { u.cross(b0 - a).abs() } _ => f64::INFINITY, diff --git a/crates/engine/src/cmd/sketch_constraints_tests.rs b/crates/engine/src/cmd/sketch_constraints_tests.rs index e600fd72..5070d57c 100644 --- a/crates/engine/src/cmd/sketch_constraints_tests.rs +++ b/crates/engine/src/cmd/sketch_constraints_tests.rs @@ -1,5 +1,5 @@ use serde_json::{Value, json}; -use solvecraft_geom::Vec2; +use solidcraft_geom::Vec2; use crate::Session; @@ -20,7 +20,7 @@ fn new_sketch() -> Session { s } -fn sketch(s: &Session) -> solvecraft_sketch::Sketch { +fn sketch(s: &Session) -> solidcraft_sketch::Sketch { s.model.state().sketch(s.active_sketch.unwrap()).unwrap().sketch.clone() } @@ -97,7 +97,7 @@ fn curvature_polygon_arc_length_linear_diameter() { let sk = sketch(&s); let ai = sk.curve_index(&a).unwrap(); let len = match sk.segs(ai)[0] { - solvecraft_geom::Seg2::Arc { radius, sweep, .. } => radius * sweep, + solidcraft_geom::Seg2::Arc { radius, sweep, .. } => radius * sweep, _ => 0.0, }; assert!((len - 20.0).abs() < 1e-6, "{len}"); diff --git a/crates/engine/src/cmd/sketch_corner_tests.rs b/crates/engine/src/cmd/sketch_corner_tests.rs index b34ea4c3..aca73784 100644 --- a/crates/engine/src/cmd/sketch_corner_tests.rs +++ b/crates/engine/src/cmd/sketch_corner_tests.rs @@ -23,7 +23,7 @@ fn curves(r: &Value) -> Vec { r["curves"].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_default() } -fn sk(s: &Session) -> solvecraft_sketch::Sketch { +fn sk(s: &Session) -> solidcraft_sketch::Sketch { s.model.state().sketch(s.active_sketch.unwrap()).unwrap().sketch.clone() } @@ -132,9 +132,9 @@ fn a_fillet_keeps_the_dimensions_of_its_corner_on_a_virtual_sharp() { // The virtual corner sits where the corner was, and the part keeps its size. let k = sk(&s); let vc = k.point(k.point_index(&corner).unwrap()).unwrap(); - assert!(vc.dist(solvecraft_geom::Vec2::new(0.0, 100.0)) < 1e-6, "{vc:?}"); + assert!(vc.dist(solidcraft_geom::Vec2::new(0.0, 100.0)) < 1e-6, "{vc:?}"); let top_end = k.point(k.resolve_point("top.end").unwrap()).unwrap(); - assert!(top_end.dist(solvecraft_geom::Vec2::new(-80.0, 100.0)) < 1e-6, "{top_end:?}"); + assert!(top_end.dist(solidcraft_geom::Vec2::new(-80.0, 100.0)) < 1e-6, "{top_end:?}"); let (lo, hi) = k.bounds().unwrap(); assert!((hi.x - lo.x - 80.0).abs() < 1e-6 && (hi.y - lo.y - 100.0).abs() < 1e-6, "{lo:?} {hi:?}"); // It is drawn with extension lines from both lines' ends to it. @@ -150,7 +150,7 @@ fn a_fillet_keeps_the_dimensions_of_its_corner_on_a_virtual_sharp() { assert!((hi.x - lo.x - 90.0).abs() < 1e-6 && (hi.y - lo.y - 120.0).abs() < 1e-6, "{lo:?} {hi:?}"); // It is kept in the design file, and undo and redo take it away and bring it back. let id = s.active_sketch.unwrap(); - let doc = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); + let doc = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); assert_eq!(doc.sketch(id).unwrap(), s.doc.sketch(id).unwrap()); run(&mut s, "edit.undo", json!({})); run(&mut s, "edit.undo", json!({})); diff --git a/crates/engine/src/cmd/sketch_create.rs b/crates/engine/src/cmd/sketch_create.rs index 92d61a99..0cf9478d 100644 --- a/crates/engine/src/cmd/sketch_create.rs +++ b/crates/engine/src/cmd/sketch_create.rs @@ -2,8 +2,8 @@ //! arc slots. use serde_json::{Value, json}; -use solvecraft_geom::Vec2; -use solvecraft_sketch::{ConstraintKind, CurveKind, LinkKind, LinkSource, Sketch}; +use solidcraft_geom::Vec2; +use solidcraft_sketch::{ConstraintKind, CurveKind, LinkKind, LinkSource, Sketch}; use super::sketch::{add_c, circumcircle, edit, ids_of, mark_construction, req_parg, result, slot}; use super::{CommandSpec, in_sketch}; @@ -92,7 +92,7 @@ fn text_source(p: &Value, cmd: &str, old: Option<(String, Vec2, f64, f64)>) -> R if text.trim().is_empty() { return Err(bad(cmd, "`text` must not be empty")); } - if text.chars().count() > solvecraft_sketch::MAX_TEXT_CHARS { + if text.chars().count() > solidcraft_sketch::MAX_TEXT_CHARS { return Err(bad(cmd, "text too long")); } let at = p.get("at").and_then(crate::params::vec2).unwrap_or(at0); @@ -136,7 +136,7 @@ fn edit_text(s: &mut Session, p: &Value) -> Result { }; let src = text_source(p, cmd, Some(old))?; let LinkSource::Text { text, at, height, angle } = &src else { return Err(bad(cmd, "text")) }; - let geom = solvecraft_sketch::text_geometry(text, *at, *height, *angle)?; + let geom = solidcraft_sketch::text_geometry(text, *at, *height, *angle)?; sk.set_link_source(&l, src.clone()); sk.update_link(&l, &geom)?; let curves = ids_of(&sk, &sk.link_curves(&l)); @@ -231,7 +231,7 @@ fn ellipse(s: &mut Session, p: &Value) -> Result { fn point_list(sk: &mut Sketch, p: &Value, cmd: &str) -> Result> { let list = p.get("points").and_then(Value::as_array).ok_or_else(|| bad(cmd, "`points` must be a list"))?; - if list.len() < 2 || list.len() > solvecraft_sketch::MAX_SPLINE_POINTS { + if list.len() < 2 || list.len() > solidcraft_sketch::MAX_SPLINE_POINTS { return Err(bad(cmd, "a spline needs 2 to 500 points")); } let mut out = Vec::new(); @@ -359,9 +359,9 @@ fn leaving_dir(sk: &Sketch, ci: usize, pi: usize) -> Option { CurveKind::Spline { ref pts, control, periodic, .. } => { let q: Vec = pts.iter().filter_map(|i| sk.point(*i)).collect(); if pts.last() == Some(&pi) { - solvecraft_sketch::spline_end_tangent_mode(&q, control, periodic, true) + solidcraft_sketch::spline_end_tangent_mode(&q, control, periodic, true) } else if pts.first() == Some(&pi) { - solvecraft_sketch::spline_end_tangent_mode(&q, control, periodic, false).map(|t| -t) + solidcraft_sketch::spline_end_tangent_mode(&q, control, periodic, false).map(|t| -t) } else { None } @@ -441,7 +441,7 @@ fn tangent_circle(s: &mut Session, p: &Value, cmd: &str, n: usize, radius: Optio let (out, info) = edit(s, p, cmd, true, |sk, doc| { let cs = tangent_curves(sk, p, cmd, n)?; let r0 = match &radius { - Some(e) => doc.eval(e, solvecraft_doc::expr::Kind::Length).map_err(|e| bad(cmd, format!("radius: {e}")))?, + Some(e) => doc.eval(e, solidcraft_doc::expr::Kind::Length).map_err(|e| bad(cmd, format!("radius: {e}")))?, None => guess_radius(sk, &cs, near), }; if !(r0 > 1e-9 && r0 < 1e8) { @@ -564,7 +564,7 @@ pub(super) fn solve_new_only(sk: &mut Sketch, first_new: usize) { p.fixed = true; } } - if solvecraft_sketch::solve(&mut trial).ok() { + if solidcraft_sketch::solve(&mut trial).ok() { for (p, t) in sk.points.iter_mut().zip(&trial.points).skip(first_new) { p.pos = t.pos; } diff --git a/crates/engine/src/cmd/sketch_dimensioned.rs b/crates/engine/src/cmd/sketch_dimensioned.rs index f9ed36c9..7f04d883 100644 --- a/crates/engine/src/cmd/sketch_dimensioned.rs +++ b/crates/engine/src/cmd/sketch_dimensioned.rs @@ -1,6 +1,6 @@ //! Shape creation and its typed driving dimensions form one command and one undo step. use serde_json::{Value, json}; -use solvecraft_doc::expr::Kind; +use solidcraft_doc::expr::Kind; use crate::{ Result, Session, diff --git a/crates/engine/src/cmd/sketch_dimensioned_tests.rs b/crates/engine/src/cmd/sketch_dimensioned_tests.rs index 7161c63f..b949794c 100644 --- a/crates/engine/src/cmd/sketch_dimensioned_tests.rs +++ b/crates/engine/src/cmd/sketch_dimensioned_tests.rs @@ -1,6 +1,6 @@ use crate::Session; use serde_json::{Value, json}; -use solvecraft_sketch::ConstraintKind; +use solidcraft_sketch::ConstraintKind; fn run(s: &mut Session, command: &str, params: Value) -> Value { s.execute(command, ¶ms).unwrap_or_else(|error| panic!("{command} {params}: {error}")) @@ -10,7 +10,7 @@ fn session() -> Session { run(&mut s, "sketch.create", json!({"plane":"XY"})); s } -fn sketch(s: &Session) -> solvecraft_sketch::Sketch { +fn sketch(s: &Session) -> solidcraft_sketch::Sketch { s.model.state().sketch(s.active_sketch.unwrap()).unwrap().sketch.clone() } fn draw(s: &mut Session, command: &str, params: Value, dimensions: Value) -> Value { @@ -22,7 +22,7 @@ fn close(a: f64, b: f64) { fn measure(s: &Session, kind: fn(&ConstraintKind) -> bool) -> f64 { let sk = sketch(s); let c = sk.constraints.iter().find(|c| kind(&c.kind)).unwrap(); - solvecraft_sketch::measure_dimension(&sk, &c.kind).unwrap() + solidcraft_sketch::measure_dimension(&sk, &c.kind).unwrap() } #[test] @@ -41,7 +41,7 @@ fn typed_line_angle_survives_quadrants_move_reopen_and_one_undo() { close(measure(&s, |k| matches!(k, ConstraintKind::Length { .. })), 10.0); } let encoded = serde_json::to_value(&*s.doc).unwrap(); - let doc: solvecraft_doc::Document = serde_json::from_value(encoded).unwrap(); + let doc: solidcraft_doc::Document = serde_json::from_value(encoded).unwrap(); let mut reopened = Session { doc: std::sync::Arc::new(doc), active_sketch: s.active_sketch, ..Session::default() }; reopened.refresh(); close(measure(&reopened, |k| matches!(k, ConstraintKind::LineAngle { .. })), 90f64.to_radians()); @@ -161,7 +161,7 @@ fn dimensioned_authoring_uses_selected_occurrence_and_keeps_local_geometry() { assert_eq!(s.active_occurrence_path, Some(vec![second])); let sk = sketch(&s); let line = sk.curve_index(out["curves"][0].as_str().unwrap()).unwrap(); - let solvecraft_sketch::CurveKind::Line { a, b } = sk.curves[line].kind else { panic!("line") }; + let solidcraft_sketch::CurveKind::Line { a, b } = sk.curves[line].kind else { panic!("line") }; close(sk.points[a].pos.x, 0.0); close(sk.points[b].pos.x, 5.0 / std::f64::consts::SQRT_2); } diff --git a/crates/engine/src/cmd/sketch_drag.rs b/crates/engine/src/cmd/sketch_drag.rs index 0c82a2fd..189dd597 100644 --- a/crates/engine/src/cmd/sketch_drag.rs +++ b/crates/engine/src/cmd/sketch_drag.rs @@ -14,8 +14,8 @@ use std::f64::consts::{PI, TAU}; -use solvecraft_geom::Vec2; -use solvecraft_sketch::{Analysis, Budget, ConstraintKind, CurveKind, Hold, Sketch}; +use solidcraft_geom::Vec2; +use solidcraft_sketch::{Analysis, Budget, ConstraintKind, CurveKind, Hold, Sketch}; use super::{Ent, Left, counted_solve}; diff --git a/crates/engine/src/cmd/sketch_drag_live_tests.rs b/crates/engine/src/cmd/sketch_drag_live_tests.rs index e13670a4..268b7d6b 100644 --- a/crates/engine/src/cmd/sketch_drag_live_tests.rs +++ b/crates/engine/src/cmd/sketch_drag_live_tests.rs @@ -2,7 +2,7 @@ //! rank analysis, and evaluates nothing after it; the gesture's end evaluates the model once. use serde_json::{Value, json}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use super::solve_counts; use crate::Session; @@ -49,12 +49,12 @@ fn body_contains(s: &Session, p: Vec3) -> bool { s.model.state().bodies.first().expect("a body").body.contains(p) } -fn pt(s: &Session, id: &str) -> solvecraft_geom::Vec2 { +fn pt(s: &Session, id: &str) -> solidcraft_geom::Vec2 { let sk = s.active_sketch.and_then(|i| s.doc.sketch(i).ok()).expect("sketch mode"); sk.resolve_point(id).and_then(|i| sk.point(i)).expect("the point") } -fn corner(s: &Session) -> solvecraft_geom::Vec2 { +fn corner(s: &Session) -> solidcraft_geom::Vec2 { let id = s.active_sketch.expect("sketch mode"); let st = s.world_state(); let sk = &st.sketch(id).expect("the shown sketch").sketch; @@ -73,7 +73,7 @@ fn a_live_frame_solves_once_and_leaves_the_features_after_the_sketch_stale() { assert_eq!(r["sketch"]["solved"], json!(true)); assert_eq!(r["sketch"]["dof"], json!(2), "the degrees of freedom from before the drag"); // The sketch shows its new place; the extrude keeps its last result. - assert!(corner(&s).dist(solvecraft_geom::Vec2::new(4.0, 5.0)) < 1e-9); + assert!(corner(&s).dist(solidcraft_geom::Vec2::new(4.0, 5.0)) < 1e-9); let result = s.model.result(extrude).expect("the extrude's result"); assert!(result.stale); assert!(s.model.state().bodies.first().expect("a body").body.same(&body), "the body is not rebuilt"); @@ -100,7 +100,7 @@ fn the_drag_end_evaluates_once_to_the_scripted_drags_result() { assert!(live.model.result(extrude).is_some_and(|r| !r.stale && r.error.is_none())); // One undo step for the gesture is the UI's business; each frame is still undoable. run(&mut live, "edit.undo", json!({})); - assert!(corner(&live).dist(solvecraft_geom::Vec2::new(3.0, 5.0)) < 1e-9); + assert!(corner(&live).dist(solidcraft_geom::Vec2::new(3.0, 5.0)) < 1e-9); } #[test] @@ -150,7 +150,7 @@ fn a_drag_reports_how_far_the_geometry_followed() { assert_eq!(r["reached"], json!(0.0), "{r}"); let moved = pt(&s, "l1.end") - before; let at = crate::params::vec2(&r["at"]).expect("`at`"); - assert!(at.dist(solvecraft_geom::Vec2::new(25.0, 10.0) + moved) < 1e-9 && at.y < 20.0, "{r}"); + assert!(at.dist(solidcraft_geom::Vec2::new(25.0, 10.0) + moved) < 1e-9 && at.y < 20.0, "{r}"); let to = 40 + 10 * usize::from(live); let r = run(&mut s, "sketch.drag", json!({"entity": "l2", "from": [25, to - 10], "to": [25, to]})); assert_eq!((&r["reached"], &r["at"]), (&json!(1.0), &json!([25.0, to as f64])), "{r}"); @@ -170,7 +170,7 @@ fn a_live_drag_of_a_lone_sketch_shows_it_without_evaluating() { assert_eq!(s.evaluations, 0); assert_eq!(solve_counts::take(), (2, 0)); // The free rectangle's corner moves alone; its profile follows. - assert!(corner(&s).dist(solvecraft_geom::Vec2::new(3.0, 5.0)) < 1e-9); + assert!(corner(&s).dist(solidcraft_geom::Vec2::new(3.0, 5.0)) < 1e-9); let st = s.model.state(); let profile = st.sketches[0].profiles.first().expect("a profile"); assert!((profile.area - 22.0 * 15.0).abs() < 1e-9, "{}", profile.area); @@ -199,8 +199,8 @@ fn the_grab_follows_the_points_that_move() { run(&mut s, "sketch.line", json!({"points": [[10, 10], [30, 10]]})); run(&mut s, "sketch.constraint.fix", json!({"entities": ["l1.start"]})); ui_frames(&mut s, "l1", [20.0, 10.0], &[[0.0, 1.0]; 6]); - assert!(pt(&s, "l1.end").dist(solvecraft_geom::Vec2::new(30.0, 16.0)) < 1e-9, "{:?}", pt(&s, "l1.end")); - assert!(pt(&s, "l1.start").dist(solvecraft_geom::Vec2::new(10.0, 10.0)) < 1e-12); + assert!(pt(&s, "l1.end").dist(solidcraft_geom::Vec2::new(30.0, 16.0)) < 1e-9, "{:?}", pt(&s, "l1.end")); + assert!(pt(&s, "l1.start").dist(solidcraft_geom::Vec2::new(10.0, 10.0)) < 1e-12); } #[test] @@ -219,7 +219,7 @@ fn a_query_during_a_live_drag_does_not_evaluate_the_model() { assert_eq!(s.evaluations, 0); } -fn sketch_of(s: &Session) -> solvecraft_sketch::Sketch { +fn sketch_of(s: &Session) -> solidcraft_sketch::Sketch { s.active_sketch.and_then(|i| s.doc.sketch(i).ok()).cloned().expect("sketch mode") } @@ -232,7 +232,7 @@ fn a_live_frame_solves_the_dimensions_current_values() { let param = sk .constraints .iter() - .find(|c| matches!(c.kind, solvecraft_sketch::ConstraintKind::Length { l, .. } if l == l1)) + .find(|c| matches!(c.kind, solidcraft_sketch::ConstraintKind::Length { l, .. } if l == l1)) .and_then(|c| c.param.clone()) .expect("l1's dimension"); if let Some(p) = s.doc_mut().params.iter_mut().find(|p| p.name == param) { diff --git a/crates/engine/src/cmd/sketch_face.rs b/crates/engine/src/cmd/sketch_face.rs index b71e0283..94f0f0f9 100644 --- a/crates/engine/src/cmd/sketch_face.rs +++ b/crates/engine/src/cmd/sketch_face.rs @@ -2,8 +2,8 @@ use crate::params::{bad, vec3}; use crate::{Result, Session}; use serde_json::Value; -use solvecraft_doc::{EmbossFace, PlaneRef, apply_point, apply_vector, mat_inverse, mat_mul}; -use solvecraft_geom::Plane; +use solidcraft_doc::{EmbossFace, PlaneRef, apply_point, apply_vector, mat_inverse, mat_mul}; +use solidcraft_geom::Plane; pub(in crate::cmd) fn is_face(p: &Value) -> bool { p.get("plane").and_then(|plane| plane.get("face")).is_some() @@ -144,10 +144,10 @@ fn capture_impl(s: &Session, value: &Value, cmd: &str, internal: bool) -> Result return Ok(PlaneRef::Custom { plane }); } let at = apply_point(&author_inverse, point); - let name = solvecraft_doc::naming::face_names(body).get(index).cloned(); + let name = solidcraft_doc::naming::face_names(body).get(index).cloned(); let anchor = match doc.capture_face_anchor(body, index, &plane, &path, s.active_component, &author_path) { Ok(anchor) => Some(Box::new(anchor)), - Err(solvecraft_doc::DocError::Invalid(message)) + Err(solidcraft_doc::DocError::Invalid(message)) if internal && matches!( message.as_str(), @@ -166,7 +166,7 @@ fn capture_impl(s: &Session, value: &Value, cmd: &str, internal: bool) -> Result } /// The sole durable face attachment beneath an offset/angle wrapper. -pub(in crate::cmd) fn anchor(mut plane: &PlaneRef) -> Option<&solvecraft_doc::FaceAnchor> { +pub(in crate::cmd) fn anchor(mut plane: &PlaneRef) -> Option<&solidcraft_doc::FaceAnchor> { loop { match plane { PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } => plane = base, @@ -176,7 +176,7 @@ pub(in crate::cmd) fn anchor(mut plane: &PlaneRef) -> Option<&solvecraft_doc::Fa } } -pub(in crate::cmd) fn anchor_mut(mut plane: &mut PlaneRef) -> Option<&mut solvecraft_doc::FaceAnchor> { +pub(in crate::cmd) fn anchor_mut(mut plane: &mut PlaneRef) -> Option<&mut solidcraft_doc::FaceAnchor> { loop { match plane { PlaneRef::Offset { base, .. } | PlaneRef::AtAngle { base, .. } => plane = base, diff --git a/crates/engine/src/cmd/sketch_face_tests.rs b/crates/engine/src/cmd/sketch_face_tests.rs index e2798552..4d9b276e 100644 --- a/crates/engine/src/cmd/sketch_face_tests.rs +++ b/crates/engine/src/cmd/sketch_face_tests.rs @@ -1,6 +1,6 @@ use crate::Session; use serde_json::{Value, json}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; fn run(s: &mut Session, command: &str, params: Value) -> Value { s.execute(command, ¶ms).unwrap_or_else(|error| panic!("{command} {params}: {error}")) @@ -41,7 +41,7 @@ fn exact_selected_base_follows_height_and_never_retargets_control() { assert!(s.model.state().body("Badge").is_none()); assert!((s.world_state().body("Control").unwrap().mesh().bounds().max.z - 30.).abs() < 1e-6); let before = s.doc.to_json(); - let reopened = Session::new(solvecraft_doc::Document::from_json(&before).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&before).unwrap()); assert!(reopened.model.result(sketch).unwrap().error.is_some()); } #[test] @@ -69,7 +69,7 @@ fn explicit_rebind_preserves_geometry_dimensions_context_and_one_undo() { assert_eq!(s.doc.to_json(), before); assert!(s.model.result(id).unwrap().error.is_some()); run(&mut s, "edit.redo", json!({})); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!((reopened.model.state().sketch(id).unwrap().plane.origin.z - 30.).abs() < 1e-6); } #[test] @@ -84,7 +84,7 @@ fn unsupported_face_has_explicit_fixed_alternative_and_no_live_projection() { assert_eq!(s.doc.to_json(), before); pick["fixed"] = json!(true); let id = run(&mut s, "sketch.create", json!({"plane":pick}))["sketch"].as_u64().unwrap(); - assert!(matches!(s.doc.feature(id).unwrap().kind, solvecraft_doc::FeatureKind::Sketch { plane: solvecraft_doc::PlaneRef::Custom { .. }, .. })); + assert!(matches!(s.doc.feature(id).unwrap().kind, solidcraft_doc::FeatureKind::Sketch { plane: solidcraft_doc::PlaneRef::Custom { .. }, .. })); assert!(s.doc.sketch(id).unwrap().links.is_empty()); } #[test] @@ -130,7 +130,7 @@ fn default_certified_borders_follow_width_and_height_with_stable_entities() { let max_y = sketch.sketch.points.iter().map(|point| point.pos.y).fold(f64::NEG_INFINITY, f64::max); let min_y = sketch.sketch.points.iter().map(|point| point.pos.y).fold(f64::INFINITY, f64::min); assert!(((max_x - min_x) * (max_y - min_y) - 160.).abs() < 1e-5); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(reopened.model.state().sketch(id).unwrap().sketch.links.iter().all(|link| !link.lost)); } @@ -175,7 +175,7 @@ fn exact_nested_source_and_cross_owner_pending_paths_capture_only_involved_posit let before = s.doc.to_json(); let pending = s.pending_moves.clone(); let id = run(&mut s, "sketch.create", json!({"plane":pick,"project_edges":false}))["sketch"].as_u64().unwrap(); - let solvecraft_doc::FeatureKind::Sketch { plane, .. } = &s.doc.feature(id).unwrap().kind else { panic!("not sketch") }; + let solidcraft_doc::FeatureKind::Sketch { plane, .. } = &s.doc.feature(id).unwrap().kind else { panic!("not sketch") }; let anchor = super::anchor(plane).unwrap(); assert_eq!(anchor.path, vec![second, child_occ]); assert_eq!(anchor.author_path, vec![author["occurrence"].as_u64().unwrap()]); @@ -186,7 +186,7 @@ fn exact_nested_source_and_cross_owner_pending_paths_capture_only_involved_posit assert_eq!(s.doc.to_json(), before); assert_eq!(s.pending_moves, pending); run(&mut s, "edit.redo", json!({})); - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&saved).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&saved).unwrap()); assert!(reopened.model.result(id).unwrap().error.is_none()); run(&mut reopened, "component.activate", json!({"component":0})); let original = reopened.doc.to_json(); @@ -225,7 +225,7 @@ fn fixed_rebind_freezes_current_resized_borders_and_only_detaches_auto_links() { visible.curves.iter().map(|curve| curve.id.clone()).collect::>() ); assert_eq!(fixed.points.iter().map(|point| point.pos).collect::>(), visible.points.iter().map(|point| point.pos).collect::>()); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(reopened.model.result(id).unwrap().error.is_none()); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), before); @@ -249,13 +249,13 @@ fn strict_anchor_timeline_copy_remaps_producer_and_refuses_external_translation_ let pasted = run(&mut s, "timeline.paste", json!({"translate":[30,0,0]})); assert!(pasted["errors"].as_array().unwrap().is_empty(), "{pasted}"); let new_id = pasted["pasted"][1]["id"].as_u64().unwrap(); - let solvecraft_doc::FeatureKind::Sketch { plane, .. } = &s.doc.feature(new_id).unwrap().kind else { panic!("not sketch") }; + let solidcraft_doc::FeatureKind::Sketch { plane, .. } = &s.doc.feature(new_id).unwrap().kind else { panic!("not sketch") }; let anchor = super::anchor(plane).unwrap(); assert_ne!(anchor.target.origin_feature, s.doc.find_feature("BaseFeature").unwrap().id); assert_ne!(anchor.target.body, "Base"); assert!(s.model.state().sketch(new_id).unwrap().sketch.links.iter().all(|link| !link.lost)); assert!(s.model.state().sketch(id).unwrap().sketch.links.iter().all(|link| !link.lost)); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(reopened.model.result(new_id).unwrap().error.is_none()); } @@ -274,10 +274,10 @@ fn strict_anchor_deep_clone_is_independent_and_rename_preserves_certified_projec .doc .features .iter() - .find(|feature| feature.component == owner && matches!(feature.kind, solvecraft_doc::FeatureKind::Sketch { .. })) + .find(|feature| feature.component == owner && matches!(feature.kind, solidcraft_doc::FeatureKind::Sketch { .. })) .unwrap(); let new_id = new_feature.id; - let solvecraft_doc::FeatureKind::Sketch { plane, .. } = &new_feature.kind else { panic!("not sketch") }; + let solidcraft_doc::FeatureKind::Sketch { plane, .. } = &new_feature.kind else { panic!("not sketch") }; let anchor = super::anchor(plane).unwrap(); let body = anchor.target.body.clone(); assert_eq!(anchor.component, owner); @@ -287,7 +287,7 @@ fn strict_anchor_deep_clone_is_independent_and_rename_preserves_certified_projec run(&mut s, "body.rename", json!({"body":body,"name":"Independent"})); assert!(s.model.result(new_id).unwrap().error.is_none()); assert!(s.model.state().sketch(new_id).unwrap().sketch.links.iter().all(|link| !link.lost)); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(reopened.model.result(new_id).unwrap().error.is_none()); } @@ -308,7 +308,7 @@ fn legacy_or_fixed_rebind_needs_explicit_author_path_for_repeated_owner() { assert!(error.to_string().contains("ambiguous"), "{error}"); assert_eq!(s.doc.to_json(), before); run(&mut s, "sketch.redefine", json!({"sketch":id,"plane":pick,"author_path":[second]})); - let solvecraft_doc::FeatureKind::Sketch { plane, .. } = &s.doc.feature(id).unwrap().kind else { panic!("not sketch") }; + let solidcraft_doc::FeatureKind::Sketch { plane, .. } = &s.doc.feature(id).unwrap().kind else { panic!("not sketch") }; assert_eq!(super::anchor(plane).unwrap().author_path, vec![second]); assert_eq!(s.active_component, 0); } @@ -370,12 +370,12 @@ fn freezing_rebuilt_arc_borders_keeps_authored_dimension_entity_indices() { let dimension = frozen .constraints .iter() - .find(|constraint| matches!(constraint.kind, solvecraft_sketch::ConstraintKind::Radius { c, .. } if c == circle_index)) + .find(|constraint| matches!(constraint.kind, solidcraft_sketch::ConstraintKind::Radius { c, .. } if c == circle_index)) .unwrap(); - assert!((solvecraft_sketch::measure_dimension(frozen, &dimension.kind).unwrap() - 2.).abs() < 1e-6); + assert!((solidcraft_sketch::measure_dimension(frozen, &dimension.kind).unwrap() - 2.).abs() < 1e-6); assert_eq!(frozen.curves.iter().map(|curve| &curve.id).collect::>(), visible.curves.iter().map(|curve| &curve.id).collect::>()); assert!(s.model.result(id).unwrap().error.is_none()); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert!(reopened.model.result(id).unwrap().error.is_none()); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), before); @@ -388,21 +388,21 @@ fn angled_attachment_tracks_source_plane_and_reopens() { let pick = top_pick(&s, "Base"); let id = run(&mut s, "sketch.create", json!({"plane":pick,"project_edges":false}))["sketch"].as_u64().unwrap(); run(&mut s, "sketch.finish", json!({})); - let solvecraft_doc::FeatureKind::Sketch { plane, .. } = &mut s.doc_mut().feature_mut(id).unwrap().kind else { panic!("not sketch") }; - *plane = solvecraft_doc::PlaneRef::AtAngle { base: Box::new(plane.clone()), axis_origin: Vec3::ZERO, axis_dir: Vec3::X, angle: "90 deg".into() }; + let solidcraft_doc::FeatureKind::Sketch { plane, .. } = &mut s.doc_mut().feature_mut(id).unwrap().kind else { panic!("not sketch") }; + *plane = solidcraft_doc::PlaneRef::AtAngle { base: Box::new(plane.clone()), axis_origin: Vec3::ZERO, axis_dir: Vec3::X, angle: "90 deg".into() }; s.refresh(); let height = s.doc.find_feature("BaseFeature").unwrap().param_names[2].clone(); run(&mut s, "parameters.change", json!({"name":height,"expression":"20 mm"})); let placed = s.model.state().sketch(id).unwrap().plane; assert!((placed.origin.y + 20.).abs() < 1e-6); assert!(placed.origin.z.abs() < 1e-6); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert_eq!(reopened.model.state().sketch(id).unwrap().plane, placed); } #[test] fn rounded_enclosure_shell_side_and_floor_attach_through_width_wall_and_reopen() { - use solvecraft_doc::{FeatureKind, config::FaceCertificate, face_anchor::plane_face_anchor}; + use solidcraft_doc::{FeatureKind, config::FaceCertificate, face_anchor::plane_face_anchor}; let mut s = Session::default(); for (name, expression) in [("width", "80 mm"), ("depth", "60 mm"), ("height", "30 mm"), ("wall", "2 mm")] { run(&mut s, "parameters.add", json!({"name":name,"expression":expression})); @@ -463,7 +463,7 @@ fn rounded_enclosure_shell_side_and_floor_attach_through_width_wall_and_reopen() } run(&mut s, "parameters.change", json!({"name":"width","expression":"100 mm"})); run(&mut s, "parameters.change", json!({"name":"wall","expression":"3 mm"})); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); for session in [&s, &reopened] { for id in [side, floor] { let result = session.model.result(id).unwrap(); @@ -481,7 +481,7 @@ fn rounded_enclosure_shell_side_and_floor_attach_through_width_wall_and_reopen() assert!(state.sketch(floor).unwrap().plane.normal().dot(Vec3::Z) > 0.999); let mesh = state.body("Case").unwrap().body.tessellate_with(1e-4, true).unwrap(); let case = &state.body("Case").unwrap().body; - let corners: Vec<_> = (0..case.face_count()).filter(|i| matches!(solvecraft_kernel::face_surface(case, *i), Some(solvecraft_kernel::FaceSurface::Cylinder { radius, .. }) if (radius - 6.).abs() < 1e-6)).collect(); + let corners: Vec<_> = (0..case.face_count()).filter(|i| matches!(solidcraft_kernel::face_surface(case, *i), Some(solidcraft_kernel::FaceSurface::Cylinder { radius, .. }) if (radius - 6.).abs() < 1e-6)).collect(); assert_eq!(corners.len(), 4, "later port/boss features must not subdivide untouched outer corners"); for index in corners { let points: Vec<_> = mesh @@ -498,7 +498,7 @@ fn rounded_enclosure_shell_side_and_floor_attach_through_width_wall_and_reopen() assert!(!mesh.contains(Vec3::new(49., 0., 12.)), "USB must remain open after resize/reopen"); assert!(mesh.contains(Vec3::new(49., 15., 12.)), "adjacent right wall remains solid"); assert!(mesh.contains(Vec3::new(-49., 0., 12.)), "opposite wall must stay solid"); - let seed = state.sketch(floor).unwrap().plane.to_world(solvecraft_geom::Vec2::new(40., 20.)) + Vec3::Z * 6.; + let seed = state.sketch(floor).unwrap().plane.to_world(solidcraft_geom::Vec2::new(40., 20.)) + Vec3::Z * 6.; assert!(mesh.contains(seed), "seed boss starts on current floor"); for shift in [Vec3::ZERO, Vec3::new(-80., 0., 0.), Vec3::new(0., -40., 0.), Vec3::new(-80., -40., 0.)] { assert!(mesh.contains(seed + shift), "each patterned boss must survive resize/reopen at {:?}", seed + shift); @@ -525,14 +525,14 @@ fn offset_rectangle_pocket_has_one_logical_outer_wall_and_closed_face_projection run(&mut s, "sketch.finish", json!({})); run(&mut s, "solid.extrude", json!({"sketch":control_sketch,"distance":10,"operation":"new","body_name":"Control"})); let untouched = s.model.state().body("Control").unwrap().body.clone(); - let before_control = solvecraft_kernel::step_export(&[&untouched], "control snapshot").unwrap(); + let before_control = solidcraft_kernel::step_export(&[&untouched], "control snapshot").unwrap(); let pick = top_pick(&s, "Stock"); let pocket = run(&mut s, "sketch.create", json!({"plane":pick}))["sketch"].as_u64().unwrap(); let side = { let state = s.model.state(); let sk = &state.sketch(pocket).unwrap().sketch; let curve = sk.curves.iter().find(|c| c.id == "l1").unwrap(); - let solvecraft_sketch::CurveKind::Line { a, b } = curve.kind else { panic!() }; + let solidcraft_sketch::CurveKind::Line { a, b } = curve.kind else { panic!() }; sk.point(a).unwrap() + sk.point(b).unwrap() }; let outer = run(&mut s, "sketch.offset", json!({"curves":["l1"],"distance":0.3425231845240546,"side":[side.x,side.y]}))["curves"].clone(); @@ -541,7 +541,7 @@ fn offset_rectangle_pocket_has_one_logical_outer_wall_and_closed_face_projection run(&mut s, "solid.extrude", json!({"sketch":pocket,"profiles":[{"curves":inner}],"distance":-9,"operation":"cut"})); let control = s.model.state().body("Control").unwrap().body.clone(); assert!(control.same(&untouched), "a separate body crossing the cutter end plane remains the exact same solid"); - assert_eq!(solvecraft_kernel::step_export(&[&control], "control snapshot").unwrap(), before_control); + assert_eq!(solidcraft_kernel::step_export(&[&control], "control snapshot").unwrap(), before_control); let state = s.model.state(); let body = state.body("Stock").unwrap(); let display = body.body.display_mesh(0.01).unwrap(); @@ -556,7 +556,7 @@ fn offset_rectangle_pocket_has_one_logical_outer_wall_and_closed_face_projection assert_eq!(display.surface_patch(face.index).len(), 1); let attached = run(&mut s, "sketch.create", json!({"plane":{"body":"Stock","index":face.index,"face":[30,0,0.5]}}))["sketch"].as_u64().unwrap(); run(&mut s, "sketch.finish", json!({})); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); for session in [&s, &reopened] { let result = session.model.result(attached).unwrap(); assert!(result.error.is_none(), "{:?}", result.error); diff --git a/crates/engine/src/cmd/sketch_freeform.rs b/crates/engine/src/cmd/sketch_freeform.rs index 699d672e..cd47682c 100644 --- a/crates/engine/src/cmd/sketch_freeform.rs +++ b/crates/engine/src/cmd/sketch_freeform.rs @@ -3,8 +3,8 @@ //! stays exact; the pieces replace the original curve. use serde_json::{Value, json}; -use solvecraft_geom::{Seg2, Vec2}; -use solvecraft_sketch::{ConstraintKind, CurveKind, Sketch}; +use solidcraft_geom::{Seg2, Vec2}; +use solidcraft_sketch::{ConstraintKind, CurveKind, Sketch}; use super::sketch::add_c; use crate::Result; @@ -163,7 +163,7 @@ fn add_pieces(sk: &mut Sketch, segs: &[Seg2], start: Option, end: Option< CurveKind::Conic { a: prev, b: to, apex: x, rho: w / (1.0 + w) } } Seg2::Bezier { n, p } => { - let k = (n as usize).clamp(1, solvecraft_geom::MAX_BEZIER_DEGREE); + let k = (n as usize).clamp(1, solidcraft_geom::MAX_BEZIER_DEGREE); let mut pts = vec![prev]; for q in p.iter().take(k).skip(1) { pts.push(sk.add_point(*q, None)?); diff --git a/crates/engine/src/cmd/sketch_fuzz_tests.rs b/crates/engine/src/cmd/sketch_fuzz_tests.rs index 4d76cc81..8e42b98b 100644 --- a/crates/engine/src/cmd/sketch_fuzz_tests.rs +++ b/crates/engine/src/cmd/sketch_fuzz_tests.rs @@ -94,7 +94,7 @@ fn random_sketch_editing_never_breaks() { } } // The design round-trips. - let back = solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); + let back = solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap(); assert_eq!(back, *s.doc, "seed {seed}"); } } diff --git a/crates/engine/src/cmd/sketch_ids_tests.rs b/crates/engine/src/cmd/sketch_ids_tests.rs index 65063ba0..76605670 100644 --- a/crates/engine/src/cmd/sketch_ids_tests.rs +++ b/crates/engine/src/cmd/sketch_ids_tests.rs @@ -3,7 +3,7 @@ //! new geometry right away; ids are never reused after a delete. use serde_json::{Value, json}; -use solvecraft_sketch::{CurveKind, Sketch}; +use solidcraft_sketch::{CurveKind, Sketch}; use crate::Session; diff --git a/crates/engine/src/cmd/sketch_import.rs b/crates/engine/src/cmd/sketch_import.rs index d44d0712..69fb8fa4 100644 --- a/crates/engine/src/cmd/sketch_import.rs +++ b/crates/engine/src/cmd/sketch_import.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use serde_json::{Value, json}; -use solvecraft_geom::Vec2; -use solvecraft_io::{Geom2, MAX_DRAWING_BYTES}; -use solvecraft_sketch::{CurveKind, LinkKind, LinkSource, Sketch}; +use solidcraft_geom::Vec2; +use solidcraft_io::{Geom2, MAX_DRAWING_BYTES}; +use solidcraft_sketch::{CurveKind, LinkKind, LinkSource, Sketch}; use super::CommandSpec; use super::sketch::{edit, ids_of}; @@ -34,7 +34,7 @@ fn source_text(p: &Value, cmd: &str) -> Result { return Ok(t.to_string()); } let path = str_(p, "path").filter(|x| !x.trim().is_empty() && x.len() < 4096).ok_or_else(|| bad(cmd, "give `path` or `text`"))?; - let bytes = solvecraft_io::vfs::read(path, MAX_DRAWING_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + let bytes = solidcraft_io::vfs::read(path, MAX_DRAWING_BYTES).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; Ok(String::from_utf8_lossy(&bytes).to_string()) } @@ -79,7 +79,7 @@ fn add_geometry(sk: &mut Sketch, geom: &[Geom2], f: &dyn Fn(Vec2) -> Vec2, k: f6 Geom2::Ellipse { c, major, minor } => Some(CurveKind::Ellipse { c: pt(sk, *c)?, m: pt(sk, *major)?, r: minor * k }), Geom2::Spline { pts, control, degree } => { let mut idx = Vec::new(); - for p in pts.iter().take(solvecraft_sketch::MAX_SPLINE_POINTS) { + for p in pts.iter().take(solidcraft_sketch::MAX_SPLINE_POINTS) { let i = pt(sk, *p)?; if idx.last() != Some(&i) { idx.push(i); @@ -107,7 +107,7 @@ fn add_geometry(sk: &mut Sketch, geom: &[Geom2], f: &dyn Fn(Vec2) -> Vec2, k: f6 fn insert(s: &mut Session, p: &Value, cmd: &str, svg: bool) -> Result { let text = source_text(p, cmd)?; - let geom = if svg { solvecraft_io::read_svg(&text) } else { solvecraft_io::read_dxf(&text) }.map_err(|e| bad(cmd, e.to_string()))?; + let geom = if svg { solidcraft_io::read_svg(&text) } else { solidcraft_io::read_dxf(&text) }.map_err(|e| bad(cmd, e.to_string()))?; if geom.is_empty() { return Err(bad(cmd, "the drawing has no geometry SolidCraft reads")); } @@ -180,9 +180,9 @@ fn export_dxf(s: &mut Session, p: &Value) -> Result { } } } - let text = solvecraft_io::write_dxf(&geom); + let text = solidcraft_io::write_dxf(&geom); if let Some(path) = str_(p, "path").filter(|x| !x.trim().is_empty()) { - solvecraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; + solidcraft_io::vfs::write(path, text.as_bytes()).map_err(|e| EngineError::Other(format!("{path}: {e}")))?; return Ok(json!({"path": path, "entities": geom.len()})); } Ok(json!({"dxf": text, "entities": geom.len()})) diff --git a/crates/engine/src/cmd/sketch_inspect.rs b/crates/engine/src/cmd/sketch_inspect.rs index 7a112ff8..b387218b 100644 --- a/crates/engine/src/cmd/sketch_inspect.rs +++ b/crates/engine/src/cmd/sketch_inspect.rs @@ -2,7 +2,7 @@ //! centre of mass of bodies. use serde_json::{Value, json}; -use solvecraft_geom::{Seg2, Vec2, Vec3}; +use solidcraft_geom::{Seg2, Vec2, Vec3}; use super::CommandSpec; use crate::params::{bad, num, string_list, vec3}; @@ -147,7 +147,7 @@ fn min_of(teeth: &[(Vec3, f64, Vec3)]) -> Option<(f64, Vec3)> { } /// The face of a body at `at` (nearest triangle): (body index, face index). -fn face_at(st: &solvecraft_doc::ModelState, at: Vec3) -> Option<(usize, u32)> { +fn face_at(st: &solidcraft_doc::ModelState, at: Vec3) -> Option<(usize, u32)> { let mut best: Option<(f64, usize, u32)> = None; for (bi, b) in st.bodies.iter().enumerate() { let m = b.mesh(); @@ -184,7 +184,7 @@ fn closest_on_triangle(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 { /// Smallest radius of curvature over a face of a mesh, from the turn of the vertex normals /// along each triangle side: (radius, where). -fn face_min_radius(m: &solvecraft_geom::Mesh, face: u32) -> Option<(f64, Vec3)> { +fn face_min_radius(m: &solidcraft_geom::Mesh, face: u32) -> Option<(f64, Vec3)> { let mut best: Option<(f64, Vec3)> = None; for (ti, t) in m.triangles.iter().enumerate() { if m.tri_face.get(ti) != Some(&face) { @@ -294,7 +294,7 @@ fn isocurves(s: &mut Session, p: &Value) -> Result { let at = vec3(v).ok_or_else(|| bad(cmd, "`faces` must list points [x,y,z]"))?; let (bi, f) = face_at(&st, at).ok_or_else(|| bad(cmd, "no face there"))?; let m = st.bodies.get(bi).map(|b| b.mesh()).ok_or_else(|| bad(cmd, "body"))?; - for w in solvecraft_doc::iso_grid(&m, f, at, count) { + for w in solidcraft_doc::iso_grid(&m, f, at, count) { out.push(w.iter().map(|q| [q.x, q.y, q.z]).collect::>()); } } diff --git a/crates/engine/src/cmd/sketch_modify.rs b/crates/engine/src/cmd/sketch_modify.rs index 365f2a0c..a36a85d1 100644 --- a/crates/engine/src/cmd/sketch_modify.rs +++ b/crates/engine/src/cmd/sketch_modify.rs @@ -4,9 +4,9 @@ use std::f64::consts::TAU; use serde_json::{Value, json}; -use solvecraft_doc::ProfileSel; -use solvecraft_geom::Vec2; -use solvecraft_sketch::{ConstraintKind, Curve, CurveKind, Shape, Sketch, intersections}; +use solidcraft_doc::ProfileSel; +use solidcraft_geom::Vec2; +use solidcraft_sketch::{ConstraintKind, Curve, CurveKind, Shape, Sketch, intersections}; use super::sketch::{add_c, edit, ids_of, solve_current}; use super::{CommandSpec, in_sketch}; @@ -451,8 +451,8 @@ fn extend(s: &mut Session, p: &Value) -> Result { } pin(sk, end, oi)?; let before = sk.points.iter().map(|p| p.pos).collect::>(); - let hold = solvecraft_sketch::Hold { points: (0..sk.points.len()).filter(|i| *i != end).collect(), radii: (0..sk.curves.len()).collect() }; - if !solvecraft_sketch::solve_with(sk, &hold, None, solvecraft_sketch::Analysis::Full, None).ok() + let hold = solidcraft_sketch::Hold { points: (0..sk.points.len()).filter(|i| *i != end).collect(), radii: (0..sk.curves.len()).collect() }; + if !solidcraft_sketch::solve_with(sk, &hold, None, solidcraft_sketch::Analysis::Full, None).ok() || sk.point(end).is_none_or(|point| point.dist(q) > TOL) || sk.points.iter().enumerate().any(|(i, point)| i != end && point.pos.dist(before[i]) > TOL) { @@ -602,7 +602,7 @@ fn fillet(s: &mut Session, p: &Value) -> Result { None => vec![p.clone()], }; let (out, info) = edit(s, p, cmd, false, |sk, doc| { - let r = doc.eval(&r_expr, solvecraft_doc::expr::Kind::Length).map_err(|e| bad(cmd, format!("radius: {e}")))?; + let r = doc.eval(&r_expr, solidcraft_doc::expr::Kind::Length).map_err(|e| bad(cmd, format!("radius: {e}")))?; if !(r > 1e-9 && r < 1e8) { return Err(bad(cmd, "radius must be positive")); } @@ -958,7 +958,7 @@ fn offset(s: &mut Session, p: &Value) -> Result { } let ci = sk.add_circle(cc, nr, Some(c), None)?; made.push(ci); - sk.offsets.push(solvecraft_sketch::OffsetChain { + sk.offsets.push(solidcraft_sketch::OffsetChain { sources: vec![(ids[0].clone(), true)], outputs: ids_of(sk, &made), distance: r - nr, @@ -1014,7 +1014,7 @@ fn offset(s: &mut Session, p: &Value) -> Result { .map(|topology| topology.roles.clone()) .ok_or_else(|| bad(cmd, "new offset association is missing"))?; for (source, role) in sources.iter().zip(&roles) { - if role.kind == solvecraft_sketch::OffsetRoleKind::Line { + if role.kind == solidcraft_sketch::OffsetRoleKind::Line { let a = sk.curve_index(&source.0).ok_or_else(|| bad(cmd, "offset source line vanished"))?; let b = sk.curve_index(&role.output).ok_or_else(|| bad(cmd, "offset output line vanished"))?; cons.push(add_c(sk, ConstraintKind::Parallel { a, b })?); @@ -1106,7 +1106,7 @@ fn offset(s: &mut Session, p: &Value) -> Result { let outputs = ids_of(sk, &made); if pieces.iter().all(|p| !matches!(p, Piece::Free(_))) { let sources = order.iter().filter_map(|(i, forward)| sk.curves.get(*i).map(|c| (c.id.clone(), *forward))).collect(); - sk.offsets.push(solvecraft_sketch::OffsetChain { sources, outputs: outputs.clone(), distance: d, closed, topology: None }); + sk.offsets.push(solidcraft_sketch::OffsetChain { sources, outputs: outputs.clone(), distance: d, closed, topology: None }); sk.refresh_offsets()?; } Ok((outputs, cons, None)) @@ -1172,7 +1172,7 @@ fn break_offset(s: &mut Session, p: &Value) -> Result { /// A broken logical boundary becomes ordinary geometry. Keep downstream features /// on that exact current boundary while other associations retain their full roles. -fn freeze_offset_profiles(doc: &mut solvecraft_doc::Document, sketch: u64, boundary: &Sketch, association: &str) -> Result<()> { +fn freeze_offset_profiles(doc: &mut solidcraft_doc::Document, sketch: u64, boundary: &Sketch, association: &str) -> Result<()> { let cmd = "sketch.offset.break"; let mut groups = std::collections::BTreeMap::new(); let mut work = 0usize; diff --git a/crates/engine/src/cmd/sketch_modify_tests.rs b/crates/engine/src/cmd/sketch_modify_tests.rs index a453db76..35f48539 100644 --- a/crates/engine/src/cmd/sketch_modify_tests.rs +++ b/crates/engine/src/cmd/sketch_modify_tests.rs @@ -1,6 +1,6 @@ use serde_json::{Value, json}; -use solvecraft_geom::Vec2; -use solvecraft_sketch::CurveKind; +use solidcraft_geom::Vec2; +use solidcraft_sketch::CurveKind; use crate::Session; @@ -15,7 +15,7 @@ fn ids(v: &Value) -> Vec { v.as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect()).unwrap_or_default() } -fn sketch(s: &Session) -> solvecraft_sketch::Sketch { +fn sketch(s: &Session) -> solidcraft_sketch::Sketch { let id = s.active_sketch.unwrap(); s.model.state().sketch(id).unwrap().sketch.clone() } @@ -63,7 +63,7 @@ fn explicit_freeform_break_preserves_exact_geometry_and_undo() { } } let encoded = serde_json::to_value(&after).unwrap(); - let restored: solvecraft_sketch::Sketch = serde_json::from_value(encoded).unwrap(); + let restored: solidcraft_sketch::Sketch = serde_json::from_value(encoded).unwrap(); assert_eq!(restored.curves.len(), after.curves.len()); run(&mut s, "edit.undo", json!({})); assert!(sketch(&s).curve_index(&id).is_some()); @@ -120,7 +120,7 @@ fn analytic_trim_and_break_use_freeform_cutters() { let sk = sketch(&s); for id in ids(&out["curves"]) { let shape = sk.shape(sk.curve_index(&id).unwrap()).unwrap(); - assert!(matches!(shape, solvecraft_sketch::Shape::Line { a, b } if (a.y - 5.0).abs() < 1e-6 && (b.y - 5.0).abs() < 1e-6)); + assert!(matches!(shape, solidcraft_sketch::Shape::Line { a, b } if (a.y - 5.0).abs() < 1e-6 && (b.y - 5.0).abs() < 1e-6)); } let mut s = new_sketch(); @@ -144,7 +144,7 @@ fn trim_extend_break() { let sk = sketch(&s); let sh = sk.shape(sk.curve_index(&h).unwrap()).unwrap(); assert!( - matches!(sh, solvecraft_sketch::Shape::Line { a, b } if a.dist(Vec2::new(0.0, 0.0)) < 1e-9 && b.dist(Vec2::new(10.0, 0.0)) < 1e-9), + matches!(sh, solidcraft_sketch::Shape::Line { a, b } if a.dist(Vec2::new(0.0, 0.0)) < 1e-9 && b.dist(Vec2::new(10.0, 0.0)) < 1e-9), "{sh:?}" ); // Trim the lower half of the vertical line: the original id survives on the upper piece. @@ -198,7 +198,7 @@ fn exact_extend_ellipse_preserves_support_target_and_history() { assert!(end.dist(Vec2::new(expected_x, y)) < 1e-7, "exact ellipse contact {end:?}"); assert_eq!(sk.exact_segs(0).unwrap(), target); let persisted = s.doc.to_json(); - let reopened = solvecraft_doc::format::read(&persisted).unwrap(); + let reopened = solidcraft_doc::format::read(&persisted).unwrap(); assert_eq!(reopened.to_json(), persisted); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), before); @@ -645,7 +645,7 @@ fn certified_offset_follows_dimension_drag_reopen_break_and_undo() { run(&mut s, "sketch.drag", json!({"entity":ellipse,"from":[0,0],"to":[4,3]})); verify(&s); let mut reopened = Session { - doc: std::sync::Arc::new(solvecraft_doc::format::read(&s.doc.to_json()).unwrap()), + doc: std::sync::Arc::new(solidcraft_doc::format::read(&s.doc.to_json()).unwrap()), active_sketch: s.active_sketch, ..Session::default() }; @@ -677,7 +677,7 @@ fn certified_offset_delete_output_undo_reopen_and_refuse_dependent_source() { assert!(sk.curve_index(&id).is_none()); assert!(sk.curve_index("e1").is_some()); let deleted = s.doc.to_json(); - let reopened = solvecraft_doc::format::read(&deleted).unwrap(); + let reopened = solidcraft_doc::format::read(&deleted).unwrap(); assert_eq!(reopened.to_json(), deleted); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), attached); @@ -706,7 +706,7 @@ fn periodic_closed_offset_chain_break_drag_reopen_and_material_volume() { let check = |session: &Session| { let sk = sketch(session); assert_eq!(sk.freeform_offsets.iter().map(|r| r.certificate).collect::>(), [2, 2]); - assert_eq!(solvecraft_sketch::find_profiles(&sk).len(), 3); + assert_eq!(solidcraft_sketch::find_profiles(&sk).len(), 3); for id in [&a, &b] { assert!(matches!(sk.curves[sk.curve_index(id).unwrap()].kind, CurveKind::Spline { periodic: true, .. })); } @@ -719,7 +719,7 @@ fn periodic_closed_offset_chain_break_drag_reopen_and_material_volume() { run(&mut s, "parameters.change", json!({"name":"d1","expression":12})); check(&s); let mut reopened = - Session { doc: solvecraft_doc::format::read(&s.doc.to_json()).unwrap().into(), active_sketch: s.active_sketch, ..Default::default() }; + Session { doc: solidcraft_doc::format::read(&s.doc.to_json()).unwrap().into(), active_sketch: s.active_sketch, ..Default::default() }; reopened.refresh(); check(&reopened); let attached = reopened.doc.to_json(); @@ -736,12 +736,12 @@ fn periodic_closed_offset_chain_break_drag_reopen_and_material_volume() { let indices = moved.curves[moved.curve_index(&b).unwrap()].kind.point_ids(); assert_eq!(moved.point(indices[0]), moved.point(*indices.last().unwrap())); assert!(moved.point(indices[0]).unwrap().dist(from) > 0.1); - assert_eq!(solvecraft_doc::format::read(&reopened.doc.to_json()).unwrap().to_json(), reopened.doc.to_json()); + assert_eq!(solidcraft_doc::format::read(&reopened.doc.to_json()).unwrap().to_json(), reopened.doc.to_json()); run(&mut reopened, "edit.undo", json!({})); run(&mut reopened, "edit.undo", json!({})); assert_eq!(reopened.doc.to_json(), attached); // An independent ring area multiplied by height probes actual profile material. - let regions = solvecraft_sketch::find_profiles(&sketch(&s)); + let regions = solidcraft_sketch::find_profiles(&sketch(&s)); let selected = regions.iter().enumerate().min_by(|(_, x), (_, y)| x.area.total_cmp(&y.area)).unwrap(); let n = 4096; let h = std::f64::consts::TAU / n as f64; @@ -767,7 +767,7 @@ fn periodic_closed_offset_chain_break_drag_reopen_and_material_volume() { run(&mut s, "sketch.finish", json!({})); run(&mut s, "solid.extrude", json!({"sketch":sketch_id,"profiles":[selected.0],"distance":3,"operation":"new"})); let state = s.world_state(); - let volume = solvecraft_kernel::measure(&state.bodies.last().unwrap().body).unwrap().volume; + let volume = solidcraft_kernel::measure(&state.bodies.last().unwrap().body).unwrap().volume; assert!((volume - expected).abs() < 0.05, "{volume} vs {expected}"); } @@ -780,7 +780,7 @@ fn periodic_fit_creator_keeps_legacy_and_refuses_hostile_intent_atomically() { let i = sk.curve_index(&id).unwrap(); assert!(matches!(sk.curves[i].kind, CurveKind::Spline { periodic: true, .. })); sk.validate_periodic_splines().unwrap(); - assert_eq!(solvecraft_sketch::find_profiles(&sk).len(), 1); + assert_eq!(solidcraft_sketch::find_profiles(&sk).len(), 1); let before = s.doc.to_json(); let history = s.undo.len(); for (cmd, p) in [ @@ -822,7 +822,7 @@ fn analytic_offset_partial_delete_releases_survivors_preserves_source_and_undo() } assert!(sk.curve_index("l1").is_some()); let saved = s.doc.to_json(); - assert_eq!(solvecraft_doc::format::read(&saved).unwrap().to_json(), saved); + assert_eq!(solidcraft_doc::format::read(&saved).unwrap().to_json(), saved); run(&mut s, "edit.undo", json!({})); assert_eq!(s.doc.to_json(), before); } @@ -835,7 +835,7 @@ fn periodic_offset_extrusion_survives_post_extrude_span_changes_and_cold_reopen( let a = run(&mut s, "sketch.offset", json!({"curves":["e1"],"distance":-1,"chain":false}))["curves"][0].as_str().unwrap().to_string(); run(&mut s, "sketch.offset", json!({"curves":[a],"distance":-0.5,"chain":false})); let sk_id = s.active_sketch.unwrap(); - let initial = solvecraft_sketch::find_profiles(&sketch(&s)); + let initial = solidcraft_sketch::find_profiles(&sketch(&s)); let (index, ring) = initial.iter().enumerate().min_by(|(_, a), (_, b)| a.area.total_cmp(&b.area)).unwrap(); let initial_count = ring.outer_curves.len(); let boundary = ring.whole_curve_boundaries().unwrap(); @@ -887,21 +887,21 @@ fn periodic_offset_extrusion_survives_post_extrude_span_changes_and_cold_reopen( let body = &state.bodies[0].body; for (distance, material) in [(1.25, true), (0.5, false), (1.75, false)] { let point = ellipse + normal * distance; - assert_eq!(body.contains(solvecraft_geom::Vec3::new(point.x, point.y, 1.5)), material); + assert_eq!(body.contains(solidcraft_geom::Vec3::new(point.x, point.y, 1.5)), material); } // Repeat at the exact major-axis seam as a physical material control. for (distance, material) in [(1.25, true), (0.5, false), (1.75, false)] { let point = centre + u * (major + distance); assert_eq!(ring.region.contains(point), material); - assert_eq!(body.contains(solvecraft_geom::Vec3::new(point.x, point.y, 1.5)), material); + assert_eq!(body.contains(solidcraft_geom::Vec3::new(point.x, point.y, 1.5)), material); } - let volume = solvecraft_kernel::measure(body).unwrap().volume; + let volume = solidcraft_kernel::measure(body).unwrap().volume; assert!((volume - expected).abs() < 0.05, "{major}: {volume} vs independent {expected}"); - let mut cold = Session { doc: solvecraft_doc::format::read(&s.doc.to_json()).unwrap().into(), ..Default::default() }; + let mut cold = Session { doc: solidcraft_doc::format::read(&s.doc.to_json()).unwrap().into(), ..Default::default() }; cold.refresh(); let cold_state = cold.world_state(); assert_eq!(cold_state.bodies.len(), 1); - let cold_volume = solvecraft_kernel::measure(&cold_state.bodies[0].body).unwrap().volume; + let cold_volume = solidcraft_kernel::measure(&cold_state.bodies[0].body).unwrap().volume; assert!((cold_volume - volume).abs() < 1e-8); let edited = s.doc.to_json(); run(&mut s, "edit.undo", json!({})); diff --git a/crates/engine/src/cmd/sketch_perf_tests.rs b/crates/engine/src/cmd/sketch_perf_tests.rs index a3c172c8..0f97b4ac 100644 --- a/crates/engine/src/cmd/sketch_perf_tests.rs +++ b/crates/engine/src/cmd/sketch_perf_tests.rs @@ -1,4 +1,4 @@ -//! Timings for large sketches (run with `cargo test -p solvecraft-engine --release sketch_perf -- --ignored --nocapture`; +//! Timings for large sketches (run with `cargo test -p solidcraft-engine --release sketch_perf -- --ignored --nocapture`; //! numbers are recorded in docs/perf.md). use std::time::Instant; @@ -19,8 +19,8 @@ fn big(n: usize) -> Session { let id = s.active_sketch.unwrap(); let mut doc = (*s.doc).clone(); let sk = doc.sketch_mut(id).unwrap(); - use solvecraft_geom::Vec2; - use solvecraft_sketch::ConstraintKind::*; + use solidcraft_geom::Vec2; + use solidcraft_sketch::ConstraintKind::*; let side = (n as f64).sqrt().ceil() as usize; for k in 0..n { let (x, y) = ((k % side) as f64 * 30.0, (k / side) as f64 * 30.0); @@ -54,10 +54,10 @@ fn sketch_perf() { let entities = sk.curves.len() + sk.points.len(); let t = Instant::now(); let mut k = sk.clone(); - let rep = solvecraft_sketch::solve(&mut k); + let rep = solidcraft_sketch::solve(&mut k); let solve = ms(t); let t = Instant::now(); - let prof = solvecraft_sketch::find_profiles(&k); + let prof = solidcraft_sketch::find_profiles(&k); let profiles = ms(t); let ss = s.model.state().sketch(id).unwrap().clone(); let t = Instant::now(); diff --git a/crates/engine/src/cmd/sketch_project.rs b/crates/engine/src/cmd/sketch_project.rs index 4605b24e..49f8baf1 100644 --- a/crates/engine/src/cmd/sketch_project.rs +++ b/crates/engine/src/cmd/sketch_project.rs @@ -4,10 +4,10 @@ //! normal geometry. use serde_json::{Value, json}; -use solvecraft_doc::project::resolve; -use solvecraft_doc::{Document, FeatureKind}; -use solvecraft_geom::{Plane, Vec3}; -use solvecraft_sketch::{ConstraintKind, LinkKind, LinkSource, Sketch}; +use solidcraft_doc::project::resolve; +use solidcraft_doc::{Document, FeatureKind}; +use solidcraft_geom::{Plane, Vec3}; +use solidcraft_sketch::{ConstraintKind, LinkKind, LinkSource, Sketch}; use super::{CommandSpec, in_sketch}; use crate::params::{bad, bool_, str_, vec3}; @@ -141,7 +141,7 @@ fn placed_candidate<'a>(s: &Session, doc: &'a Document) -> std::borrow::Cow<'a, std::borrow::Cow::Owned(placed) } -fn owner_frame(s: &Session, doc: &Document, comp: u64) -> solvecraft_doc::Mat { +fn owner_frame(s: &Session, doc: &Document, comp: u64) -> solidcraft_doc::Mat { if comp == s.active_component { s.active_component_frame() } else { doc.component_transform(comp) } } @@ -157,19 +157,19 @@ pub(super) fn refresh(s: &Session, doc: &Document, id: u64, sk: &mut Sketch) -> let st = s.model.state_before(id); let comp = doc.feature(id).map(|f| f.component).unwrap_or(0); let attachment = doc.feature(id).and_then(|feature| match &feature.kind { - solvecraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane), + solidcraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane), _ => None, }); let st = match attachment { - Some(anchor) => solvecraft_doc::state_in_face_anchor_frame(&doc, &st, anchor, comp), - None => Ok(solvecraft_doc::state_in_frame_at(&doc, &st, comp, owner_frame(s, &doc, comp))), + Some(anchor) => solidcraft_doc::state_in_face_anchor_frame(&doc, &st, anchor, comp), + None => Ok(solidcraft_doc::state_in_frame_at(&doc, &st, comp, owner_frame(s, &doc, comp))), }; let Ok(st) = st else { return vec!["lost face attachment; reattach explicitly".into()]; }; match attachment { - Some(anchor) => solvecraft_doc::project::refresh_face_anchor_links(&doc, &vals, &st, &plane, sk, anchor), - None => solvecraft_doc::project::refresh_links(&doc, &vals, &st, &plane, sk), + Some(anchor) => solidcraft_doc::project::refresh_face_anchor_links(&doc, &vals, &st, &plane, sk, anchor), + None => solidcraft_doc::project::refresh_links(&doc, &vals, &st, &plane, sk), } } @@ -182,21 +182,21 @@ pub(super) fn add_link(s: &Session, doc: &Document, id: u64, sk: &mut Sketch, ki // The model as the sketch's component sees it. let comp = doc.feature(id).map(|f| f.component).unwrap_or(0); let attachment = doc.feature(id).and_then(|feature| match &feature.kind { - solvecraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane), + solidcraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane), _ => None, }); let st = match attachment { - Some(anchor) => solvecraft_doc::state_in_face_anchor_frame(&doc, &st, anchor, comp), - None => Ok(solvecraft_doc::state_in_frame_at(&doc, &st, comp, owner_frame(s, &doc, comp))), + Some(anchor) => solidcraft_doc::state_in_face_anchor_frame(&doc, &st, anchor, comp), + None => Ok(solidcraft_doc::state_in_frame_at(&doc, &st, comp, owner_frame(s, &doc, comp))), }; let st = st?; let r = resolve(&doc, &vals, &st, &plane, kind, &src)?; let name = if let LinkSource::Face { body, at } = &r.source { st.body(body).and_then(|body| { if let Some(index) = r.face_index { - solvecraft_doc::naming::face_names(body).get(index).filter(|name| !name.is_empty()).cloned() + solidcraft_doc::naming::face_names(body).get(index).filter(|name| !name.is_empty()).cloned() } else { - solvecraft_doc::naming::face_name_at_body(body, *at) + solidcraft_doc::naming::face_name_at_body(body, *at) } }) } else { @@ -312,7 +312,7 @@ fn spun_profile(s: &mut Session, p: &Value) -> Result { let st = s.model.state(); let ss = st.sketch(id).ok_or_else(|| EngineError::Other("the sketch is not evaluated".into()))?; let ci = ss.sketch.curve_index(axis).ok_or_else(|| bad(cmd, format!("unknown curve `{axis}`")))?; - let Some(solvecraft_sketch::Shape::Line { a, b }) = ss.sketch.shape(ci) else { return Err(bad(cmd, "the axis must be a line")) }; + let Some(solidcraft_sketch::Shape::Line { a, b }) = ss.sketch.shape(ci) else { return Err(bad(cmd, "the axis must be a line")) }; let (wa, wb) = (ss.plane.to_world(a), ss.plane.to_world(b)); (wa, wb - wa) } @@ -417,7 +417,7 @@ pub(super) fn snap_points(s: &mut Session, p: &Value, cmd: &str) -> Result = xy.split(',').filter_map(|x| x.trim().parse().ok()).collect(); match n[..] { - [x, y] if x.is_finite() && y.is_finite() => Some((c.to_string(), solvecraft_geom::Vec2::new(x, y))), + [x, y] if x.is_finite() && y.is_finite() => Some((c.to_string(), solidcraft_geom::Vec2::new(x, y))), _ => None, } }); @@ -444,7 +444,7 @@ pub(super) fn snap_points(s: &mut Session, p: &Value, cmd: &str) -> Result": a new point at the line's midpoint, kept there. let l = sk.curve_index(&line).ok_or_else(|| bad(cmd, format!("unknown curve `{line}`")))?; - let Some(solvecraft_sketch::Shape::Line { a, b }) = sk.shape(l) else { return Err(bad(cmd, format!("`{line}` is not a line"))) }; + let Some(solidcraft_sketch::Shape::Line { a, b }) = sk.shape(l) else { return Err(bad(cmd, format!("`{line}` is not a line"))) }; let pi = sk.add_point((a + b) * 0.5, None)?; sk.add_constraint(ConstraintKind::Midpoint { p: pi, l }, None)?; sk.points.get(pi).map(|q| q.id.clone()).unwrap_or_default() @@ -493,9 +493,9 @@ pub(super) fn project_internal_face(s: &mut Session, id: u64, body: &str, index: let plane = sketch_plane(&doc, id)?; let raw = s.model.state_before(id); let component = doc.feature(id).map(|feature| feature.component).unwrap_or(0); - let state = solvecraft_doc::state_in_frame_at(&doc, &raw, component, owner_frame(s, &doc, component)); - let resolved = solvecraft_doc::project::resolve_face_indices(&state, &plane, LinkKind::Project, body, &[index])?; - let face_name = state.body(body).and_then(|body| solvecraft_doc::naming::face_names(body).get(index).cloned()); + let state = solidcraft_doc::state_in_frame_at(&doc, &raw, component, owner_frame(s, &doc, component)); + let resolved = solidcraft_doc::project::resolve_face_indices(&state, &plane, LinkKind::Project, body, &[index])?; + let face_name = state.body(body).and_then(|body| solidcraft_doc::naming::face_names(body).get(index).cloned()); let mut sk = doc.sketch(id)?.clone(); let link = sk.add_link_with_wires(LinkKind::Project, resolved.source, &resolved.geom, &resolved.wires)?; if let Some(entry) = sk.links.iter_mut().find(|entry| entry.id == link) { @@ -512,7 +512,7 @@ pub(super) fn auto_project_face(s: &mut Session, id: u64, _at: Vec3) -> Result super::sketch::face::anchor(plane), + solidcraft_doc::FeatureKind::Sketch { plane, .. } => super::sketch::face::anchor(plane), _ => None, }) .cloned() @@ -521,9 +521,9 @@ pub(super) fn auto_project_face(s: &mut Session, id: u64, _at: Vec3) -> Result 19.)); assert!(sk.points.iter().any(|point| point.pos.y > 19.)); assert!((session.world_state().body("Control").unwrap().mesh().bounds().max.z - 10.).abs() < 1e-6); }; verify(&s); - verify(&Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap())); + verify(&Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap())); } #[test] @@ -48,19 +48,19 @@ fn initial_projection_retains_selected_face_when_closest_point_lands_on_shared_e run(&mut s, "solid.box", json!({"length":20,"width":20,"height":10,"body_name":"Target"})); let state = s.model.state(); let body = state.body("Target").unwrap(); - let source = solvecraft_sketch::LinkSource::Face { body: "Target".into(), at: solvecraft_geom::Vec3::new(-0.1, 5., 10.5) }; - let resolved = solvecraft_doc::project::resolve( + let source = solidcraft_sketch::LinkSource::Face { body: "Target".into(), at: solidcraft_geom::Vec3::new(-0.1, 5., 10.5) }; + let resolved = solidcraft_doc::project::resolve( &s.doc, &std::collections::BTreeMap::new(), &state, - &solvecraft_geom::Plane::XY, - solvecraft_sketch::LinkKind::Project, + &solidcraft_geom::Plane::XY, + solidcraft_sketch::LinkKind::Project, &source, ) .unwrap(); let index = resolved.face_index.unwrap(); - let expected = solvecraft_doc::naming::face_names(body)[index].clone(); - let solvecraft_sketch::LinkSource::Face { at, .. } = resolved.source else { panic!("not face") }; + let expected = solidcraft_doc::naming::face_names(body)[index].clone(); + let solidcraft_sketch::LinkSource::Face { at, .. } = resolved.source else { panic!("not face") }; assert!(at.x.abs() < 1e-6 && (at.z - 10.).abs() < 1e-6, "closestpoint must be on shared top/wall edge: {at:?}"); let plane = body.body.face_plane(index).unwrap(); let id = run(&mut s, "sketch.create", json!({"plane":{"origin":plane.origin,"x_dir":plane.x,"y_dir":plane.y}}))["sketch"].as_u64().unwrap(); @@ -71,7 +71,7 @@ fn initial_projection_retains_selected_face_when_closest_point_lands_on_shared_e assert_eq!(sk.links[0].face_name, Some(expected)); assert!(!sk.links[0].lost); assert_eq!(sk.curves.len(), 4); - let reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); assert_eq!(reopened.model.state().sketch(id).unwrap().sketch.curves, sk.curves); } @@ -83,7 +83,7 @@ fn box_part(s: &mut Session) { let l = ids(&r["curves"]); let sk = s.doc.sketch(s.active_sketch.unwrap()).unwrap(); let start = match sk.curves[0].kind { - solvecraft_sketch::CurveKind::Line { a, .. } => sk.points[a].id.clone(), + solidcraft_sketch::CurveKind::Line { a, .. } => sk.points[a].id.clone(), _ => panic!(), }; run(s, "sketch.constraint.coincident", json!({"a": start, "b": "origin"})); @@ -446,7 +446,7 @@ fn sweeps_and_pipes_follow_3d_sketch_curves() { // A sweep of a square profile drawn square to the curve at its start. let (a, b) = (pts[0], pts[1]); let t = (b - a).normalized().unwrap(); - let x = t.cross(solvecraft_geom::Vec3::Z).normalized().unwrap(); + let x = t.cross(solidcraft_geom::Vec3::Z).normalized().unwrap(); let y = t.cross(x); run(&mut s, "sketch.create", json!({"plane": {"origin": [a.x, a.y, a.z], "x_dir": [x.x, x.y, x.z], "y_dir": [y.x, y.y, y.z]}})); run(&mut s, "sketch.rectangle.two_point", json!({"p0": [-1, -1], "p1": [1, 1]})); @@ -488,11 +488,11 @@ fn project_and_include_from_other_bodies() { run(&mut s, "sketch.finish", json!({})); // Widen the second box: the projection follows (its far edge is at 70). run(&mut s, "parameters.change", json!({"name": "w", "expression": "20 mm"})); - let id = s.doc.features.iter().find(|f| matches!(f.kind, solvecraft_doc::FeatureKind::Sketch { .. })).unwrap().id; + let id = s.doc.features.iter().find(|f| matches!(f.kind, solidcraft_doc::FeatureKind::Sketch { .. })).unwrap().id; let si = run(&mut s, "sketch.inspect", json!({"sketch": id})); assert!(xs(&si).iter().any(|x| (x - 70.0).abs() < 1e-6), "{:?}", xs(&si)); // Remove the cylinder: its silhouette and edge are lost, with a warning and no error. - let cyl = s.doc.features.iter().find(|f| matches!(f.kind, solvecraft_doc::FeatureKind::Cylinder { .. })).unwrap().id; + let cyl = s.doc.features.iter().find(|f| matches!(f.kind, solidcraft_doc::FeatureKind::Cylinder { .. })).unwrap().id; run(&mut s, "timeline.delete", json!({"feature": cyl})); let t = run(&mut s, "document.inspect", json!({})); let row = t["timeline"].as_array().unwrap().iter().find(|r| r["id"] == json!(id)).cloned().unwrap(); diff --git a/crates/engine/src/cmd/sketch_support.rs b/crates/engine/src/cmd/sketch_support.rs index 28498d61..1a3208ef 100644 --- a/crates/engine/src/cmd/sketch_support.rs +++ b/crates/engine/src/cmd/sketch_support.rs @@ -1,7 +1,7 @@ //! Bounded roots on exact curve pieces against an infinite line or complete circle. //! Rational pieces are intersected in homogeneous coordinates, without sampled chords. -use solvecraft_geom::{Seg2, Vec2}; -use solvecraft_sketch::{Shape, intersections}; +use solidcraft_geom::{Seg2, Vec2}; +use solidcraft_sketch::{Shape, intersections}; use crate::{Result, params::bad}; diff --git a/crates/engine/src/cmd/sketch_ux_tests.rs b/crates/engine/src/cmd/sketch_ux_tests.rs index 2e0f43c1..89f35ef8 100644 --- a/crates/engine/src/cmd/sketch_ux_tests.rs +++ b/crates/engine/src/cmd/sketch_ux_tests.rs @@ -2,7 +2,7 @@ //! which entity moves when a two-entity constraint is applied. use serde_json::{Value, json}; -use solvecraft_sketch::{DimFrame, Sketch, dim_frame, dim_layout}; +use solidcraft_sketch::{DimFrame, Sketch, dim_frame, dim_layout}; use crate::Session; @@ -53,7 +53,7 @@ fn every_dimension_kind_has_a_drawable_frame() { let mut kinds = Vec::new(); for c in dims { let f = dim_frame(&sk, &c.kind).unwrap_or_else(|| panic!("no frame for {c:?}")); - let l = dim_layout(&f, None, 0.1, solvecraft_geom::Vec2::new(1.0, 0.5)); + let l = dim_layout(&f, None, 0.1, solidcraft_geom::Vec2::new(1.0, 0.5)); assert!(!l.lines.is_empty() && !l.arrows.is_empty(), "{c:?}"); assert!(l.text.is_finite()); kinds.push(std::mem::discriminant(&f)); @@ -80,10 +80,10 @@ fn dimension_text_is_stored_relative_to_the_dimension_and_follows_it() { let f = dim_frame(&sk, &c.kind).expect("frame"); dim_layout(&f, c.text, 0.1, Default::default()).text }; - assert!(text(&s).dist(solvecraft_geom::Vec2::new(20.0, -8.0)) < 1e-9); + assert!(text(&s).dist(solidcraft_geom::Vec2::new(20.0, -8.0)) < 1e-9); // Moved by the parameter name; then the line moves and the text goes with it. run(&mut s, "sketch.dimension_text", json!({"dimension": param, "at": [30, 12]})); - assert!(text(&s).dist(solvecraft_geom::Vec2::new(30.0, 12.0)) < 1e-9); + assert!(text(&s).dist(solidcraft_geom::Vec2::new(30.0, 12.0)) < 1e-9); run(&mut s, "sketch.move_point", json!({"point": "l1.start", "to": [0, 10]})); run(&mut s, "sketch.move_point", json!({"point": "l1.end", "to": [40, 10]})); // Still 10 along and 12 across from the middle of the (moved) line. @@ -114,7 +114,7 @@ fn deleting_a_dimension_frees_its_parameter() { // --------------------------------------------------------------------------------------------- // Fix / Unfix -fn pt(s: &Session, id: &str) -> solvecraft_geom::Vec2 { +fn pt(s: &Session, id: &str) -> solidcraft_geom::Vec2 { let sk = sketch(s); sk.resolve_point(id).and_then(|i| sk.point(i)).unwrap_or_else(|| panic!("no point {id}")) } @@ -163,7 +163,7 @@ fn fixing_curves_holds_their_points_and_radius() { assert!(si["curves"].as_array().is_some_and(|c| c.iter().all(|c| c["fixed"] == true)), "{si}"); // A fixed circle keeps centre and radius; a dimension on it conflicts. run(&mut s, "sketch.move_point", json!({"point": "c1.center", "to": [0, 0]})); - assert_eq!(pt(&s, "c1.center"), solvecraft_geom::Vec2::new(10.0, 10.0)); + assert_eq!(pt(&s, "c1.center"), solidcraft_geom::Vec2::new(10.0, 10.0)); assert!(s.execute("sketch.dimension", &json!({"entities": ["c1"], "value": 20})).is_err()); let r0 = sketch(&s).radius(0); assert_eq!(r0, Some(5.0)); @@ -272,7 +272,7 @@ fn projected_geometry_can_become_construction() { // Two-entity constraints: the first pick stays, the second moves /// Positions of an entity's points (and its radius). -fn snap(s: &Session, id: &str) -> (Vec, Option) { +fn snap(s: &Session, id: &str) -> (Vec, Option) { let sk = sketch(s); if let Some(i) = sk.curve_index(id) { let pts = sk.curves[i].kind.point_ids().into_iter().filter_map(|p| sk.point(p)).collect(); @@ -281,7 +281,7 @@ fn snap(s: &Session, id: &str) -> (Vec, Option) { (vec![pt(s, id)], None) } -fn same(a: &(Vec, Option), b: &(Vec, Option)) -> bool { +fn same(a: &(Vec, Option), b: &(Vec, Option)) -> bool { a.0.len() == b.0.len() && a.0.iter().zip(&b.0).all(|(p, q)| p.dist(*q) < 1e-9) && a.1.zip(b.1).is_none_or(|(x, y)| (x - y).abs() < 1e-9) } @@ -413,7 +413,7 @@ fn point_constraints_keep_the_first_pick() { let (a0, l0) = (snap(&s, "p1"), snap(&s, "l1")); run(&mut s, "sketch.constraint.symmetry", json!({"a": "p1", "b": "p2", "line": "l1"})); assert!(same(&snap(&s, "p1"), &a0) && same(&snap(&s, "l1"), &l0)); - assert!(pt(&s, "p2").dist(solvecraft_geom::Vec2::new(3.0, -7.0)) < 1e-6, "{:?}", pt(&s, "p2")); + assert!(pt(&s, "p2").dist(solidcraft_geom::Vec2::new(3.0, -7.0)) < 1e-6, "{:?}", pt(&s, "p2")); } #[test] @@ -468,7 +468,7 @@ fn palette_options_are_kept_with_each_sketch() { let first = s.active_sketch.expect("sketch"); run(&mut s, "sketch.finish", json!({})); run(&mut s, "sketch.create", json!({"plane": "XZ"})); - assert_eq!(sketch(&s).view, solvecraft_sketch::SketchView::default()); + assert_eq!(sketch(&s).view, solidcraft_sketch::SketchView::default()); let o = run(&mut s, "sketch.options", json!({"sketch": first})); assert_eq!(o["options"]["show_dimensions"], false); // Saved with the design. @@ -489,8 +489,8 @@ fn drag(s: &mut Session, e: &str, from: [f64; 2], to: [f64; 2]) { run(s, "sketch.drag", json!({"entity": e, "from": from, "to": to})); } -fn v(x: f64, y: f64) -> solvecraft_geom::Vec2 { - solvecraft_geom::Vec2::new(x, y) +fn v(x: f64, y: f64) -> solidcraft_geom::Vec2 { + solidcraft_geom::Vec2::new(x, y) } #[test] @@ -610,7 +610,7 @@ fn arm_drags(links: Links) -> (usize, usize) { let v3 = v2 + v(b * (th1 + bend).cos(), b * (th1 + bend).sin()); let (tr, tt) = (lo + rnd() * (hi - lo), rnd() * TAU); let to = o + v(tr * tt.cos(), tr * tt.sin()); - let clear = |p: solvecraft_geom::Vec2| (lo..=hi).contains(&p.dist(o)); + let clear = |p: solidcraft_geom::Vec2| (lo..=hi).contains(&p.dist(o)); if !(0..=100).all(|i| clear(v3 + (to - v3) * (i as f64 / 100.0))) { continue; } @@ -697,13 +697,13 @@ fn hanging_triangle_flips(n: usize) -> usize { let v3 = v2 + v(b * (th1 + bend).cos(), b * (th1 + bend).sin()); let (tr, tt) = (lo + rnd() * (hi - lo), rnd() * TAU); let to = o + v(tr * tt.cos(), tr * tt.sin()); - let clear = |p: solvecraft_geom::Vec2| (lo..=hi).contains(&p.dist(o)); + let clear = |p: solidcraft_geom::Vec2| (lo..=hi).contains(&p.dist(o)); if !(0..=100).all(|i| clear(v3 + (to - v3) * (i as f64 / 100.0))) { continue; } kept += 1; let turn = rnd() * TAU; - let u = solvecraft_geom::Vec2::from_angle(turn); + let u = solidcraft_geom::Vec2::from_angle(turn); let normal = u.perp() * if rnd() < 0.5 { 1.0 } else { -1.0 }; let (tq, tr) = (v3 + u * 3.9, v3 + u * 1.95 + normal * (4.0f64 - 1.95 * 1.95).sqrt()); { @@ -821,7 +821,7 @@ fn the_step_cap_leaves_the_plan_and_is_restored() { /// Stored (counter-clockwise) sweep of an arc, degrees in [0, 360). fn sweep_deg(s: &Session, arc: &str) -> f64 { let sk = sketch(s); - let Some(solvecraft_sketch::CurveKind::Arc { c, a, b }) = sk.curve_index(arc).map(|i| sk.curves[i].kind.clone()) else { panic!("no arc {arc}") }; + let Some(solidcraft_sketch::CurveKind::Arc { c, a, b }) = sk.curve_index(arc).map(|i| sk.curves[i].kind.clone()) else { panic!("no arc {arc}") }; let (c, a, b) = (sk.points[c].pos, sk.points[a].pos, sk.points[b].pos); ((b - c).angle() - (a - c).angle()).rem_euclid(std::f64::consts::TAU).to_degrees() } @@ -893,11 +893,11 @@ fn a_dragged_fillet_stays_in_its_corner() { run(&mut s, "sketch.constraint.fix", json!({"entities": ["l1.start"]})); run(&mut s, "sketch.constraint.horizontal_vertical", json!({"line": "l1", "mode": "horizontal"})); let sk0 = sketch(&s); - let arc = sk0.curves.iter().position(|c| matches!(c.kind, solvecraft_sketch::CurveKind::Arc { .. })).expect("fillet arc"); + let arc = sk0.curves.iter().position(|c| matches!(c.kind, solidcraft_sketch::CurveKind::Arc { .. })).expect("fillet arc"); let arc_id = sk0.curves[arc].id.clone(); let inside = |s: &Session| { let sk = sketch(s); - let side = |p: solvecraft_geom::Vec2, a: solvecraft_geom::Vec2, b: solvecraft_geom::Vec2| (b - a).cross(p - a).signum(); + let side = |p: solidcraft_geom::Vec2, a: solidcraft_geom::Vec2, b: solidcraft_geom::Vec2| (b - a).cross(p - a).signum(); let c = sk.center(arc).expect("centre"); let (p0, k1, k2, p3) = (pt(s, "l1.start"), pt(s, "l1.end"), pt(s, "l2.start"), pt(s, "l2.end")); let r = sk.radius(arc).unwrap_or(0.0); @@ -1029,7 +1029,7 @@ fn the_drag_step_counts_every_held_size_but_the_dragged_length() { run(&mut s, "sketch.line", json!({"points": [[100, 100], [100, 110]], "ids": ["la"]})); run(&mut s, "sketch.line", json!({"points": [[100.05, 120], [110, 120]], "ids": ["lb"], "connect": false})); run(&mut s, "sketch.dimension", json!({"entities": ["la.start", "lb.start"], "type": "horizontal", "value": 0.05})); - assert!(sketch(&s).constraints.iter().any(|c| matches!(c.kind, solvecraft_sketch::ConstraintKind::DistanceX { .. }))); + assert!(sketch(&s).constraints.iter().any(|c| matches!(c.kind, solidcraft_sketch::ConstraintKind::DistanceX { .. }))); let steps = drag_steps(&s, "la.start", [100.0, 100.0], [101.0, 100.0]); drag(&mut s, "la.start", [100.0, 100.0], [101.0, 100.0]); let (a, b) = (pt(&s, "la.start"), pt(&s, "lb.start")); @@ -1047,7 +1047,7 @@ fn a_circle_centred_on_a_dragged_end_does_not_set_the_step() { run(&mut s, "sketch.constraint.fix", json!({"entities": ["la.start"]})); run(&mut s, "sketch.dimension", json!({"entities": ["la"], "value": 20})); run(&mut s, "sketch.circle.center", json!({"center": [120, 100], "radius": 0.01})); - assert!(sketch(&s).constraints.iter().any(|c| matches!(c.kind, solvecraft_sketch::ConstraintKind::Coincident { .. }))); + assert!(sketch(&s).constraints.iter().any(|c| matches!(c.kind, solidcraft_sketch::ConstraintKind::Coincident { .. }))); let steps = drag_steps(&s, "la.end", [120.0, 100.0], [120.0, 101.0]); eprintln!("1 mm drag next to a 0.01 mm circle on the end: {steps} steps"); assert_eq!(steps, 1); @@ -1208,8 +1208,8 @@ fn three_d_curves_need_the_option_and_move_joined() { let sk = sketch(&s); let w = &sk.wires[sk.wire_index(&sid).expect("spline")]; assert!(w.pts.len() > 10); - assert!(w.pts.first().expect("p").dist(solvecraft_geom::Vec3::new(10.0, 0.0, 10.0)) < 1e-12); - assert!(w.pts.last().expect("p").dist(solvecraft_geom::Vec3::new(20.0, 0.0, 20.0)) < 1e-12); + assert!(w.pts.first().expect("p").dist(solidcraft_geom::Vec3::new(10.0, 0.0, 10.0)) < 1e-12); + assert!(w.pts.last().expect("p").dist(solidcraft_geom::Vec3::new(20.0, 0.0, 20.0)) < 1e-12); // The shared corner moves for both lines (and the spline that starts there). let r = run(&mut s, "sketch.move3d", json!({"wire": ids[1], "index": 1, "by": [0, 0, 5]})); assert_eq!(r["moved"], 2); @@ -1253,7 +1253,7 @@ fn pipe_and_sweep_follow_3d_sketch_curves() { /// past that above or below them the horizontal distance, beside them the vertical one (#257). #[test] fn auto_dimension_orientation_follows_where_its_text_is_placed() { - use solvecraft_sketch::ConstraintKind; + use solidcraft_sketch::ConstraintKind; // The ends of a 30 × 40 line: 50 long, and the slope keeps "across" and "past" distinct. let dimension = |end: [f64; 2], entities: Value, extra: Value| { let mut s = new_sketch(); @@ -1331,7 +1331,7 @@ fn hung_elbow_side(s: &Session) -> f64 { } /// The hand of `hung_arm` swung by `angle` about the shoulder. -fn hung_hand_target(s: &Session, angle: f64, scale: f64) -> solvecraft_geom::Vec2 { +fn hung_hand_target(s: &Session, angle: f64, scale: f64) -> solidcraft_geom::Vec2 { let (b, h) = (pt(s, "ab.end"), pt(s, "eh.end")); let r = h - b; let (c, si) = (angle.cos(), angle.sin()); diff --git a/crates/engine/src/cmd/surface.rs b/crates/engine/src/cmd/surface.rs index 23ff4ada..0f430c90 100644 --- a/crates/engine/src/cmd/surface.rs +++ b/crates/engine/src/cmd/surface.rs @@ -2,7 +2,7 @@ //! solids). use serde_json::Value; -use solvecraft_doc::{FaceAt, FeatureKind, PlaneRef, ProfileSel, expr::Kind}; +use solidcraft_doc::{FaceAt, FeatureKind, PlaneRef, ProfileSel, expr::Kind}; use super::CommandSpec; use super::features::{add_feature, check_expr, feature_sketch, operation, plane_param, profiles}; @@ -34,7 +34,7 @@ pub static COMMANDS: &[CommandSpec] = &[ fn patch(s: &mut Session, p: &Value) -> Result { let cmd = "surface.patch"; - let edges: Vec = p.get("edges").and_then(Value::as_array).map(|a| a.iter().filter_map(vec3).collect()).unwrap_or_default(); + let edges: Vec = p.get("edges").and_then(Value::as_array).map(|a| a.iter().filter_map(vec3).collect()).unwrap_or_default(); if !edges.is_empty() { if edges.len() > 10_000 { return Err(bad(cmd, "too many edges")); @@ -88,7 +88,7 @@ fn extend(s: &mut Session, p: &Value) -> Result { if s.model.state().body(&body).is_none() { return Err(bad(cmd, format!("no body `{body}`"))); } - let edges: Vec = p.get("edges").and_then(Value::as_array).map(|a| a.iter().filter_map(vec3).collect()).unwrap_or_default(); + let edges: Vec = p.get("edges").and_then(Value::as_array).map(|a| a.iter().filter_map(vec3).collect()).unwrap_or_default(); if edges.is_empty() || edges.len() > 10_000 { return Err(bad(cmd, "`edges` must list points on the edges to extend")); } @@ -126,7 +126,7 @@ mod tests { } fn volume(s: &Session, name: &str) -> f64 { let st = s.world_state(); - solvecraft_kernel::measure(&st.body(name).unwrap().body).unwrap().volume + solidcraft_kernel::measure(&st.body(name).unwrap().body).unwrap().volume } /// A rectangle patched, trimmed by a plane and thickened; a box's faces stitched back. diff --git a/crates/engine/src/cmd/surface_analysis.rs b/crates/engine/src/cmd/surface_analysis.rs index d917534d..f90d4c44 100644 --- a/crates/engine/src/cmd/surface_analysis.rs +++ b/crates/engine/src/cmd/surface_analysis.rs @@ -2,7 +2,7 @@ //! View state only: no geometry changes and no undo step. use serde_json::{Value, json}; -use solvecraft_geom::Vec3; +use solidcraft_geom::Vec3; use super::CommandSpec; use crate::params::{bad, bool_, num, vec3}; @@ -109,7 +109,7 @@ mod tests { s.execute("inspect.environment_map", &json!({})).unwrap(); assert_eq!(s.analysis, Some(SurfaceAnalysis::Environment)); s.execute("inspect.accessibility", &json!({"direction": [0, 0, -3]})).unwrap(); - assert_eq!(s.analysis, Some(SurfaceAnalysis::Access { dir: solvecraft_geom::Vec3::new(0.0, 0.0, -1.0) })); + assert_eq!(s.analysis, Some(SurfaceAnalysis::Access { dir: solidcraft_geom::Vec3::new(0.0, 0.0, -1.0) })); s.execute("inspect.curvature_map", &json!({"clear": true})).unwrap(); assert_eq!(s.analysis, None); assert_eq!(s.undo.len(), undo, "no undo steps"); diff --git a/crates/engine/src/construct_tests.rs b/crates/engine/src/construct_tests.rs index 511ae520..373fe2b8 100644 --- a/crates/engine/src/construct_tests.rs +++ b/crates/engine/src/construct_tests.rs @@ -2,7 +2,7 @@ //! 40 × 30 × 20 box at the origin, a cylinder, a sphere and a torus. use serde_json::{Value, json}; -use solvecraft_geom::{Plane, Vec3}; +use solidcraft_geom::{Plane, Vec3}; use crate::Session; use crate::view::{construction_axes, construction_planes, construction_points}; @@ -236,7 +236,7 @@ fn construction_on_a_3d_wire_ignores_its_sketch_plane_and_follows_edits() { let updated = plane(&s, "BridgePlane"); assert!(near(updated.origin, Vec3::new(30.0, 27.5, 25.0)), "{updated:?}"); assert!(parallel(updated.normal(), Vec3::new(40.0, 15.0, 30.0))); - let restored = solvecraft_doc::format::read(&s.doc.to_json()).unwrap(); + let restored = solidcraft_doc::format::read(&s.doc.to_json()).unwrap(); let mut cold = Session { doc: restored.into(), ..Default::default() }; cold.refresh(); assert!(near(plane(&cold, "BridgePlane").origin, updated.origin)); @@ -349,7 +349,7 @@ fn ambiguous_plane_aliases_refuse_without_document_or_history_changes() { let cut = run(&mut s, "inspect.section", json!({"plane":id})); assert_eq!(cut["section"]["origin"], json!([0.0, 0.0, 19.05])); run(&mut s, "parameters.change", json!({"name":"height","expression":"25.4 mm"})); - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&s.doc.to_json()).unwrap()); let old = run(&mut reopened, "sketch.inspect", json!({"sketch":"Sketch1"})); assert_eq!(old["plane"]["origin"], json!([0.0, 0.0, 0.0]), "saved origin reference is not reinterpreted"); let linked = run(&mut reopened, "sketch.inspect", json!({"sketch":"Sketch2"})); @@ -380,7 +380,7 @@ fn disambiguated_top_plane_pocket_survives_undo_and_cold_reopen() { assert_eq!(s.doc.to_json(), before); run(&mut s, "edit.redo", json!({})); assert_eq!(s.doc.to_json(), saved); - let mut reopened = Session::new(solvecraft_doc::Document::from_json(&saved).unwrap()); + let mut reopened = Session::new(solidcraft_doc::Document::from_json(&saved).unwrap()); let measured = run(&mut reopened, "inspect.measure", json!({})); assert!((measured["total"]["volume_mm3"].as_f64().unwrap() - expected).abs() < 1e-6, "{measured}"); } diff --git a/crates/engine/src/crash.rs b/crates/engine/src/crash.rs index fc2725f5..7d22af3a 100644 --- a/crates/engine/src/crash.rs +++ b/crates/engine/src/crash.rs @@ -3,8 +3,8 @@ //! //! - **Panics** are caught by a hook. One inside a command becomes a report with the design as //! it was before the command and the command itself, so `solidcraft-cli run replay.json --in -//! design.solvecraft` reproduces it. Panics the third-party kernel raises (and -//! `solvecraft_kernel::guard` turns into errors) are reported the same way, marked recovered. +//! design.solidcraft` reproduces it. Panics the third-party kernel raises (and +//! `solidcraft_kernel::guard` turns into errors) are reported the same way, marked recovered. //! - **Crashes no hook sees** (an abort, a stack overflow, a segfault, the process being killed) //! are found at the next start: every running app keeps a small, locked command journal; a //! journal nobody holds any more belongs to an app that died. Its report carries the design @@ -18,7 +18,7 @@ //! reports, [`MAX_TOTAL_BYTES`] on disk, nothing older than [`MAX_AGE_DAYS`] days. //! //! Reports are folders `crashes/