Rename displayed product to SolidCraft and add header bug reporting

This commit is contained in:
bherbruck committed 2026-10-11 16:16:48 -04:00
1 parent 1d3048062d
commit af536a7907
85 files changed
+442 -242

No files matched your search

+14 -9
View File
@@ -1,14 +1,19 @@
SolveCraft is open-source parametric 3D CAD in Rust: sketch, constrain, extrude, and change a
parameter to watch the timeline rebuild. [Documentation](https://bherbruck.github.io/solvecraft/)
· [try it in the browser](https://bherbruck.github.io/solvecraft/app/) ·
[contributing](https://github.com/bherbruck/solvecraft/blob/main/CONTRIBUTING.md) ·
[issues](https://github.com/bherbruck/solvecraft/issues). AI agents can model with it over MCP:
`solvecraft-cli mcp` ([how](https://bherbruck.github.io/solvecraft/guide/mcp.html)).
SolidCraft is open-source parametric 3D CAD in Rust: sketch, constrain, extrude, and change a
parameter to watch the timeline rebuild. [Documentation](https://bherbruck.github.io/solidcraft/)
· [try it in the browser](https://bherbruck.github.io/solidcraft/app/) ·
[contributing](https://github.com/bherbruck/solidcraft/blob/main/CONTRIBUTING.md) ·
[issues](https://github.com/bherbruck/solidcraft/issues). AI agents can model with it over MCP:
`solvecraft-cli mcp` ([how](https://bherbruck.github.io/solidcraft/guide/mcp.html)).
Compatibility names are unchanged: existing `.solvecraft` designs, `solvecraft-cli`, package
IDs and archive filenames still work. Published 0.2.0 and 0.2.1 releases keep their original
SolveCraft branding. Documentation, the browser app and the Flatpak feed use the new
SolidCraft project Pages URLs; old-name Pages addresses are not permanent aliases.
**Which file?**
- **Windows:** `SolveCraft-{version}-windows-x64.exe` runs as is. Or install the `.msi`, or unzip the
`-portable.zip` (x64, x86 and arm64).
- **macOS 11+** (Apple silicon and Intel): open the `.dmg` and drag SolveCraft onto Applications.
- **macOS 11+** (Apple silicon and Intel): open the `.dmg` and drag `SolveCraft.app` onto Applications.
The CLI is in `solvecraft-cli-{version}-macos-universal.zip`.
- **Linux** (x86_64 and aarch64, glibc 2.35+): the `.AppImage` (`chmod +x`, then run it), the
`.deb` or `.rpm`, the `.flatpak` (`flatpak install --user <file>`), or the `.tar.gz`.
@@ -16,10 +21,10 @@ parameter to watch the timeline rebuild. [Documentation](https://bherbruck.githu
- **Web:** `solvecraft-web-{version}.zip` is a static site to host yourself.
- `SHA256SUMS.txt`: `sha256sum -c SHA256SUMS.txt --ignore-missing`.
The builds aren't signed yet. On macOS, right-click SolveCraft in Applications and choose
The builds aren't signed yet. On macOS, right-click `SolveCraft.app` in Applications and choose
**Open** the first time (or run `xattr -dr com.apple.quarantine /Applications/SolveCraft.app`).
On Windows, SmartScreen may say "Windows protected your PC": **More info**, then **Run anyway**.
Licences are built in (Help ▸ About ▸ Licences, `solvecraft-cli licences`); the packages also
install `THIRD-PARTY-LICENSES.txt`. Known limitations:
[ROADMAP.md](https://github.com/bherbruck/solvecraft/blob/main/ROADMAP.md#known-limitations).
[ROADMAP.md](https://github.com/bherbruck/solidcraft/blob/main/ROADMAP.md#known-limitations).
+3 -3
View File
@@ -1,5 +1,5 @@
# The documentation site on GitHub Pages: the book (docs/book) at /solvecraft/ and the web app
# at /solvecraft/app/. Needs Settings → Pages → Source = GitHub Actions; skipped while the
# The documentation site on GitHub Pages: the book (docs/book) at /solidcraft/ and the web app
# at /solidcraft/app/. Needs Settings → Pages → Source = GitHub Actions; skipped while the
# repository is private.
name: Pages
@@ -49,7 +49,7 @@ jobs:
run: cargo xtask book
- name: Build the web app
working-directory: apps/solvecraft-web
run: trunk build --release --public-url /solvecraft/app/
run: trunk build --release --public-url /solidcraft/app/
- name: Assemble the site
run: |
mkdir -p site
+5 -5
View File
@@ -77,7 +77,7 @@ jobs:
fi
case "$VERSION" in *-*) pre=true ;; *) pre=false ;; esac
{ echo "version=$VERSION"; echo "date=$(date -u +%Y-%m-%d)"; echo "prerelease=$pre"; } >>"$GITHUB_OUTPUT"
echo "### SolveCraft v$VERSION (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
echo "### SolidCraft v$VERSION (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
# The app icon in every size, drawn by solvecraft-cli (no image files are committed), for the
# Windows jobs: the ARM64 build is cross-compiled and can't run there to draw its own.
@@ -357,7 +357,7 @@ jobs:
sha256sum --check SHA256SUMS.txt
# Stage all packages before publishing. Published tags are never replaced.
gh release create "$tag" --draft --prerelease --latest=false \
--title "SolveCraft v$VERSION (nightly)" --target "$GITHUB_SHA" \
--title "SolidCraft v$VERSION (nightly)" --target "$GITHUB_SHA" \
--notes-file ../notes.md --generate-notes -- *
gh release edit "$tag" --draft=false --prerelease --latest=false
echo "### Published nightly: $tag (\`${GITHUB_SHA::9}\`)" >>"$GITHUB_STEP_SUMMARY"
@@ -386,15 +386,15 @@ jobs:
while IFS= read -r asset; do
if [ -n "$asset" ]; then gh release delete-asset "$tag" "$asset" --yes; fi
done <<<"$assets"
gh release edit "$tag" --draft --title "SolveCraft v$VERSION" \
gh release edit "$tag" --draft --title "SolidCraft v$VERSION" \
--target "$GITHUB_SHA" --prerelease="$PRERELEASE"
gh release upload "$tag" --clobber -- *
else
gh release create "$tag" --draft --title "SolveCraft v$VERSION" \
gh release create "$tag" --draft --title "SolidCraft v$VERSION" \
--target "$GITHUB_SHA" --notes-file ../notes.md --generate-notes "${pre[@]}" -- *
fi
{
echo "Draft **SolveCraft v$VERSION** at \`${GITHUB_SHA::9}\`:"
echo "Draft **SolidCraft v$VERSION** at \`${GITHUB_SHA::9}\`:"
echo
sed 's/^/ /' SHA256SUMS.txt
} >>"$GITHUB_STEP_SUMMARY"
+13 -5
View File
@@ -1,12 +1,20 @@
# SolveCraft — instructions for agents
# SolidCraft — instructions for agents
SolveCraft is a clean-room, open-source, pure-Rust parametric 3D CAD application in the style of
SolidCraft is a clean-room, open-source, pure-Rust parametric 3D CAD application in the style of
Autodesk Fusion: sketches with a constraint solver, a parametric timeline of features, a B-rep
kernel, and a desktop UI that agents can drive. Siblings with the same conventions live next to
it in `storytold/`: `cadcraft` (AutoCAD-style, the closest relative), `photocraft`, `vectorcraft`,
`gridcraft`, `designcraft`, `wordcraft`, `deckcraft`, `soundcraft`. Repos don't share code; when
code is adapted from a sibling (MIT OR Apache-2.0) it 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.
## Start every session here
1. Read `plan/STATUS.md` (current milestone, next task), then the task in `plan/execution-plan.md`
and the relevant `plan/architecture.md` section. Behaviour reference: `plan/fusion/`
@@ -20,7 +28,7 @@ code is adapted from a sibling (MIT OR Apache-2.0) it is noted in ATTRIBUTION.md
`plan/` is gitignored (local only).
## Never crash
People trust SolveCraft with their designs; a crash loses their work. **This outranks feature work.**
People trust SolidCraft with their designs; a crash loses their work. **This outranks feature work.**
- **No panics in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`,
`unimplemented!`; no `unsafe` (`unsafe_code = "forbid"`).
- **Errors are `Result<T, E>`** through the crate's error type and `?`. An unfinished feature
@@ -44,7 +52,7 @@ People trust SolveCraft with their designs; a crash loses their work. **This out
copy or link GPL/LGPL/AGPL code**: OpenCASCADE, FreeCAD (incl. planegcs), SolveSpace,
CadQuery/OCP, LibreCAD, QCAD — don't even read their sources. The sketch solver and the blend
operations are our own.
- **Assets — absolutely essential.** SolveCraft contains **no Autodesk iconography, images,
- **Assets — absolutely essential.** SolidCraft contains **no Autodesk iconography, images,
fonts or artwork — ever.** Every icon is drawn in code (`crates/ui-egui/src/icons.rs`). Any
file asset must be original or openly licensed and **must have a row in ATTRIBUTION.md**
(`cargo xtask assets` enforces it). Screenshots of Autodesk software are never committed.
@@ -64,7 +72,7 @@ People trust SolveCraft with their designs; a crash loses their work. **This out
- **Rust only.** Units are millimetres and radians internally; Z is up.
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests,
assets, layers). Commit after every feature arc that builds.
- **Publishing**: `origin` is github.com/bherbruck/solvecraft (private). After each commit that
- **Publishing**: `origin` is github.com/bherbruck/solidcraft. After each commit that
passes `cargo xtask ci`: `git pull --rebase origin main` (re-run ci if it brought changes),
then `git push`. Never force-push. Never commit `plan/`, build dirs (`target*/`) or anything
produced by Fusion (oracle STEP/STL/PNG); check `git ls-files` before pushing.
+13 -5
View File
@@ -1,12 +1,20 @@
# SolveCraft — instructions for agents
# SolidCraft — instructions for agents
SolveCraft is a clean-room, open-source, pure-Rust parametric 3D CAD application in the style of
SolidCraft is a clean-room, open-source, pure-Rust parametric 3D CAD application in the style of
Autodesk Fusion: sketches with a constraint solver, a parametric timeline of features, a B-rep
kernel, and a desktop UI that agents can drive. Siblings with the same conventions live next to
it in `storytold/`: `cadcraft` (AutoCAD-style, the closest relative), `photocraft`, `vectorcraft`,
`gridcraft`, `designcraft`, `wordcraft`, `deckcraft`, `soundcraft`. Repos don't share code; when
code is adapted from a sibling (MIT OR Apache-2.0) it 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.
## Start every session here
1. Read `plan/STATUS.md` (current milestone, next task), then the task in `plan/execution-plan.md`
and the relevant `plan/architecture.md` section. Behaviour reference: `plan/fusion/`
@@ -20,7 +28,7 @@ code is adapted from a sibling (MIT OR Apache-2.0) it is noted in ATTRIBUTION.md
`plan/` is gitignored (local only).
## Never crash
People trust SolveCraft with their designs; a crash loses their work. **This outranks feature work.**
People trust SolidCraft with their designs; a crash loses their work. **This outranks feature work.**
- **No panics in non-test code:** no `unwrap()`, `expect()`, `panic!`, `unreachable!`, `todo!`,
`unimplemented!`; no `unsafe` (`unsafe_code = "forbid"`).
- **Errors are `Result<T, E>`** through the crate's error type and `?`. An unfinished feature
@@ -44,7 +52,7 @@ People trust SolveCraft with their designs; a crash loses their work. **This out
copy or link GPL/LGPL/AGPL code**: OpenCASCADE, FreeCAD (incl. planegcs), SolveSpace,
CadQuery/OCP, LibreCAD, QCAD — don't even read their sources. The sketch solver and the blend
operations are our own.
- **Assets — absolutely essential.** SolveCraft contains **no Autodesk iconography, images,
- **Assets — absolutely essential.** SolidCraft contains **no Autodesk iconography, images,
fonts or artwork — ever.** Every icon is drawn in code (`crates/ui-egui/src/icons.rs`). Any
file asset must be original or openly licensed and **must have a row in ATTRIBUTION.md**
(`cargo xtask assets` enforces it). Screenshots of Autodesk software are never committed.
@@ -64,7 +72,7 @@ People trust SolveCraft with their designs; a crash loses their work. **This out
- **Rust only.** Units are millimetres and radians internally; Z is up.
- **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests,
assets, layers). Commit after every feature arc that builds.
- **Publishing**: `origin` is github.com/bherbruck/solvecraft (private). After each commit that
- **Publishing**: `origin` is github.com/bherbruck/solidcraft. After each commit that
passes `cargo xtask ci`: `git pull --rebase origin main` (re-run ci if it brought changes),
then `git push`. Never force-push. Never commit `plan/`, build dirs (`target*/`) or anything
produced by Fusion (oracle STEP/STL/PNG); check `git ls-files` before pushing.
+11 -11
View File
@@ -1,6 +1,6 @@
# Contributing to SolveCraft
# Contributing to SolidCraft
SolveCraft is an open-source parametric 3D CAD app written in pure Rust, in the style of
SolidCraft is an open-source parametric 3D CAD app written in pure Rust, in the style of
Autodesk Fusion. You sketch on a plane, constrain the sketch, turn profiles into solids, and
change a parameter to watch the timeline rebuild. It runs as a desktop app (Windows, Linux), in
the browser (wasm + WebGPU/WebGL2), headless from a CLI, and as an MCP server that AI agents can
@@ -16,7 +16,7 @@ You need stable Rust 1.90 or newer. For the web
build you also need `rustup target add wasm32-unknown-unknown` and [`trunk`](https://trunkrs.dev).
```sh
git clone https://github.com/bherbruck/solvecraft && cd solvecraft
git clone https://github.com/bherbruck/solidcraft && cd solvecraft
cargo run --release -p solvecraft -- --sample # desktop app with a sample part
cargo xtask test # unit, engine and UI-scenario tests
cargo xtask ci # everything a PR must pass (see below)
@@ -97,7 +97,7 @@ The [README](README.md#architecture) has the layer table. The short version:
tooltips, templates or materials, and we never commit files Fusion produced. Command ids are
our own (`solid.fillet`, not Fusion's internal names).
2. **No GPL/LGPL/AGPL code, not even reading it.** That rules out OpenCASCADE, FreeCAD
(including planegcs), SolveSpace, CadQuery/OCP, LibreCAD and QCAD. SolveCraft is
(including planegcs), SolveSpace, CadQuery/OCP, LibreCAD and QCAD. SolidCraft is
MIT OR Apache-2.0, and its solver and blends are written from the maths. Permissive
dependencies are fine. Note which one you're adding and why in the PR, and run
`cargo xtask licences` so THIRD-PARTY-LICENSES.txt lists it (CI checks that it is current).
@@ -151,14 +151,14 @@ in the PR. `cargo xtask test` builds the tests with it and then runs every test
depend on wall-clock timing.
**The Fusion oracle** is a set of 76 reference parts, each built in Fusion from a recipe.
SolveCraft rebuilds the same recipe and compares volume, area and topology counts with Fusion's
SolidCraft rebuilds the same recipe and compares volume, area and topology counts with Fusion's
measurements; [docs/oracle.md](docs/oracle.md) has the current results. The recipes and
measurements live outside the repo (`plan/`, gitignored), because they come from a Fusion
install. If you work on geometry and want to check against it, ask in an issue.
## Sending a change
1. Work is tracked in [GitHub issues](https://github.com/bherbruck/solvecraft/issues), labelled
1. Work is tracked in [GitHub issues](https://github.com/bherbruck/solidcraft/issues), labelled
by area (`area:kernel`, `area:sketch`, `area:doc`, `area:ui`, `area:infra`). Before starting,
comment on the issue so it gets the `in progress` label and two people don't build the same
thing. For anything bigger than a bug fix that has no issue yet, open one first.
@@ -189,15 +189,15 @@ CI if it brought changes, and push without force. Report the upstream/PR state y
any conflicts resolved and validation results. While a task remains active, follow an open
PR through its status changes; at handoff, clearly identify anything still awaiting merge.
Much of SolveCraft so far was written by AI coding agents (Claude) working in parallel, each
Much of SolidCraft so far was written by AI coding agents (Claude) working in parallel, each
owning one area, with a human setting direction and reporting bugs. Their working notes are in
[AGENTS.md](AGENTS.md). Human contributors are just as welcome. Use whatever tools you like, as
long as the change meets the rules above.
## Where help is wanted most
See the [`help wanted`](https://github.com/bherbruck/solvecraft/labels/help%20wanted) and
[`good first issue`](https://github.com/bherbruck/solvecraft/labels/good%20first%20issue) labels.
See the [`help wanted`](https://github.com/bherbruck/solidcraft/labels/help%20wanted) and
[`good first issue`](https://github.com/bherbruck/solidcraft/labels/good%20first%20issue) labels.
In order of how much they'd help users:
1. **Fillet and blend robustness.** Fillets are our own code (truck has no fillet operation).
@@ -206,7 +206,7 @@ In order of how much they'd help users:
faces, and fillets that consume neighbouring faces.
2. **Boolean robustness.** A random-design fuzzer currently gets about half of its designs
through without a kernel error. Each failing seed is a reproducible bug.
3. **Y-up / Z-up.** SolveCraft is Z-up only, so most STEP files (from SolidWorks, Onshape, or
3. **Y-up / Z-up.** SolidCraft is Z-up only, so most STEP files (from SolidWorks, Onshape, or
Fusion's default) open lying on their backs. Fusion solves this with a "default modeling
orientation" preference.
4. **Imported appearances.** Colours from STEP files are lost when a feature changes the
@@ -217,4 +217,4 @@ In order of how much they'd help users:
## Licence
By contributing you agree that your contribution is licensed as MIT OR Apache-2.0, like the rest
of SolveCraft. SolveCraft isn't affiliated with Autodesk. Fusion is a trademark of Autodesk, Inc.
of SolidCraft. SolidCraft isn't affiliated with Autodesk. Fusion is a trademark of Autodesk, Inc.
+2 -2
View File
@@ -9,8 +9,8 @@ version = "0.2.1"
edition = "2024"
license = "MIT OR Apache-2.0"
rust-version = "1.90"
authors = ["SolveCraft contributors"]
repository = "https://github.com/bherbruck/solvecraft"
authors = ["SolidCraft contributors"]
repository = "https://github.com/bherbruck/solidcraft"
[workspace.dependencies]
# internal
+19 -8
View File
@@ -1,4 +1,4 @@
# SolveCraft
# SolidCraft
**Parametric 3D CAD; an open-source, [clean-room](CLAUDE.md) reimplementation of Autodesk
Fusion 360, built in pure Rust.**
@@ -7,20 +7,31 @@ Sketch, constrain, and build solids with a parametric feature timeline. Change a
and watch your model rebuild. Run it on your desktop, in your browser, or let AI agents
model through the built-in MCP server. Part of the storytold "craft" family.
![SolveCraft: draw a rounded electronics enclosure, sketch its USB port directly on a face, add four mounting bosses and resize it through Parameters](docs/screenshots/enclosure-demo-0.2.1.gif)
![SolidCraft: draw a rounded electronics enclosure, sketch its USB port directly on a face, add four mounting bosses and resize it through Parameters](docs/screenshots/enclosure-demo-0.2.1.gif)
*Start with an empty design and draw the original parametric electronics enclosure in the English desktop UI: rounded corners, a shell, a USB port sketched directly on the wall and a four-boss pattern. Change width from 80 → 100 mm and wall thickness from 2 → 3 mm in Parameters and watch it rebuild. Original animated pointer and click cues; 4× playback. [Full video](https://github.com/bherbruck/solvecraft/raw/refs/heads/main/docs/screenshots/enclosure-demo-0.2.1.mp4), generated with the [demo script](examples/demo/record_ui.sh).*
*Start with an empty design and draw the original parametric electronics enclosure in the English desktop UI: rounded corners, a shell, a USB port sketched directly on the wall and a four-boss pattern. Change width from 80 → 100 mm and wall thickness from 2 → 3 mm in Parameters and watch it rebuild. Original animated pointer and click cues; 4× playback. [Full video](https://github.com/bherbruck/solidcraft/raw/refs/heads/main/docs/screenshots/enclosure-demo-0.2.1.mp4), generated with the [demo script](examples/demo/record_ui.sh).*
**Documentation:** the [SolveCraft book](https://bherbruck.github.io/solvecraft/) (user guide,
**Documentation:** the [SolidCraft book](https://bherbruck.github.io/solidcraft/) (user guide,
developer guide and command reference; sources in [docs/book](docs/book)), and the
[web app](https://bherbruck.github.io/solvecraft/app/) to try it in the browser.
[web app](https://bherbruck.github.io/solidcraft/app/) to try it in the browser.
**Status: early but usable for simple parts.** 76 of 76 reference parts built in Fusion
rebuild in SolveCraft with the same volume, area and topology ([docs/oracle.md](docs/oracle.md));
rebuild in SolidCraft with the same volume, area and topology ([docs/oracle.md](docs/oracle.md));
command parity with Fusion's Design workspace is tracked in [docs/parity.md](docs/parity.md).
Open work and known bugs are in [GitHub issues](https://github.com/bherbruck/solvecraft/issues),
Open work and known bugs are in [GitHub issues](https://github.com/bherbruck/solidcraft/issues),
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.
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.
## What works
- **Sketches** on origin planes, offset planes or planar faces: lines, rectangles, circles,
@@ -139,5 +150,5 @@ and licence rules, and where help is wanted most.
## Licence
MIT OR Apache-2.0, at your option. SolveCraft is not affiliated with Autodesk; Fusion is a
MIT OR Apache-2.0, at your option. SolidCraft is not affiliated with Autodesk; Fusion is a
trademark of Autodesk, Inc.
+5 -2
View File
@@ -1,6 +1,6 @@
# SolveCraft roadmap
# SolidCraft roadmap
Status as of **2026-10-11**. SolveCraft targets Fusion-style parametric modelling (sketch →
Status as of **2026-10-11**. SolidCraft targets Fusion-style parametric modelling (sketch →
features → parametric timeline) as an open, pure-Rust, agent-drivable application.
## At a glance
@@ -72,6 +72,9 @@ headline counts in-scope tabs only and lists the deferred ones separately.
useful reproduction context. The draft is editable, with optional capture of the actual
viewport and copy/save controls. Opening GitHub does not submit the issue. Help also exposes
reporting for unexpected results that did not produce an error.
- The header exposes **Report a bug**, including an accessible compact control on
narrow windows. It opens the same editable report and screenshot review without
changing the active model or a pending dimension.
## 0.2.0 release batch
+4 -4
View File
@@ -1,8 +1,8 @@
Third-party software in SolveCraft
Third-party software in SolidCraft
SolveCraft (the desktop app, solvecraft-cli and the web app) is built from the 453 crates below.
SolidCraft (the desktop app, solvecraft-cli and the web app) is built from the 453 crates below.
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.
SolveCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.
accesskit 0.24.1 (MIT OR Apache-2.0)
accesskit_atspi_common 0.18.1 (MIT OR Apache-2.0)
@@ -13017,7 +13017,7 @@ freely, subject to the following restrictions:
================================================================================
Crates whose package carries no licence file (their licence is named in Cargo.toml; the MIT and Apache-2.0
texts are as in SolveCraft's own LICENSE-MIT and LICENSE-APACHE)
texts are as in SolidCraft's own LICENSE-MIT and LICENSE-APACHE)
================================================================================
accesskit 0.24.1: MIT OR Apache-2.0 (https://github.com/AccessKit/accesskit)
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-cli"
description = "SolveCraft headless CLI: run command scripts, measure, export, render snapshots, replay oracle recipes"
description = "SolidCraft headless CLI: run command scripts, measure, export, render snapshots, replay oracle recipes"
version.workspace = true
edition.workspace = true
license.workspace = true
+3 -3
View File
@@ -12,7 +12,7 @@ pub const REL_TOL: f64 = 1e-3;
const APPROXIMATE: [(&str, f64, &str); 1] = [(
"49-helical-sweep",
5e-3,
"Fusion sweeps along a spline fitted through helix points; SolveCraft builds the true helix (a coil), so the size is compared within 0.5% and the topology not at all",
"Fusion sweeps along a spline fitted through helix points; SolidCraft builds the true helix (a coil), so the size is compared within 0.5% and the topology not at all",
)];
fn check_case(dir: &str) -> Value {
@@ -77,7 +77,7 @@ fn check_case(dir: &str) -> Value {
})
.collect();
seam_note = Some(format!(
"topology compared seam-normalised: Fusion's {}/{}/{} faces/edges/vertices become {f}/{e}/{v} with closed-surface patches counted once and seam edges and vertices dropped (raw SolveCraft/Fusion: {})",
"topology compared seam-normalised: Fusion's {}/{}/{} faces/edges/vertices become {f}/{e}/{v} with closed-surface patches counted once and seam edges and vertices dropped (raw SolidCraft/Fusion: {})",
want["total"]["faces"],
want["total"]["edges"],
want["total"]["vertices"],
@@ -120,7 +120,7 @@ fn check_case(dir: &str) -> Value {
.collect();
if !effective.is_empty() {
out["note"] = json!(format!(
"known Fusion difference: {} replayed with the extent Fusion actually used (its through-all is sized as 1.1 × the farthest vertex, so curved tops stop it short; SolveCraft's through-all goes truly through)",
"known Fusion difference: {} replayed with the extent Fusion actually used (its through-all is sized as 1.1 × the farthest vertex, so curved tops stop it short; SolidCraft's through-all goes truly through)",
effective.join(", ")
));
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-web"
description = "SolveCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
description = "SolidCraft in the browser (eframe web runner, WebGPU with WebGL2 fallback)"
version.workspace = true
edition.workspace = true
license.workspace = true
+2 -2
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<title>SolveCraft</title>
<title>SolidCraft</title>
<link data-trunk rel="rust" data-bin="solvecraft-web" data-wasm-opt="z" data-weak-refs />
<style>
html, body { margin: 0; padding: 0; width: 100%; height: 100%; overflow: hidden; background: #e8ebf0; }
@@ -14,6 +14,6 @@
</head>
<body>
<canvas id="solvecraft_canvas"></canvas>
<div id="solvecraft_loading">Loading SolveCraft&hellip;</div>
<div id="solvecraft_loading">Loading SolidCraft&hellip;</div>
</body>
</html>
+1 -1
View File
@@ -406,7 +406,7 @@ mod web {
match result {
Ok(()) => el.remove(),
Err(e) => {
el.set_inner_html(&format!("<p>SolveCraft failed to start: {e:?}</p><p>A browser with WebGPU or WebGL2 is required.</p>"))
el.set_inner_html(&format!("<p>SolidCraft failed to start: {e:?}</p><p>A browser with WebGPU or WebGL2 is required.</p>"))
}
}
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft"
description = "SolveCraft desktop app"
description = "SolidCraft desktop app"
version.workspace = true
edition.workspace = true
license.workspace = true
+3 -3
View File
@@ -57,7 +57,7 @@ fn services() -> Services {
"Designs, STEP, IGES and meshes",
&["solvecraft", "step", "stp", "STEP", "STP", "igs", "iges", "IGS", "IGES", "3mf", "3MF", "stl", "STL"],
)
.add_filter("SolveCraft design", &["solvecraft"])
.add_filter("SolidCraft design", &["solvecraft"])
.add_filter("STEP", &["step", "stp", "STEP", "STP"])
.add_filter("IGES", &["igs", "iges", "IGS", "IGES"])
.add_filter("Mesh (3MF, STL)", &["3mf", "3MF", "stl", "STL"])
@@ -139,7 +139,7 @@ fn main() -> eframe::Result {
}
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_title("SolveCraft")
.with_title("SolidCraft")
.with_inner_size([1600.0, 1000.0])
.with_min_inner_size([960.0, 600.0])
.with_drag_and_drop(true)
@@ -166,7 +166,7 @@ fn main() -> eframe::Result {
}));
}
eframe::run_native(
"SolveCraft",
"SolidCraft",
options,
Box::new(move |cc| {
solvecraft_ui_egui::fonts::setup_fonts(&cc.egui_ctx);
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-doc"
description = "SolveCraft document model: parameters and expressions, sketches and features on a parametric timeline, incremental evaluation"
description = "SolidCraft document model: parameters and expressions, sketches and features on a parametric timeline, incremental evaluation"
version.workspace = true
edition.workspace = true
license.workspace = true
+11 -11
View File
@@ -132,21 +132,21 @@ fn v2_to_v3(v: &mut Value) -> Result<()> {
/// Read a design file's JSON, upgrading older formats.
pub fn read(s: &str) -> Result<Document> {
let mut v: Value = serde_json::from_str(s).map_err(|e| DocError::Invalid(format!("not a SolveCraft design: {e}")))?;
let mut v: Value = serde_json::from_str(s).map_err(|e| DocError::Invalid(format!("not a SolidCraft design: {e}")))?;
let Some(obj) = v.as_object() else {
return Err(DocError::Invalid("not a SolveCraft design (expected a JSON object)".into()));
return Err(DocError::Invalid("not a SolidCraft design (expected a JSON object)".into()));
};
let version = match obj.get("format") {
None => 1,
Some(Value::String(f)) => version_of(f).ok_or_else(|| DocError::Invalid(format!("not a SolveCraft design (format `{f}`)")))?,
Some(_) => return Err(DocError::Invalid("not a SolveCraft design (format is not a string)".into())),
Some(Value::String(f)) => version_of(f).ok_or_else(|| DocError::Invalid(format!("not a SolidCraft design (format `{f}`)")))?,
Some(_) => return Err(DocError::Invalid("not a SolidCraft design (format is not a string)".into())),
};
if version == 0 {
return Err(DocError::Invalid("not a SolveCraft design (format 0)".into()));
return Err(DocError::Invalid("not a SolidCraft design (format 0)".into()));
}
if version > FORMAT {
return Err(DocError::Invalid(format!(
"this design was saved by a newer SolveCraft (file format {version}; this version reads formats up to {FORMAT}): update SolveCraft to open it"
"this design was saved by a newer SolidCraft (file format {version}; this version reads formats up to {FORMAT}): update SolidCraft to open it"
)));
}
check_numbers(&v, 0)?;
@@ -950,7 +950,7 @@ mod tests {
let error = read(&value.to_string()).unwrap_err().to_string();
assert!(error.contains("free-form offset metadata"), "{error}");
value["format"] = Value::from(format_string(FORMAT + 1));
assert!(read(&value.to_string()).unwrap_err().to_string().contains("newer SolveCraft"));
assert!(read(&value.to_string()).unwrap_err().to_string().contains("newer SolidCraft"));
}
}
@@ -975,7 +975,7 @@ mod tests {
assert!(error.contains("emboss face"), "{error}");
value["format"] = Value::from(format_string(FORMAT + 1));
let error = read(&value.to_string()).unwrap_err().to_string();
assert!(error.contains("newer SolveCraft"), "{error}");
assert!(error.contains("newer SolidCraft"), "{error}");
}
}
@@ -1038,7 +1038,7 @@ mod tests {
value["configs"]["columns"] = serde_json::json!([{"unknown_future_column":"Body"}]);
value["configs"]["rules"] = serde_json::json!({"unknown_future_rule": true});
let error = read(&value.to_string()).unwrap_err().to_string();
assert!(error.contains("newer SolveCraft") && error.contains("update SolveCraft"), "{error}");
assert!(error.contains("newer SolidCraft") && error.contains("update SolidCraft"), "{error}");
assert!(!error.contains("unknown variant"), "format refusal precedes decoding new variants");
}
@@ -1090,7 +1090,7 @@ mod tests {
saved["format"] = Value::from(format_string(FORMAT + 1));
saved["features"][0]["sketch"]["constraints"][0]["type"] = Value::from("future_dimension");
let error = read(&saved.to_string()).unwrap_err().to_string();
assert!(error.contains("newer SolveCraft") && !error.contains("unknown variant"), "{error}");
assert!(error.contains("newer SolidCraft") && !error.contains("unknown variant"), "{error}");
}
#[test]
@@ -1202,7 +1202,7 @@ mod tests {
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 SolveCraft"), "{e}");
assert!(e.contains("newer SolidCraft"), "{e}");
for bad in ["solvecraft/0", "acad/3", "solvecraft/-1", "solvecraft/99999999999999999999"] {
v["format"] = Value::from(bad);
assert!(read(&v.to_string()).is_err(), "{bad}");
+1 -1
View File
@@ -369,7 +369,7 @@ mod tests {
damaged["features"][1]["profiles"]["associations"] = associations;
assert!(validate_raw(&damaged).is_err());
damaged["format"] = json!(crate::format::format_string(crate::format::FORMAT + 1));
assert!(crate::format::read(&damaged.to_string()).unwrap_err().to_string().contains("newer SolveCraft"));
assert!(crate::format::read(&damaged.to_string()).unwrap_err().to_string().contains("newer SolidCraft"));
}
// Absent metadata preserves the historical one-to-one chain representation.
let mut legacy = fixture();
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-engine"
description = "SolveCraft engine: the session, the command registry (everything is a command), undo/redo and selection"
description = "SolidCraft engine: the session, the command registry (everything is a command), undo/redo and selection"
version.workspace = true
edition.workspace = true
license.workspace = true
+1 -1
View File
@@ -314,7 +314,7 @@ pub(super) fn paste(
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], "SolveCraft independent reassigned body")?;
let step = solvecraft_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);
+1 -1
View File
@@ -109,7 +109,7 @@ fn insert(s: &mut Session, p: &Value, cmd: &str, svg: bool) -> Result<Value> {
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()))?;
if geom.is_empty() {
return Err(bad(cmd, "the drawing has no geometry SolveCraft reads"));
return Err(bad(cmd, "the drawing has no geometry SolidCraft reads"));
}
let k = num(p, "scale").filter(|k| *k > 1e-9 && *k < 1e6).unwrap_or(1.0);
let off = p.get("at").and_then(vec2).unwrap_or(Vec2::ZERO);
+1 -1
View File
@@ -11,7 +11,7 @@ pub const THIRD_PARTY: &str = include_str!("../../../THIRD-PARTY-LICENSES.txt");
pub fn all() -> String {
let rule = "=".repeat(80);
format!(
"SolveCraft is licensed under the MIT License or the Apache License, Version 2.0, at your option.\n\n{rule}\nNOTICE\n{rule}\n\n{NOTICE}\n{rule}\nLICENSE-MIT\n{rule}\n\n{LICENSE_MIT}\n{rule}\nLICENSE-APACHE\n{rule}\n\n{LICENSE_APACHE}\n{rule}\n{THIRD_PARTY}"
"SolidCraft is licensed under the MIT License or the Apache License, Version 2.0, at your option.\n\n{rule}\nNOTICE\n{rule}\n\n{NOTICE}\n{rule}\nLICENSE-MIT\n{rule}\n\n{LICENSE_MIT}\n{rule}\nLICENSE-APACHE\n{rule}\n\n{LICENSE_APACHE}\n{rule}\n{THIRD_PARTY}"
)
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-geom"
description = "SolveCraft geometry foundation: vectors, planes, frames, meshes and their measures"
description = "SolidCraft geometry foundation: vectors, planes, frames, meshes and their measures"
version.workspace = true
edition.workspace = true
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-io"
description = "SolveCraft file formats: designs (JSON), STL, OBJ, STEP and 3MF"
description = "SolidCraft file formats: designs (JSON), STL, OBJ, STEP and 3MF"
version.workspace = true
edition.workspace = true
license.workspace = true
+2 -2
View File
@@ -202,7 +202,7 @@ pub fn export_mesh(b: &solvecraft_kernel::Body) -> Result<Mesh> {
pub fn stl_binary(meshes: &[Mesh], name: &str) -> Vec<u8> {
let n: usize = meshes.iter().map(|m| m.triangles.len()).sum();
let mut out = Vec::with_capacity(84 + n * 50);
let mut header = format!("SolveCraft STL {name}").into_bytes();
let mut header = format!("SolidCraft STL {name}").into_bytes();
header.resize(80, b' ');
out.extend_from_slice(&header);
out.extend_from_slice(&u32::try_from(n).unwrap_or(u32::MAX).to_le_bytes());
@@ -242,7 +242,7 @@ pub fn stl_ascii(meshes: &[Mesh], name: &str) -> String {
/// Wavefront OBJ, one object per body.
pub fn obj(meshes: &[(String, Mesh)]) -> String {
let mut s = String::from("# SolveCraft OBJ export (mm)\n");
let mut s = String::from("# SolidCraft OBJ export (mm)\n");
let mut base = 1usize;
for (name, m) in meshes {
s += &format!("o {}\n", name.replace(char::is_whitespace, "_"));
+1 -1
View File
@@ -211,7 +211,7 @@ pub fn model_xml(objects: &[MeshObject]) -> String {
let mut x = String::new();
x.push_str("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
let _ = writeln!(x, "<model unit=\"millimeter\" xml:lang=\"en-US\" xmlns=\"{CORE_NS}\">");
x.push_str(" <metadata name=\"Application\">SolveCraft</metadata>\n <resources>\n");
x.push_str(" <metadata name=\"Application\">SolidCraft</metadata>\n <resources>\n");
let colored = objects.iter().any(|o| o.color.is_some() || o.transparency > 0.0 || !o.looks.is_empty());
// Base material group id 1 (each object's own colour, then its faces' colours); objects
// from 2. A see-through colour carries its alpha (#RRGGBBAA).
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-kernel"
description = "SolveCraft B-rep kernel boundary: solids, extrude/revolve/booleans/fillets, tessellation, measures and STEP, behind our own API (implemented with truck)"
description = "SolidCraft B-rep kernel boundary: solids, extrude/revolve/booleans/fillets, tessellation, measures and STEP, behind our own API (implemented with truck)"
version.workspace = true
edition.workspace = true
license.workspace = true
+1 -1
View File
@@ -1164,7 +1164,7 @@ fn translate(text: &str) -> std::result::Result<(String, Vec<String>), String> {
t.o.add(format!("STYLED_ITEM('color',(#{psa}),#{item})"));
}
let mut s = String::from(
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION(('IGES import'),'2;1');\nFILE_NAME('','',(''),(''),'SolveCraft IGES','','');\nFILE_SCHEMA(('AUTOMOTIVE_DESIGN'));\nENDSEC;\nDATA;\n",
"ISO-10303-21;\nHEADER;\nFILE_DESCRIPTION(('IGES import'),'2;1');\nFILE_NAME('','',(''),(''),'SolidCraft IGES','','');\nFILE_SCHEMA(('AUTOMOTIVE_DESIGN'));\nENDSEC;\nDATA;\n",
);
for (i, l) in t.o.lines.iter().enumerate() {
let _ = writeln!(s, "#{}={l};", i + 1);
+4 -4
View File
@@ -609,7 +609,7 @@ fn layout(w: &W, header: &StepHeader, max_coord: f64) -> String {
let line = |out: &mut String, data: &str, sect: char, seq: usize| {
let _ = writeln!(out, "{data:<72}{sect}{seq:>7}");
};
line(&mut out, "SolveCraft IGES export", 'S', 1);
line(&mut out, "SolidCraft IGES export", 'S', 1);
let stamp = {
let t = header.time_stamp.replace(['-', ':', 'T'], "");
let t: String = t.chars().filter(char::is_ascii_digit).collect();
@@ -618,10 +618,10 @@ fn layout(w: &W, header: &StepHeader, max_coord: f64) -> String {
let g = [
"1H,".to_string(),
"1H;".to_string(),
holl("SolveCraft"),
holl("SolidCraft"),
holl(&header.file_name),
holl("SolveCraft"),
holl("SolveCraft IGES 5.3"),
holl("SolidCraft"),
holl("SolidCraft IGES 5.3"),
"32".into(),
"38".into(),
"6".into(),
+3 -3
View File
@@ -1190,7 +1190,7 @@ fn merge_split_faces(ex: &mut p21::Exchange, paint: &mut HashMap<u64, FaceLook>,
/// The B-rep entities of one body as truck writes them, parsed, and the surfaces of faces
/// merged from pieces.
fn brep_exchange(b: &Body, merge: bool) -> Result<(p21::Exchange, HashMap<u64, V3>, HashMap<u64, FaceLook>)> {
let text = crate::step::truck_step(&[b], "SolveCraft")?;
let text = crate::step::truck_step(&[b], "SolidCraft")?;
let mut ex = p21::parse(&text).map_err(|e| KernelError::Failed(format!("STEP export: {e}")))?;
collapse_short_edges(&mut ex);
// Face colours by exchange face: truck writes each shell's faces in the body's face order.
@@ -1429,10 +1429,10 @@ fn products_text(products: &[ExportProduct], root: usize, header: &StepHeader, m
let mut s = String::new();
s.push_str("ISO-10303-21;\nHEADER;\n");
s.push_str("FILE_DESCRIPTION(('SolveCraft model'),'2;1');\n");
s.push_str("FILE_DESCRIPTION(('SolidCraft model'),'2;1');\n");
let _ = writeln!(
s,
"FILE_NAME({},{},({}),({}),'SolveCraft','SolveCraft','');",
"FILE_NAME({},{},({}),({}),'SolidCraft','SolidCraft','');",
string(&header.file_name),
string(&header.time_stamp),
string(&header.author),
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-mcp"
description = "SolveCraft MCP server: JSON-RPC 2.0 over stdio, driving a headless session or the running app"
description = "SolidCraft MCP server: JSON-RPC 2.0 over stdio, driving a headless session or the running app"
version.workspace = true
edition.workspace = true
license.workspace = true
+3 -3
View File
@@ -89,7 +89,7 @@ impl Backend for Remote {
let reply = match self.roundtrip(&line) {
Ok(r) => r,
// One retry on a fresh connection (the app may have restarted).
Err(_) => self.roundtrip(&line).map_err(|e| format!("SolveCraft at {}: {e}", self.addr))?,
Err(_) => self.roundtrip(&line).map_err(|e| format!("SolidCraft at {}: {e}", self.addr))?,
};
let v: Value = serde_json::from_str(&reply).map_err(|e| format!("bad reply from the app: {e}"))?;
if v.get("ok").and_then(Value::as_bool) == Some(true) {
@@ -102,7 +102,7 @@ impl Backend for Remote {
true
}
fn describe(&self) -> String {
format!("connected to the SolveCraft app at {} (the user watches the viewport)", self.addr)
format!("connected to the SolidCraft app at {} (the user watches the viewport)", self.addr)
}
}
@@ -168,6 +168,6 @@ impl Backend for Headless {
false
}
fn describe(&self) -> String {
"headless in-process SolveCraft session".into()
"headless in-process SolidCraft session".into()
}
}
+2 -2
View File
@@ -23,7 +23,7 @@ const RESOURCE_NOT_FOUND: i64 = -32002;
/// Most messages in one JSON-RPC batch.
const MAX_BATCH: usize = 1000;
const INSTRUCTIONS: &str = "SolveCraft is a parametric 3D CAD app in the style of Autodesk Fusion. Units are millimetres \
const INSTRUCTIONS: &str = "SolidCraft is a parametric 3D CAD app in the style of Autodesk Fusion. Units are millimetres \
and degrees in expressions (internally radians), Z is up. Model like in Fusion: `sketch.create {plane: XY}`, draw \
(`sketch.rectangle.two_point {p0, p1}`, `sketch.circle.center {center, radius}`, `sketch.line {points, closed}`), constrain \
and dimension (`sketch.dimension {entities, value}`; values may name parameters), `sketch.finish`, then features \
@@ -133,7 +133,7 @@ impl Server {
Ok(json!({
"protocolVersion": version,
"capabilities": {"tools": {"listChanged": false}, "resources": {"listChanged": false}},
"serverInfo": {"name": "solvecraft", "title": "SolveCraft", "version": env!("CARGO_PKG_VERSION")},
"serverInfo": {"name": "solvecraft", "title": "SolidCraft", "version": env!("CARGO_PKG_VERSION")},
"instructions": format!("{INSTRUCTIONS} Backend: {}.", self.backend.describe()),
}))
}
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-render"
description = "SolveCraft view math (orbit camera, projection, picking rays) and a CPU rasterizer for headless snapshots"
description = "SolidCraft view math (orbit camera, projection, picking rays) and a CPU rasterizer for headless snapshots"
version.workspace = true
edition.workspace = true
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-sketch"
description = "SolveCraft sketches: points, curves, geometric constraints and dimensions, the constraint solver, and profile detection"
description = "SolidCraft sketches: points, curves, geometric constraints and dimensions, the constraint solver, and profile detection"
version.workspace = true
edition.workspace = true
license.workspace = true
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "solvecraft-ui-egui"
description = "SolveCraft user interface (egui + wgpu): the swappable front end"
description = "SolidCraft user interface (egui + wgpu): the swappable front end"
version.workspace = true
edition.workspace = true
license.workspace = true
+6 -6
View File
@@ -11,14 +11,14 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const COMMIT: &str = env!("SOLVECRAFT_COMMIT");
pub const BUILD_DATE: &str = env!("SOLVECRAFT_BUILD_DATE");
/// The user and developer guide (docs/book, published by .github/workflows/pages.yml).
pub const DOCS_URL: &str = "https://bherbruck.github.io/solvecraft/";
pub const DOCS_URL: &str = "https://bherbruck.github.io/solidcraft/";
const ATTRIBUTION: &str = include_str!("../../../ATTRIBUTION.md");
use solvecraft_engine::licences::{LICENSE_APACHE, LICENSE_MIT, NOTICE, THIRD_PARTY};
/// Fonts: what this build carries.
pub const FONTS: &str = "The interface uses egui's built-in fonts (Ubuntu Light and Hack, under the Ubuntu Font Licence and \
the MIT/Bitstream Vera licence; Noto Emoji and the emoji icon font, under the OFL and MIT). Sketch text uses Liberation Sans \
(SIL OFL 1.1). The Crafting Apps' optional font pack, craft-fonts (BIZ UDPGothic, Shippori Mincho, BIZ UDMincho, Noto Sans \
CJK SC, Noto Sans Arabic; SIL OFL 1.1), is not bundled in SolveCraft builds.";
CJK SC, Noto Sans Arabic; SIL OFL 1.1), is not bundled in SolidCraft builds.";
#[derive(Default)]
pub struct HelpState {
@@ -32,7 +32,7 @@ pub struct HelpState {
pub fn diagnostics(app: &SolveApp) -> String {
let renderer = if app.viewport.gpu.is_some() { "GPU" } else { "CPU" };
format!(
"SolveCraft {VERSION} ({COMMIT}, built {BUILD_DATE})\nOS: {} {}\nRenderer: {renderer}{}\nWindow: {}\nDesign: {} ({} features, {} bodies)",
"SolidCraft {VERSION} ({COMMIT}, built {BUILD_DATE})\nOS: {} {}\nRenderer: {renderer}{}\nWindow: {}\nDesign: {} ({} features, {} bodies)",
std::env::consts::OS,
std::env::consts::ARCH,
app.help.gpu.as_ref().map(|g| format!(", {g}")).unwrap_or_default(),
@@ -71,7 +71,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context, anchor: egui::Pos2) {
crate::context_menu::menu_frame(ui, |ui| {
for (id, label) in [
("docs", "Documentation"),
("about", "About SolveCraft"),
("about", "About SolidCraft"),
("shortcuts", "Keyboard Shortcuts"),
("report", "Report an Issue…"),
] {
@@ -100,12 +100,12 @@ fn about(app: &mut SolveApp, ctx: &egui::Context) {
}
let t = Tokens::get();
let mut open = true;
crate::frame::window(ctx, "About SolveCraft", crate::frame::Width::Normal)
crate::frame::window(ctx, "About SolidCraft", crate::frame::Width::Normal)
.id(egui::Id::new("sc_about"))
.open(&mut open)
.default_size(vec2(440.0, 560.0))
.show(ctx, |ui| {
ui.label(RichText::new("SolveCraft").size(22.0).strong());
ui.label(RichText::new("SolidCraft").size(22.0).strong());
ui.label(format!("Version {VERSION} · commit {COMMIT} · built {BUILD_DATE}"));
ui.label(RichText::new("Open-source parametric 3D CAD in Rust. Licensed MIT OR Apache-2.0.").color(t.text_dim));
ui.add_space(6.0);
+1 -1
View File
@@ -343,7 +343,7 @@ pub fn show(app: &mut SolveApp, ui: &mut egui::Ui) {
egui::CentralPanel::default().frame(egui::Frame::NONE.fill(t.panel).inner_margin(egui::Margin::symmetric(36, 24))).show(ui, |ui| {
egui::ScrollArea::vertical().auto_shrink([false, false]).show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(RichText::new("SolveCraft").size(26.0).strong().color(t.text));
ui.label(RichText::new("SolidCraft").size(26.0).strong().color(t.text));
ui.add_space(12.0);
ui.label(RichText::new("Start").size(16.0).color(t.text_dim));
});
+11
View File
@@ -192,6 +192,17 @@ pub fn paint(p: &Painter, r: Rect, name: &str, ink: Color32, fill: Color32, acce
pen.poly(&[(4.0, 7.5), (12.0, 12.0), (12.0, 21.0), (4.0, 16.5)], a, ink);
pen.poly(&[(12.0, 12.0), (20.0, 7.5), (20.0, 16.5), (12.0, 21.0)], Color32::TRANSPARENT, ink);
}
// Original bug glyph: rounded body, antennae and three pairs of legs.
"bug_report" => {
pen.circle(12.0, 13.5, 5.0, Color32::TRANSPARENT, ink);
pen.line(&[(12.0, 9.0), (12.0, 18.0)], ink);
pen.line(&[(9.5, 8.5), (8.0, 5.0)], ink);
pen.line(&[(14.5, 8.5), (16.0, 5.0)], ink);
for y in [10.0, 13.5, 17.0] {
pen.line(&[(3.0, y), (7.0, y)], ink);
pen.line(&[(17.0, y), (21.0, y)], ink);
}
}
// Help: a question mark in a ring.
"help" => {
pen.circle(12.0, 12.0, 9.0, Color32::TRANSPARENT, ink);
+2 -2
View File
@@ -10,7 +10,7 @@ use crate::{SolveApp, dialogs, help};
const MAX_BODY: usize = 24_000;
const MAX_IMAGE_PIXELS: usize = 16_000_000;
const ISSUE_URL: &str = "https://github.com/bherbruck/solvecraft/issues/new";
const ISSUE_URL: &str = "https://github.com/bherbruck/solidcraft/issues/new";
thread_local! {
static CAD_INPUT_BLOCKED: std::cell::Cell<bool> = const { std::cell::Cell::new(false) };
@@ -480,7 +480,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
if ui.add_enabled(draft.image.is_some() && app.services.pick_save.is_some(), egui::Button::new("Save screenshot…")).clicked()
&& let Some(image) = draft.image.as_ref()
&& let Some(path) =
app.services.pick_save.as_ref().and_then(|pick| pick(&format!("solvecraft-issue-{}.png", app.shot_token), &["png"]))
app.services.pick_save.as_ref().and_then(|pick| pick(&format!("solidcraft-issue-{}.png", app.shot_token), &["png"]))
{
let result = crate::control::save_screenshot(image, Some(&path));
draft.note = if result.get("ok").and_then(serde_json::Value::as_bool) == Some(true) {
+2 -2
View File
@@ -87,7 +87,7 @@ pub fn command_for(app: &SolveApp, key: &str) -> Option<&'static str> {
pub fn bind(app: &mut SolveApp, id: &str, key: &str, replace: bool) -> Result<(), String> {
let spec = solvecraft_engine::find_command(id).ok_or_else(|| format!("no command `{id}`"))?;
if RESERVED.contains(&key) {
return Err(format!("{key} is used by SolveCraft itself"));
return Err(format!("{key} is used by SolidCraft itself"));
}
if !key.is_empty() {
let others: Vec<&'static str> = commands_for(app, key).into_iter().filter(|o| *o != id).collect();
@@ -224,7 +224,7 @@ pub fn page(app: &mut SolveApp, ui: &mut egui::Ui) {
Some((_, "reset_all")) => reset(app, None),
Some((_, "export")) => {
let text = crate::toolbar_custom::export(app).to_string();
if let Some(path) = app.services.pick_save.as_ref().and_then(|f| f("SolveCraft shortcuts.json", &["json"])) {
if let Some(path) = app.services.pick_save.as_ref().and_then(|f| f("SolidCraft shortcuts.json", &["json"])) {
app.keymap.message = std::fs::write(&path, text).err().map(|e| format!("export: {e}"));
}
}
+1 -1
View File
@@ -16,7 +16,7 @@
"sketch.make_selection": "選択を{linetype}にする",
"ui.1_degree_of_freedom": "自由度 1",
"ui.3d_sketch": "3D スケッチ",
"ui.about": "SolveCraft について",
"ui.about": "SolidCraft について",
"ui.activate": "アクティブ化",
"ui.active_component": "アクティブなコンポーネント",
"ui.agent_cursor": "AIエージェントカーソル",
+1 -1
View File
@@ -616,7 +616,7 @@ fn page(app: &mut SolveApp, ui: &mut egui::Ui, t: &Tokens) {
}
});
ui.end_row();
note(ui, "Anti-aliasing applies the next time SolveCraft starts");
note(ui, "Anti-aliasing applies the next time SolidCraft starts");
ui.label("Renderer");
ui.label(match (&app.viewport.gpu, &app.help.gpu) {
(Some(_), Some(g)) => format!("GPU: {g}"),
+1 -1
View File
@@ -63,7 +63,7 @@ pub fn show(app: &mut SolveApp, ctx: &egui::Context) {
.min_width(420.0)
.anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
.show(ctx, |ui| {
ui.label("SolveCraft closed before these designs were saved. Their last autosave can be opened.");
ui.label("SolidCraft closed before these designs were saved. Their last autosave can be opened.");
ui.add_space(6.0);
for e in app.recoverable.iter().take(20) {
ui.label(RichText::new(&e.name).strong());
+142 -6
View File
@@ -36,6 +36,13 @@ pub fn promoted(tab: &str, panel: &str) -> usize {
}
}
fn report_rect(bar: Rect, captions: f32, tabs_start: f32) -> (Rect, bool) {
let right = bar.right() - captions - 90.0;
let labeled = right - tabs_start >= 340.0;
let width = if labeled { 124.0 } else { 28.0 };
(Rect::from_min_size(pos2(right - width, bar.top() + 4.0), vec2(width, 26.0)), labeled)
}
pub fn app_bar(app: &mut SolveApp, ui: &mut egui::Ui) {
let t = Tokens::get();
egui::Panel::top("sc_appbar").exact_size(34.0).show_separator_line(false).frame(egui::Frame::NONE.fill(t.app_bar)).show(ui, |ui| {
@@ -45,14 +52,20 @@ pub fn app_bar(app: &mut SolveApp, ui: &mut egui::Ui) {
crate::titlebar::drag_area(ui, r);
}
let left = r.left() + if app.integrated_titlebar { crate::titlebar::TRAFFIC_LIGHTS } else { 0.0 };
// At high UI scale, keep the report, Home, Help and window controls reachable.
// The theme shortcut remains available in Preferences.
let compact_actions = r.right() - left < 640.0;
// Brand mark: a small solid block drawn in code.
let logo = Rect::from_center_size(pos2(left + 20.0, r.center().y), vec2(20.0, 20.0));
icons::paint(ui.painter(), logo, "box", Color32::from_rgb(220, 226, 236), Color32::from_rgb(90, 160, 240), Color32::WHITE);
ui.painter().text(pos2(left + 36.0, r.center().y), Align2::LEFT_CENTER, "SolveCraft", FontId::proportional(14.0), t.app_bar_text);
let mut x = left + 128.0;
if !compact_actions {
ui.painter().text(pos2(left + 36.0, r.center().y), Align2::LEFT_CENTER, "SolidCraft", FontId::proportional(14.0), t.app_bar_text);
}
let mut x = left + if compact_actions { 60.0 } else { 128.0 };
let mut click = |ui: &mut egui::Ui, icon: &str, tip: &str| -> bool {
let br = Rect::from_center_size(pos2(x, r.center().y), vec2(26.0, 26.0));
x += 30.0;
let width = if compact_actions { 22.0 } else { 26.0 };
let br = Rect::from_center_size(pos2(x, r.center().y), vec2(width, 26.0));
x += if compact_actions { 22.0 } else { 30.0 };
let resp = ui.interact(br, ui.id().with(("ab", icon)), Sense::click());
crate::scenario::publish_handle(&format!("appbar:{icon}"), br.center());
if resp.hovered() {
@@ -83,7 +96,7 @@ pub fn app_bar(app: &mut SolveApp, ui: &mut egui::Ui) {
app.home.show(rev);
}
let theme_tip = if app.ui.dark { app.tr("ui.light") } else { app.tr("ui.dark") };
if click(ui, if app.ui.dark { "sun" } else { "moon" }, theme_tip) {
if !compact_actions && click(ui, if app.ui.dark { "sun" } else { "moon" }, theme_tip) {
app.ui.dark = !app.ui.dark;
app.preferences.theme = if app.ui.dark { "dark" } else { "light" }.into();
}
@@ -109,7 +122,34 @@ pub fn app_bar(app: &mut SolveApp, ui: &mut egui::Ui) {
}
// Document tabs.
let captions = if app.custom_titlebar { crate::titlebar::WIDTH } else { 0.0 };
crate::documents::tabs(app, ui, r, x + 16.0, r.right() - captions - 90.0);
let tabs_start = x + 16.0;
let (report, report_label) = report_rect(r, captions, tabs_start);
let tabs_end = report.left() - 8.0;
// Narrow title bars can fit only part of the tab strip. Its hit regions must not
// extend into the persistent report and window controls.
let tabs_rect = Rect::from_min_max(pos2(tabs_start.min(tabs_end), r.top()), pos2(tabs_end, r.bottom()));
let mut tabs_ui = ui.new_child(egui::UiBuilder::new().max_rect(tabs_rect));
tabs_ui.set_clip_rect(tabs_rect.intersect(ui.clip_rect()));
crate::documents::tabs(app, &mut tabs_ui, r, tabs_start, tabs_end);
let response = ui.interact(report, ui.id().with("sc_report_button"), Sense::click());
response.widget_info(|| egui::WidgetInfo::labeled(egui::WidgetType::Button, ui.is_enabled(), "Report a bug"));
crate::scenario::publish_handle("titlebar:report", report.center());
ui.painter().rect_filled(report, 4.0, if response.hovered() || response.has_focus() { t.hover } else { t.app_bar });
ui.painter().rect_stroke(report, 4.0, Stroke::new(1.0, t.border), egui::StrokeKind::Inside);
let icon = Rect::from_center_size(pos2(report.left() + 14.0, report.center().y), vec2(18.0, 18.0));
icons::paint(ui.painter(), icon, "bug_report", t.app_bar_text, t.app_bar_text, t.app_bar);
if report_label {
ui.painter().text(
pos2(report.left() + 29.0, report.center().y),
Align2::LEFT_CENTER,
"Report a bug",
FontId::proportional(13.0),
t.app_bar_text,
);
}
if response.on_hover_text("Report a bug — review a screenshot and open a GitHub draft").clicked() {
crate::help::run(app, &ui.ctx().clone(), "report");
}
// Preferences, at the top right like Fusion's profile menu (also Ctrl+,).
let gear = Rect::from_center_size(pos2(r.right() - captions - 70.0, r.center().y), vec2(24.0, 24.0));
let gresp = ui.interact(gear, ui.id().with("sc_prefs_button"), Sense::click());
@@ -612,3 +652,99 @@ pub fn shortcuts(app: &mut SolveApp, ctx: &egui::Context) {
}
}
}
#[cfg(test)]
mod header_report_tests {
use super::*;
fn captured_frame(h: &mut crate::scenario::Harness) {
let token = h.app.shot_token;
crate::issue_report::receive(&mut h.app, token, std::sync::Arc::new(egui::ColorImage::new([2, 2], vec![Color32::GREEN; 4])));
h.frames(3);
}
#[test]
fn header_report_reserves_controls_at_normal_and_double_scale_widths() {
for physical_width in [900.0, 1400.0] {
for scale in [1.0, 2.0] {
let width = physical_width / scale;
let bar = Rect::from_min_size(egui::Pos2::ZERO, vec2(width, 34.0));
for captions in [0.0, crate::titlebar::WIDTH] {
let (report, labeled) = report_rect(bar, captions, 354.0);
assert!(bar.contains_rect(report));
let prefs = Rect::from_center_size(pos2(width - captions - 70.0, 17.0), vec2(24.0, 24.0));
assert!(!report.intersects(prefs));
assert!(report.right() < width - captions - 82.0);
assert_eq!(labeled, width - captions - 90.0 - 354.0 >= 340.0);
}
}
}
}
#[test]
fn header_report_actual_click_opens_capture_without_changing_healthy_design() {
for width in [450.0, 700.0, 900.0, 1400.0] {
let mut h = crate::scenario::Harness::new();
h.app.home.open = false;
h.app.custom_titlebar = true;
h.app.run("solid.box", json!({"length":35,"width":12,"height":4})).unwrap();
h.call("ui.resize", json!({"width":width,"height":720}));
h.frames(3);
let before = (h.app.session.doc.to_json(), h.app.session.undo.len(), h.app.session.revision);
let point = h.widget("Report a bug").expect("compact and full buttons retain an accessible name");
h.step(&json!({"click":point})).unwrap();
assert!(h.app.issue_report.draft.is_some(), "width {width}: header click must reach the report");
assert!(crate::issue_report::pending(&h.app));
assert_eq!(before, (h.app.session.doc.to_json(), h.app.session.undo.len(), h.app.session.revision));
captured_frame(&mut h);
// Existing review content scrolls vertically when the window is narrow.
if width < 700.0 {
let [x, y] = h.widget("Close draft").unwrap();
h.call("ui.scroll", json!({"x":x,"y":y - 60.0,"delta":-1200}));
// Let the scroll animation finish before locating the click target.
h.frames(60);
}
h.step(&json!({"widget":"Close draft"})).unwrap();
assert!(h.app.issue_report.draft.is_none(), "width {width}: review closes through its actual button");
h.step(&json!({"click":{"handle":"appbar:home"}})).unwrap();
assert!(h.app.home.open, "width {width}: Home remains reachable beside Report");
assert_eq!(before, (h.app.session.doc.to_json(), h.app.session.undo.len(), h.app.session.revision));
}
}
#[test]
fn header_report_preserves_new_dimension_draft_and_single_commit() {
crate::dim_view::cancel();
let mut h = crate::scenario::Harness::new();
h.app.home.open = false;
h.app.run("sketch.create", json!({"plane":"XY"})).unwrap();
h.app.run("sketch.line", json!({"points":[[0,0],[20,0]]})).unwrap();
h.call("ui.view", json!({"view":"top"}));
h.app.cam.distance = 80.0;
h.app.cam.target = solvecraft_engine::geom::Vec3::new(10.0, 0.0, 0.0);
h.settle();
h.step(&json!({"start":"sketch.dimension"})).unwrap();
for point in [[0, 0, 0], [20, 0, 0], [10, 5, 0]] {
h.step(&json!({"click":{"world":point}})).unwrap();
}
h.step(&json!({"text":"30 mm"})).unwrap();
let before = (h.app.session.doc.to_json(), h.app.session.undo.len(), h.app.session.revision);
h.step(&json!({"widget":"Report a bug"})).unwrap();
h.frames(3);
assert!(h.app.issue_report.draft.is_some() && crate::dim_view::editing());
captured_frame(&mut h);
h.ctx.memory_mut(|m| m.request_focus(egui::Id::new("issue_report_body")));
h.step(&json!({"text":"Describe this dimension problem."})).unwrap();
h.step(&json!({"key":"Enter"})).unwrap();
assert_eq!(before, (h.app.session.doc.to_json(), h.app.session.undo.len(), h.app.session.revision));
h.step(&json!({"widget":"Close draft"})).unwrap();
h.frames(2);
assert_eq!(h.ctx.memory(|m| m.focused()), Some(egui::Id::new("sc_dim_edit")));
h.step(&json!({"key":"Enter"})).unwrap();
assert!(!crate::dim_view::editing());
assert_eq!(h.app.session.undo.len(), before.1 + 1);
assert_eq!(h.app.session.doc.param("d1").unwrap().expr, "30 mm");
h.app.run("edit.undo", json!({})).unwrap();
assert_eq!(h.app.session.doc.to_json(), before.0);
}
}
+1 -1
View File
@@ -177,7 +177,7 @@ pub fn export(app: &SolveApp) -> Value {
pub fn import(app: &mut SolveApp, v: &Value) -> Result<(), String> {
if v.get("format").and_then(Value::as_str) != Some("solvecraft-shortcuts/1") {
return Err("not a SolveCraft shortcuts file".into());
return Err("not a SolidCraft shortcuts file".into());
}
if let Some(s) = v.get("shortcuts") {
app.keymap.load(s);
@@ -1,9 +1,9 @@
[
{"note": "Help ▸ About ▸ Licences shows SolveCraft's licences and every third-party crate's, built into the program."},
{"note": "Help ▸ About ▸ Licences shows SolidCraft's licences and every third-party crate's, built into the program."},
{"call": "ui.help", "params": {"item": "about"}},
{"widget": "Licences"},
{"expect": {"shown": "Third-party licences"}},
{"widget": "Third-party licences"},
{"expect": {"shown": "Copy all licences"}},
{"expect": {"shown": "Third-party software in SolveCraft"}}
{"expect": {"shown": "Third-party software in SolidCraft"}}
]
+9 -9
View File
@@ -1,7 +1,7 @@
[book]
title = "SolveCraft"
title = "SolidCraft"
description = "An open-source parametric 3D CAD application in pure Rust: user guide and developer guide."
authors = ["SolveCraft contributors"]
authors = ["SolidCraft contributors"]
language = "en"
src = "src"
@@ -10,9 +10,9 @@ build-dir = "../../target/book"
create-missing = false
[output.html]
git-repository-url = "https://github.com/bherbruck/solvecraft"
edit-url-template = "https://github.com/bherbruck/solvecraft/edit/main/docs/book/{path}"
site-url = "/solvecraft/"
git-repository-url = "https://github.com/bherbruck/solidcraft"
edit-url-template = "https://github.com/bherbruck/solidcraft/edit/main/docs/book/{path}"
site-url = "/solidcraft/"
no-section-label = false
[output.html.search]
@@ -23,7 +23,7 @@ enable = true
# links; these send them to the right place.
"/dev/mcp.html" = "../guide/mcp.html"
"/dev/docs/oracle.html" = "../oracle.html"
"/dev/README.html" = "https://github.com/bherbruck/solvecraft/blob/main/README.md"
"/dev/ATTRIBUTION.html" = "https://github.com/bherbruck/solvecraft/blob/main/ATTRIBUTION.md"
"/dev/AGENTS.html" = "https://github.com/bherbruck/solvecraft/blob/main/AGENTS.md"
"/dev/ROADMAP.html" = "https://github.com/bherbruck/solvecraft/blob/main/ROADMAP.md"
"/dev/README.html" = "https://github.com/bherbruck/solidcraft/blob/main/README.md"
"/dev/ATTRIBUTION.html" = "https://github.com/bherbruck/solidcraft/blob/main/ATTRIBUTION.md"
"/dev/AGENTS.html" = "https://github.com/bherbruck/solidcraft/blob/main/AGENTS.md"
"/dev/ROADMAP.html" = "https://github.com/bherbruck/solidcraft/blob/main/ROADMAP.md"
+1 -1
View File
@@ -14,7 +14,7 @@
- [Plastic parts](guide/plastic.md)
- [Import and export](guide/files.md)
- [Keyboard shortcuts](guide/shortcuts.md)
- [Driving SolveCraft with an AI agent (MCP)](guide/mcp.md)
- [Driving SolidCraft with an AI agent (MCP)](guide/mcp.md)
# Developer guide
+2 -2
View File
@@ -1,6 +1,6 @@
# Architecture
SolveCraft is a Cargo workspace in layers. A crate only depends on crates in lower layers, and
SolidCraft is a Cargo workspace in layers. A crate only depends on crates in lower layers, and
`cargo xtask layers` fails the build otherwise.
{{#include ../../../../README.md:architecture}}
@@ -11,7 +11,7 @@ The web app, `apps/solvecraft-web`, is the desktop front end compiled to wasm wi
A few rules follow from the layering:
- **Only `crates/kernel` touches truck.** Everything above it sees SolveCraft's own types:
- **Only `crates/kernel` touches truck.** Everything above it sees SolidCraft's own types:
bodies, faces, edges, meshes and measures. See [The kernel boundary](kernel.md).
- **The document knows nothing about commands or the UI.** `crates/doc` holds the parameters,
the feature timeline and the component tree, and evaluates them into a model.
+3 -3
View File
@@ -1,9 +1,9 @@
# The kernel boundary and truck
`crates/kernel` is SolveCraft's B-rep kernel. It wraps
`crates/kernel` is SolidCraft's B-rep kernel. It wraps
[truck](https://github.com/ricosjp/truck) (Apache-2.0) for topology, NURBS geometry, booleans,
tessellation and STEP parsing, and it is the only crate allowed to use truck. Callers get
SolveCraft types (`Body`, face and edge indices, meshes, measures) and a
SolidCraft types (`Body`, face and edge indices, meshes, measures) and a
`KernelError` instead of truck types and panics.
## `guard`
@@ -20,7 +20,7 @@ A failing feature shows an error on the timeline; it never takes the app down.
## What is our own
truck has no fillets, chamfers, shells or face edits, so these are SolveCraft code in the
truck has no fillets, chamfers, shells or face edits, so these are SolidCraft code in the
kernel: constant, chord and variable-radius fillets with corner blends, chamfers, shell and
offset, Delete Face, Split Face, Replace Face, sewing, STEP and IGES reading and writing beyond
truck's parser, and face provenance (which input face each output face came from, for
+1 -1
View File
@@ -2,7 +2,7 @@
A fillet stores the edges it rounds, and a sketch stores the face it sits on. If those were face
and edge *indices*, an upstream edit that adds a face would move the fillet to the wrong edge.
SolveCraft stores **names** instead (`crates/doc/src/naming.rs`):
SolidCraft stores **names** instead (`crates/doc/src/naming.rs`):
- A face of an evaluated body is named after how it was made. A face lying on a face of the
feature's input keeps that face's name; a face new to the feature is named by its role:
+3 -3
View File
@@ -8,7 +8,7 @@ faded.
A component is placed by its **occurrences**. Paste creates another instance of the same
component, and **Paste New** creates an independent copy. Move an occurrence with the move
triad, then **Capture Position** to keep it or **Revert** to put it back. SolveCraft asks before
triad, then **Capture Position** to keep it or **Revert** to put it back. SolidCraft asks before
the next command if a move wasn't captured. **Ground** fixes an occurrence in place.
Paste New copies nested component definitions, sketches, feature histories and generated
@@ -16,7 +16,7 @@ dimension parameters. Repeated child instances remain shared within the new copy
appearances and wholly internal joints/motion records receive copied references. Design-wide
user parameters remain shared; retained external links and omitted cross-boundary motion
records are reported. Unsupported destructive links refuse the entire copy. See
[individual-instance and copy details](https://github.com/bherbruck/solvecraft/blob/main/docs/occurrence-workflows.md).
[individual-instance and copy details](https://github.com/bherbruck/solidcraft/blob/main/docs/occurrence-workflows.md).
**Joints** (`joint.create`, J) connect two components by joint origins that snap to faces, edges,
circles and vertices: rigid, revolute, slider, cylindrical, pin-slot, planar and ball, with
@@ -35,4 +35,4 @@ uses its first placement; `occurrence.properties` selects a specific instance. R
measures the complete assembly. Volumes are summed, including overlapping bodies, rather than
measuring their union. The same workflow is available through `component.properties` and
`occurrence.properties`; see
[placement and measurement details](https://github.com/bherbruck/solvecraft/blob/main/docs/assembly-properties.md).
[placement and measurement details](https://github.com/bherbruck/solidcraft/blob/main/docs/assembly-properties.md).
+7 -3
View File
@@ -1,13 +1,17 @@
# Getting started
SolidCraft keeps its compatibility names: the desktop executable is `solvecraft`, the CLI
is `solvecraft-cli`, and saved designs remain `.solvecraft`. Published 0.2.0 and 0.2.1 packages
were released under the SolveCraft name.
## Desktop
Download the latest build from the
[releases page](https://github.com/bherbruck/solvecraft/releases), or build it yourself with
[releases page](https://github.com/bherbruck/solidcraft/releases), or build it yourself with
stable Rust (1.90 or newer):
```sh
git clone https://github.com/bherbruck/solvecraft && cd solvecraft
git clone https://github.com/bherbruck/solidcraft && cd solidcraft
cargo run --release -p solvecraft -- --sample # opens with a sample part
```
@@ -17,7 +21,7 @@ cargo run --release -p solvecraft -- --sample # opens with a sample part
## Web
The same app runs in the browser: [open SolveCraft on the web](../app/) (`?sample` opens the sample
The same app runs in the browser: [open SolidCraft on the web](../app/) (`?sample` opens the sample
part, `?webgl` forces WebGL2 where WebGPU is missing). Designs are saved in the browser's private
file storage, which needs HTTPS or localhost. To build it yourself:
+1 -1
View File
@@ -22,4 +22,4 @@ suppressed features, physical materials and body appearances, switched with `con
The table's Library picker selects material or appearance values. Choose **Follow material**
to clear an appearance override. Custom looks retain their colour and opacity across
activation, undo and save/reopen. See
[configuration commands and examples](https://github.com/bherbruck/solvecraft/blob/main/docs/configurations.md).
[configuration commands and examples](https://github.com/bherbruck/solidcraft/blob/main/docs/configurations.md).
+1 -1
View File
@@ -85,7 +85,7 @@ fresh link once the source is available; their stored point values are preserved
including the origin, endpoints and circle centres. For two points, it chooses horizontal
or vertical alignment from their current positions. Esc cancels a pending first point.
The solver is SolveCraft's own (damped Gauss–Newton with degree-of-freedom analysis). Constraints:
The solver is SolidCraft's own (damped Gauss–Newton with degree-of-freedom analysis). Constraints:
coincident, horizontal/vertical, parallel, perpendicular, tangent, equal, concentric, collinear,
midpoint, symmetry, fix, curvature (G2) and polygon. **Sketch Dimension** (D) adds linear,
aligned, horizontal, vertical, radius, diameter and angle dimensions. Each is a parameter
+2 -2
View File
@@ -1,6 +1,6 @@
# A tour of the window
![The SolveCraft window](../generated/sample-plate.png)
![The SolidCraft window](../generated/sample-plate.png)
- **Title bar.** On Windows and Linux the application bar is the window's title bar: File, save,
undo and redo, the start page, the theme, Help, and one tab per open design (each with its own
@@ -16,7 +16,7 @@
wheel zooms at the cursor, the middle button pans and right-drag orbits freely (the mouse
preset in Preferences switches to SolidWorks or Inventor style). Hold Shift during an orbit
to snap to the nearest standard or diagonal view; the default Shift+middle gesture also snaps.
The axis view shows the world's X/Y/Z directions in SolveCraft's colors. Click a signed
The axis view shows the world's X/Y/Z directions in SolidCraft's colors. Click a signed
axis tip to look along that axis; click the aligned tip again to look from the opposite side.
Drag the axis view to orbit around the view centre, with Shift for snapping. Top and bottom
keep the nearest quarter-turn heading. The house and navigation bar's Home button go home.
+9 -7
View File
@@ -1,11 +1,11 @@
# SolveCraft
# SolidCraft
SolveCraft is an open-source parametric 3D CAD application written in pure Rust. It works the way
SolidCraft is an open-source parametric 3D CAD application written in pure Rust. It works the way
Fusion-style modellers do: sketch on a plane, constrain and dimension the sketch, turn profiles
into solids with features on a timeline, and change a parameter to watch the whole timeline
rebuild.
![The sample plate in SolveCraft](generated/sample-plate.png)
![The sample plate in SolidCraft](generated/sample-plate.png)
It runs as:
@@ -18,15 +18,17 @@ Everything the user can do is a *command* with JSON parameters. The toolbar, the
the CLI, the control channel and MCP all run the same commands, so anything you can click you can
also script, test or hand to an agent.
SolveCraft is early. Sketching, solid modelling, parameters, components with joints, sheet metal
SolidCraft is early. Sketching, solid modelling, parameters, components with joints, sheet metal
and a plastic-enclosure subset work; surfaces, drawings, CAM and simulation are deferred. The
[roadmap](https://github.com/bherbruck/solvecraft/blob/main/ROADMAP.md) has the details, and
[roadmap](https://github.com/bherbruck/solidcraft/blob/main/ROADMAP.md) has the details, and
[command parity](dev/parity.md) and the [Fusion oracle](dev/oracle.md) measure how far along it
is.
This book has two halves: the **user guide** for people modelling with SolveCraft, and the
Existing `.solvecraft` designs and `solvecraft-cli` commands keep their names and behavior.
This book has two halves: the **user guide** for people modelling with SolidCraft, and the
**developer guide** for people working on it. The [command reference](reference/commands.md) is
generated from the code on every build.
SolveCraft is licensed MIT OR Apache-2.0. It is a clean-room implementation and is not affiliated
SolidCraft is licensed MIT OR Apache-2.0. It is a clean-room implementation and is not affiliated
with Autodesk; Fusion is a trademark of Autodesk, Inc.
+1 -1
View File
@@ -149,5 +149,5 @@ the current rule scope.
Newly saved designs use file format 12, including optional durable face attachments and
analytic offset boundary identities and qualified adaptive whole-profile references, including
holes. Older designs upgrade on opening without
changing their existing configuration values. Older SolveCraft versions must be updated to
changing their existing configuration values. Older SolidCraft versions must be updated to
open newly saved designs.
+1 -1
View File
@@ -1,6 +1,6 @@
# IGES import (Fusion corpus)
Generated by `cargo xtask step-corpus --iges`. Each case is a reference part exported from Fusion as IGES 5.3 (kept locally in `plan/fusion/oracle/`, never committed). SolveCraft opens `part.igs` exactly as File → Open does and compares with Fusion's measurements of the part it exported: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file. How IGES is read: see [STEP import](step-import.md#iges). 50-sweep-tight-bend is written by Fusion as five loose, partly degenerate surfaces (seams running into slits, a ring given twice); Fusion cannot read it back as a solid either.
Generated by `cargo xtask step-corpus --iges`. Each case is a reference part exported from Fusion as IGES 5.3 (kept locally in `plan/fusion/oracle/`, never committed). SolidCraft opens `part.igs` exactly as File → Open does and compares with Fusion's measurements of the part it exported: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file. How IGES is read: see [STEP import](step-import.md#iges). 50-sweep-tight-bend is written by Fusion as five loose, partly degenerate surfaces (seams running into slits, a ring given twice); Fusion cannot read it back as a solid either.
**75 / 76 files pass.**
+1 -1
View File
@@ -1,6 +1,6 @@
# UI localization
SolveCraft embeds its translations and works offline. English and Japanese are currently
SolidCraft embeds its translations and works offline. English and Japanese are currently
registered in `crates/ui-egui/src/i18n.rs`. Adding a language requires an embedded catalog and
one registration there; the preferences selector and validation use the same registry.
+2 -2
View File
@@ -1,6 +1,6 @@
# SolveCraft and AI agents (MCP)
# SolidCraft and AI agents (MCP)
SolveCraft speaks the [Model Context Protocol](https://modelcontextprotocol.io) over stdio
SolidCraft speaks the [Model Context Protocol](https://modelcontextprotocol.io) over stdio
(JSON-RPC 2.0, one message per line; protocol revisions 2025-06-18, 2025-03-26 and 2024-11-05).
An agent models exactly like a person does: every tool runs engine commands, the same ones the
toolbar, palette, scripts and control channel use, and never opens a dialog.
+6 -6
View File
@@ -1,6 +1,6 @@
# Fusion oracle
Generated by `cargo xtask oracle`. Each case is a part built in Fusion (recipe and measurements kept locally in `plan/fusion/oracle/`, not committed). SolveCraft replays the recipe through its command engine and compares body count, volume and area (relative tolerance 1e-3) and face/edge/vertex counts (exact). SolveCraft counts each closed-surface patch once and ignores seam edges and vertices; when body count, volume and area pass but the raw counts differ, Fusion's counts are normalised the same way from its face, edge and vertex lists (faces cut from one cylinder, cone, sphere or torus and separated only by seams count once; an edge on that surface and no other is a seam and is dropped; a vertex left with two edges joins them) and compared exactly; the note gives both sides' raw counts.
Generated by `cargo xtask oracle`. Each case is a part built in Fusion (recipe and measurements kept locally in `plan/fusion/oracle/`, not committed). SolidCraft replays the recipe through its command engine and compares body count, volume and area (relative tolerance 1e-3) and face/edge/vertex counts (exact). SolidCraft counts each closed-surface patch once and ignores seam edges and vertices; when body count, volume and area pass but the raw counts differ, Fusion's counts are normalised the same way from its face, edge and vertex lists (faces cut from one cylinder, cone, sphere or torus and separated only by seams count once; an edge on that surface and no other is a seam and is dropped; a vertex left with two edges joins them) and compared exactly; the note gives both sides' raw counts.
**76 / 76 cases pass** (0 need features not implemented yet).
@@ -40,7 +40,7 @@ Generated by `cargo xtask oracle`. Each case is a part built in Fusion (recipe a
| 32-boss-plate-fillets | pass | |
| 33-fillet-tangent-chain | pass | |
| 34-chamfer-then-fillet | pass | |
| 35-fillet-curved-curved | pass | topology compared seam-normalised: Fusion's 6/6/5 faces/edges/vertices become 6/5/5 with closed-surface patches counted once and seam edges and vertices dropped (raw SolveCraft/Fusion: faces 9/6, edges 17/6, vertices 10/5) |
| 35-fillet-curved-curved | pass | topology compared seam-normalised: Fusion's 6/6/5 faces/edges/vertices become 6/5/5 with closed-surface patches counted once and seam edges and vertices dropped (raw SolidCraft/Fusion: faces 9/6, edges 17/6, vertices 10/5) |
| 36-corner-blend-two-radii | pass | |
| 37-pyramid-apex-blend | pass | |
| 38-coincident-cylinders | pass | |
@@ -54,21 +54,21 @@ Generated by `cargo xtask oracle`. Each case is a part built in Fusion (recipe a
| 46-shell-two-open-faces | pass | |
| 47-draft-curved-pocket | pass | |
| 48-pulley | pass | |
| 49-helical-sweep | pass | Fusion sweeps along a spline fitted through helix points; SolveCraft builds the true helix (a coil), so the size is compared within 0.5% and the topology not at all |
| 49-helical-sweep | pass | Fusion sweeps along a spline fitted through helix points; SolidCraft builds the true helix (a coil), so the size is compared within 0.5% and the topology not at all |
| 50-sweep-tight-bend | pass | area not compared: Fusion reports this tight sweep's horn torus face at three times its area (π²·R·r quarter-turn value; 740 mm² against 246.7 by Pappus); volume and topology are compared |
| 51-loft-rect-circle-offset | pass | |
| 52-circular-pattern-fillet | pass | |
| 53-path-pattern | pass | |
| 54-mirror-body-fillets | pass | |
| 55-tapped-hole-cosmetic | pass | |
| 56-holes-on-curved-face | pass | topology compared seam-normalised: Fusion's 7/10/10 faces/edges/vertices become 7/8/8 with closed-surface patches counted once and seam edges and vertices dropped (raw SolveCraft/Fusion: faces 16/7, edges 32/10, vertices 19/10) |
| 56-holes-on-curved-face | pass | topology compared seam-normalised: Fusion's 7/10/10 faces/edges/vertices become 7/8/8 with closed-surface patches counted once and seam edges and vertices dropped (raw SolidCraft/Fusion: faces 16/7, edges 32/10, vertices 19/10) |
| 57-hole-pattern-sketch-points | pass | |
| 58-phone-stand | pass | |
| 59-enclosure-base | pass | |
| 60-pipe-flange | pass | |
| 61-shaft-keyway | pass | topology compared seam-normalised: Fusion's 14/22/18 faces/edges/vertices become 14/20/16 with closed-surface patches counted once and seam edges and vertices dropped (raw SolveCraft/Fusion: faces 24/14, edges 54/22, vertices 34/18) |
| 61-shaft-keyway | pass | topology compared seam-normalised: Fusion's 14/22/18 faces/edges/vertices become 14/20/16 with closed-surface patches counted once and seam edges and vertices dropped (raw SolidCraft/Fusion: faces 24/14, edges 54/22, vertices 34/18) |
| 62-heat-sink | pass | |
| 63-hinge-knuckle | pass | known Fusion difference: Gap replayed with the extent Fusion actually used (its through-all is sized as 1.1 × the farthest vertex, so curved tops stop it short; SolveCraft's through-all goes truly through) |
| 63-hinge-knuckle | pass | known Fusion difference: Gap replayed with the extent Fusion actually used (its through-all is sized as 1.1 × the farthest vertex, so curved tops stop it short; SolidCraft's through-all goes truly through) |
| 64-sketch-tangent-arc-chain | pass | |
| 65-sketch-gear-tooth | pass | |
| 66-sketch-linkage | pass | |
+1 -1
View File
@@ -1,6 +1,6 @@
# Fusion command parity
Generated by `cargo xtask parity` from `xtask/data/fusion-catalog.tsv` (the toolbar commands of Fusion's Design workspace, ids and names only) and the live command registry (`solvecraft-cli commands`). A command counts as live when SolveCraft registers the same command id. This measures breadth, not depth; see ROADMAP.md and docs/oracle.md for depth.
Generated by `cargo xtask parity` from `xtask/data/fusion-catalog.tsv` (the toolbar commands of Fusion's Design workspace, ids and names only) and the live command registry (`solvecraft-cli commands`). A command counts as live when SolidCraft registers the same command id. This measures breadth, not depth; see ROADMAP.md and docs/oracle.md for depth.
**In scope (SOLID, SKETCH, ASSEMBLY, SHEET METAL, PLASTIC enclosure subset): 335 / 541 commands live (62%).** SOLID + SKETCH: 188 / 291 (65%). Registered commands: 311.
+1 -1
View File
@@ -136,7 +136,7 @@ Pi case in 15.2 s and 10.5 s. During those jobs, median `ui.inspect` round trips
and 8.3 ms; the slowest were 33.7 ms and 28.6 ms. This fixes the window freeze, not the
remaining cost of cold geometry evaluation.
Recent thumbnails are 240 × 150 PNG files in `SolveCraft/thumbnails` under the platform
Recent thumbnails are 240 × 150 PNG files in `SolveCraft/thumbnails` under the platform
cache directory (`LOCALAPPDATA` on Windows). Their keys include the source path, file size
and modification time. Open and Save render from the model already in memory. Cached
recents decode the PNG without reading or rebuilding a document, and run before cold
+15 -3
View File
@@ -1,4 +1,4 @@
# Releasing SolveCraft
# Releasing SolidCraft
Releases are deliberate operations that an agent can complete end to end. Development lands
on `main`; pushing the selected commit to `release` builds all packages and creates a **draft**
@@ -6,6 +6,18 @@ GitHub Release. Publishing that verified draft is a separate operation. Pushing
pushing a tag does not start the release workflow. There is no automatic commit-message-based
version bump and no patch release for every merge.
## Compatibility names and deployment
SolidCraft is the product display name. Keep existing release asset filenames, including
`SolveCraft-{version}-windows-x64.exe`, `SolveCraft.app` and `solvecraft-cli` archives, together
with installed package IDs and settings paths. Do not rewrite published 0.2.0 or 0.2.1 assets,
checksums, tags or historical release notes.
Project Pages publishes the documentation, browser app and published-release Flatpak
repository under `/solidcraft/`. Do not add a separate owner-site publisher or permanent
old-name Pages/Flatpak aliases. GitHub repository redirects are separate from Pages hosting;
verify the new project URLs after deployment.
## Nightly prereleases
`release.yml` also runs at midnight in `America/New_York` (Eastern time, following daylight
@@ -42,7 +54,7 @@ deployment. The stable Flatpak repository is not automatically updated with nigh
2. Update `[workspace.package] version` in root `Cargo.toml`. Then run
`cargo metadata --format-version 1 --no-deps > /dev/null` to refresh workspace package
versions in `Cargo.lock`. Inspect the diff and keep unrelated dependency changes out.
SolveCraft does not currently implement the siblings' `cargo xtask version set` command.
SolidCraft does not currently implement the siblings' `cargo xtask version set` command.
3. Run `cargo xtask ci` and the packaging checks in `.github/workflows/packaging-lint.yml`.
Commit the version files through the normal review flow and land them on `main`.
Do not use `[skip ci]`. Record the exact commit SHA to release. If it changes, revalidate it.
@@ -68,7 +80,7 @@ fast-forward fails, inspect and resolve the divergence through the normal review
Find the `release.yml` Actions run for the recorded SHA and `release` branch, and monitor that
specific run. The workflow builds Linux (both architectures, including Flatpak), macOS,
Windows, FreeBSD, and web packages. Once every required job succeeds it creates a draft
`SolveCraft v<version>` with the artifacts, `SHA256SUMS.txt`, install notes, and generated changes.
`SolidCraft v<version>` with the artifacts, `SHA256SUMS.txt`, install notes, and generated changes.
A second push with the same untagged version can rebuild and replace that draft's complete
asset set. Existing release notes are preserved on rebuild; refresh their changes section if
+2 -2
View File
@@ -1,6 +1,6 @@
# STEP import
SolveCraft reads STEP (ISO 10303-21; AP203, AP214 and AP242 geometry) with its own reader in `crates/kernel/src/step_in` (written from the public standards; no third-party STEP code). A STEP file becomes an **Import** base feature on the timeline: it holds the file's B-rep (the STEP text is kept in the design, so the design reopens without the file) and later features — fillets, holes, cuts sketched on its faces, shells — build on its bodies.
SolidCraft reads STEP (ISO 10303-21; AP203, AP214 and AP242 geometry) with its own reader in `crates/kernel/src/step_in` (written from the public standards; no third-party STEP code). A STEP file becomes an **Import** base feature on the timeline: it holds the file's B-rep (the STEP text is kept in the design, so the design reopens without the file) and later features — fillets, holes, cuts sketched on its faces, shells — build on its bodies.
- **Open**: File → Open (`doc.open`) accepts `.step`/`.stp` in any case and starts a new design; `solvecraft part.step`; `solvecraft-cli eval part.step`, `solvecraft-cli run script.json --in part.step`; the MCP `open` tool.
- **Insert** into the current design: File → Insert STEP… or SOLID → INSERT (`file.insert_step {path}`).
@@ -14,7 +14,7 @@ IGES 5.3 files (`.igs`/`.iges`) open, insert and drop the same way: `crates/kern
## Fusion corpus
Generated by `cargo xtask step-corpus`. Each case is a part exported from Fusion as STEP (AP214; kept locally in `plan/fusion/oracle/`, never committed). SolveCraft opens `part.step` exactly as File → Open does (an Import base feature) and compares with Fusion's own measurements: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file.
Generated by `cargo xtask step-corpus`. Each case is a part exported from Fusion as STEP (AP214; kept locally in `plan/fusion/oracle/`, never committed). SolidCraft opens `part.step` exactly as File → Open does (an Import base feature) and compares with Fusion's own measurements: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file.
**76 / 76 files pass.**
+6 -6
View File
@@ -49,20 +49,20 @@ for arch in x86_64 aarch64; do
ostree --repo="$validation_repo" refs | grep -Fxq "app/$app_id/$arch/stable"
flatpak build-import-bundle "$repo_dir" "$bundles/$asset"
done
flatpak build-update-repo --title="SolveCraft" --comment='Official SolveCraft releases' \
--description='Official Flatpak repository for SolveCraft' \
flatpak build-update-repo --title="SolidCraft" --comment='Official SolidCraft releases' \
--description='Official Flatpak repository for SolidCraft' \
--homepage="https://github.com/$GITHUB_REPOSITORY" --default-branch=stable "$repo_dir"
test -s "$repo_dir/summary"
cat >"$site/flatpak/solvecraft.flatpakrepo" <<META
[Flatpak Repo]
Title=SolveCraft
Comment=Official SolveCraft releases
Description=Official Flatpak repository for SolveCraft
Title=SolidCraft
Comment=Official SolidCraft releases
Description=Official Flatpak repository for SolidCraft
Url=$base_url/flatpak/repo/
Homepage=https://github.com/$GITHUB_REPOSITORY
META
cat >"$site/flatpak/index.html" <<HTML
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>SolveCraft Flatpak repository</title></head><body><main><h1>SolveCraft Flatpak repository</h1><p>Latest published release: <strong>$tag</strong></p><p><a href="solvecraft.flatpakrepo">Add the SolveCraft repository</a></p><pre>flatpak remote-add --if-not-exists solvecraft $base_url/flatpak/solvecraft.flatpakrepo
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>SolidCraft Flatpak repository</title></head><body><main><h1>SolidCraft Flatpak repository</h1><p>Latest published release: <strong>$tag</strong></p><p><a href="solvecraft.flatpakrepo">Add the SolidCraft repository</a></p><pre>flatpak remote-add --if-not-exists solvecraft $base_url/flatpak/solvecraft.flatpakrepo
flatpak install solvecraft $app_id</pre></main></body></html>
HTML
touch "$site/.nojekyll"
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Build and package SolveCraft for FreeBSD:
# Build and package SolidCraft for FreeBSD:
#
# $DIST/solvecraft-<version>-freebsd-x86_64.tar.gz a /usr/local-style tree:
# solvecraft-<version>-freebsd-x86_64/{bin, share/applications, share/icons, share/mime,
@@ -32,7 +32,7 @@ case "$(uname -m)" in
*) echo "unsupported architecture $(uname -m)" >&2; exit 2 ;;
esac
BASENAME="solvecraft-$VERSION-freebsd-$ARCH"
echo "==> SolveCraft $VERSION for FreeBSD $ARCH"
echo "==> SolidCraft $VERSION for FreeBSD $ARCH"
# The release VM has 12 GB; full parallelism on the biggest crates runs it out of memory.
export CARGO_BUILD_JOBS="${CARGO_BUILD_JOBS:-4}"
+1 -1
View File
@@ -1,5 +1,5 @@
#!/bin/sh
# SolveCraft AppImage entry point (packaging/linux/AppRun; installed by package.sh).
# SolidCraft AppImage entry point (packaging/linux/AppRun; installed by package.sh).
# Adapted from PhotoCraft's AppRun (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
#
# On Wayland the compositor resolves a window's dock icon by taking the window's
@@ -1,6 +1,6 @@
[Desktop Entry]
Type=Application
Name=SolveCraft
Name=SolidCraft
GenericName=3D CAD
Comment=Parametric 3D CAD: sketch, constrain, extrude, and change a parameter to rebuild
Exec=solvecraft %f
@@ -5,14 +5,14 @@
<id>ai.storyteller.solvecraft</id>
<metadata_license>CC0-1.0</metadata_license>
<project_license>MIT OR Apache-2.0</project_license>
<name>SolveCraft</name>
<name>SolidCraft</name>
<summary>Parametric 3D CAD in the style of Fusion</summary>
<developer id="io.github.bherbruck">
<name>SolveCraft contributors</name>
<name>SolidCraft contributors</name>
</developer>
<description>
<p>
SolveCraft is an open-source parametric 3D CAD application written in Rust. Sketch on a
SolidCraft is an open-source parametric 3D CAD application written in Rust. Sketch on a
plane, constrain and dimension the sketch, turn profiles into solids on a feature
timeline, and change a parameter to watch the timeline rebuild.
</p>
@@ -26,9 +26,9 @@
</description>
<launchable type="desktop-id">ai.storyteller.solvecraft.desktop</launchable>
<icon type="stock">ai.storyteller.solvecraft</icon>
<url type="homepage">https://bherbruck.github.io/solvecraft/</url>
<url type="bugtracker">https://github.com/bherbruck/solvecraft/issues</url>
<url type="help">https://bherbruck.github.io/solvecraft/</url>
<url type="homepage">https://bherbruck.github.io/solidcraft/</url>
<url type="bugtracker">https://github.com/bherbruck/solidcraft/issues</url>
<url type="help">https://bherbruck.github.io/solidcraft/</url>
<categories>
<category>Graphics</category>
<category>3DGraphics</category>
@@ -42,7 +42,7 @@
<screenshots>
<screenshot type="default">
<caption>A sample part with its sketches and feature timeline</caption>
<image>https://raw.githubusercontent.com/bherbruck/solvecraft/main/docs/screenshots/sample-plate.png</image>
<image>https://raw.githubusercontent.com/bherbruck/solidcraft/main/docs/screenshots/sample-plate.png</image>
</screenshot>
</screenshots>
<requires>
@@ -3,7 +3,7 @@
Installed to /usr/share/mime/packages/ (update-mime-database runs on install). -->
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/x-solvecraft">
<comment>SolveCraft design</comment>
<comment>SolidCraft design</comment>
<icon name="ai.storyteller.solvecraft"/>
<sub-class-of type="application/json"/>
<glob pattern="*.solvecraft"/>
+2 -2
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Repackage a SolveCraft Linux tarball as a single-file Flatpak bundle:
# Repackage a SolidCraft Linux tarball as a single-file Flatpak bundle:
#
# $DIST/solvecraft-<version>-linux-<arch>.flatpak
#
@@ -43,7 +43,7 @@ for tool in flatpak flatpak-builder; do
command -v "$tool" >/dev/null || { echo "error: $tool not found (apt install flatpak flatpak-builder librsvg2-common)" >&2; exit 1; }
done
echo "==> SolveCraft $VERSION Flatpak bundle for $ARCH from $(basename "$TARBALL")"
echo "==> SolidCraft $VERSION Flatpak bundle for $ARCH from $(basename "$TARBALL")"
WORK="$CARGO_TARGET_DIR/flatpak-bundle"
rm -rf "$WORK"
+2 -2
View File
@@ -11,8 +11,8 @@ release: 1
section: graphics
priority: optional
maintainer: ${SOLVECRAFT_MAINTAINER}
vendor: SolveCraft contributors
homepage: https://bherbruck.github.io/solvecraft/
vendor: SolidCraft contributors
homepage: https://bherbruck.github.io/solidcraft/
license: MIT OR Apache-2.0
description: |-
Open-source parametric 3D CAD in the style of Fusion
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Build and package SolveCraft for Linux (<arch> is x86_64 or aarch64):
# Build and package SolidCraft for Linux (<arch> is x86_64 or aarch64):
#
# $DIST/solvecraft-<version>-linux-<arch>.AppImage any distro with glibc >= the build host's
# $DIST/solvecraft-<version>-linux-<arch>.AppImage.zsync delta updates (needs zsyncmake)
@@ -37,10 +37,10 @@ case "$ARCH" in
aarch64 | arm64) ARCH=aarch64; DEB_ARCH=arm64 ;;
*) echo "unsupported architecture $ARCH" >&2; exit 2 ;;
esac
export SOLVECRAFT_MAINTAINER="${SOLVECRAFT_MAINTAINER:-SolveCraft contributors <https://github.com/bherbruck/solvecraft>}"
export SOLVECRAFT_MAINTAINER="${SOLVECRAFT_MAINTAINER:-SolidCraft contributors <https://github.com/bherbruck/solidcraft>}"
BASENAME="solvecraft-$VERSION-linux-$ARCH"
echo "==> SolveCraft $VERSION for Linux $ARCH ($FORMATS)"
echo "==> SolidCraft $VERSION for Linux $ARCH ($FORMATS)"
if [ "$SKIP_BUILD" = 0 ]; then
if [ -n "${CROSS_TARGET:-}" ]; then
+3 -3
View File
@@ -7,9 +7,9 @@
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>SolveCraft</string>
<string>SolidCraft</string>
<key>CFBundleName</key>
<string>SolveCraft</string>
<string>SolidCraft</string>
<key>CFBundleExecutable</key>
<string>SolveCraft</string>
<key>CFBundleIdentifier</key>
@@ -41,7 +41,7 @@
<array>
<dict>
<key>CFBundleTypeName</key>
<string>SolveCraft design</string>
<string>SolidCraft design</string>
<key>CFBundleTypeExtensions</key>
<array><string>solvecraft</string></array>
<key>CFBundleTypeRole</key>
+3 -3
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env bash
# Build and package SolveCraft for macOS:
# Build and package SolidCraft for macOS:
#
# $DIST/solvecraft-<version>-macos-<arch>.dmg SolveCraft.app on a drag-to-Applications DMG
# $DIST/solvecraft-cli-<version>-macos-<arch>.zip the headless CLI with the licence files
@@ -38,7 +38,7 @@ APP="$WORK/SolveCraft.app"
DMG="$DIST/solvecraft-$VERSION-macos-$ARCH.dmg"
CLI_ZIP="$DIST/solvecraft-cli-$VERSION-macos-$ARCH.zip"
echo "==> SolveCraft $VERSION for macOS ($ARCH)"
echo "==> SolidCraft $VERSION for macOS ($ARCH)"
if [ "$SKIP_BUILD" = 0 ]; then
args=()
for t in "${TARGETS[@]}"; do args+=(--target "$t"); done
@@ -84,7 +84,7 @@ ln -s /Applications "$STAGE/Applications"
rm -f "$DMG" "$WORK/raw.dmg"
# makehybrid + convert builds the image without attaching a device, unlike `create -srcfolder`,
# which is flaky on CI runners ("Resource busy").
hdiutil makehybrid -hfs -hfs-volume-name "SolveCraft" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
hdiutil makehybrid -hfs -hfs-volume-name "SolidCraft" -hfs-openfolder "$STAGE" -o "$WORK/raw.dmg" "$STAGE"
hdiutil convert "$WORK/raw.dmg" -format UDZO -imagekey zlib-level=9 -o "$DMG"
rm -f "$WORK/raw.dmg"
+1 -1
View File
@@ -53,7 +53,7 @@ $Dist = if ($env:DIST) { $env:DIST } else { Join-Path $Root 'dist\release' }
$TargetDir = if ($env:CARGO_TARGET_DIR) { $env:CARGO_TARGET_DIR } else { Join-Path $Root 'target' }
New-Item -ItemType Directory -Force -Path $Dist | Out-Null
Write-Output "SolveCraft $Version for Windows $Arch ($Target)"
Write-Output "SolidCraft $Version for Windows $Arch ($Target)"
if (-not $SkipBuild) {
# Static CRT: no VC++ redistributable needed. Scoped to the target so host build scripts and
+18 -18
View File
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
SolveCraft MSI (WiX Toolset v5, plus the UI and Util extensions). Built by package.ps1:
SolidCraft MSI (WiX Toolset v5, plus the UI and Util extensions). Built by package.ps1:
wix build solvecraft.wxs -arch x64 -ext WixToolset.UI.wixext -ext WixToolset.Util.wixext
-culture en-US -loc solvecraft.en-us.wxl -d Version=1.2.3 -d DisplayVersion=1.2.3-rc.1
@@ -8,33 +8,33 @@
Per-machine install with a setup wizard: Welcome, install folder (Program Files\SolveCraft by
default, remembered for upgrades), Ready, progress, and a Finish page with a ticked "Launch
SolveCraft" box. Installs a Start Menu shortcut, solvecraft-cli, the licence files (including
THIRD-PARTY-LICENSES.txt), makes SolveCraft the owner of `.solvecraft` and offers it under
SolidCraft" box. Installs a Start Menu shortcut, solvecraft-cli, the licence files (including
THIRD-PARTY-LICENSES.txt), makes SolidCraft the owner of `.solvecraft` and offers it under
"Open with" for STEP, IGES, 3MF and STL. `msiexec /qn` installs silently.
Adapted from PhotoCraft's (storytold/photocraft, Apache-2.0; see ATTRIBUTION.md).
-->
<Wix xmlns="http://wixtoolset.org/schemas/v4/wxs"
xmlns:ui="http://wixtoolset.org/schemas/v4/wxs/ui">
<Package
Name="SolveCraft"
Manufacturer="SolveCraft contributors"
Name="SolidCraft"
Manufacturer="SolidCraft contributors"
Version="$(var.Version)"
UpgradeCode="6A145FD6-D285-428D-AE84-A69570CDF4DB"
Scope="perMachine"
Compressed="yes"
Language="1033">
<SummaryInformation Description="SolveCraft parametric 3D CAD" Manufacturer="SolveCraft contributors" />
<SummaryInformation Description="SolidCraft parametric 3D CAD" Manufacturer="SolidCraft contributors" />
<!-- Same-version upgrades let successive pre-releases of one X.Y.Z replace each other. -->
<MajorUpgrade AllowSameVersionUpgrades="yes"
DowngradeErrorMessage="A newer version of SolveCraft is already installed." />
DowngradeErrorMessage="A newer version of SolidCraft is already installed." />
<MediaTemplate EmbedCab="yes" CompressionLevel="high" />
<!-- Advertised shortcuts to an .exe need an .exe icon identifier (ICE50). -->
<Icon Id="SolvecraftIcon.exe" SourceFile="$(var.IconPath)" />
<Property Id="ARPPRODUCTICON" Value="SolvecraftIcon.exe" />
<Property Id="ARPURLINFOABOUT" Value="https://bherbruck.github.io/solvecraft/" />
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/bherbruck/solvecraft/releases" />
<Property Id="ARPHELPLINK" Value="https://github.com/bherbruck/solvecraft/issues" />
<Property Id="ARPURLINFOABOUT" Value="https://bherbruck.github.io/solidcraft/" />
<Property Id="ARPURLUPDATEINFO" Value="https://github.com/bherbruck/solidcraft/releases" />
<Property Id="ARPHELPLINK" Value="https://github.com/bherbruck/solidcraft/issues" />
<StandardDirectory Id="ProgramFiles6432Folder">
<Directory Id="INSTALLFOLDER" Name="SolveCraft" />
@@ -46,7 +46,7 @@
<RegistrySearch Id="PreviousInstallFolder" Root="HKLM" Key="Software\SolveCraft" Name="InstallDir" Type="raw" />
</Property>
<Feature Id="Main" Title="SolveCraft" Level="1">
<Feature Id="Main" Title="SolidCraft" Level="1">
<ComponentGroupRef Id="SolvecraftFiles" />
</Feature>
@@ -60,8 +60,8 @@
</UI>
<Property Id="SolvecraftVersion" Value="$(var.DisplayVersion)" />
<Property Id="WIXUI_EXITDIALOGOPTIONALTEXT" Value="SolveCraft $(var.DisplayVersion) is installed. You can start it any time from the Start menu." />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch SolveCraft" />
<Property Id="WIXUI_EXITDIALOGOPTIONALTEXT" Value="SolidCraft $(var.DisplayVersion) is installed. You can start it any time from the Start menu." />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOXTEXT" Value="Launch SolidCraft" />
<Property Id="WIXUI_EXITDIALOGOPTIONALCHECKBOX" Value="1" />
<Property Id="WixShellExecTarget" Value="[#SolvecraftExe]" />
<!-- Runs in the wizard's own (unelevated) process, so the app starts as the user. -->
@@ -72,13 +72,13 @@
<ComponentGroup Id="SolvecraftFiles" Directory="INSTALLFOLDER">
<Component Id="SolvecraftApp" Guid="290BEF27-C200-4069-AA43-7DE6A39CDB0A">
<File Id="SolvecraftExe" Source="$(var.BinDir)\solvecraft.exe" KeyPath="yes">
<Shortcut Id="StartMenuShortcut" Directory="ProgramMenuFolder" Name="SolveCraft"
Description="SolveCraft parametric 3D CAD" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
<Shortcut Id="StartMenuShortcut" Directory="ProgramMenuFolder" Name="SolidCraft"
Description="SolidCraft parametric 3D CAD" WorkingDirectory="INSTALLFOLDER" Advertise="yes"
Icon="SolvecraftIcon.exe" IconIndex="0" />
</File>
<!-- .solvecraft: SolveCraft's own format; SolveCraft is its default app. -->
<ProgId Id="Solvecraft.Design" Description="SolveCraft Design" Icon="SolvecraftExe" IconIndex="0">
<!-- .solvecraft: SolidCraft's own format; SolidCraft is its default app. -->
<ProgId Id="Solvecraft.Design" Description="SolidCraft Design" Icon="SolvecraftExe" IconIndex="0">
<Extension Id="solvecraft" ContentType="application/x-solvecraft">
<Verb Id="open" Command="Open" TargetFile="SolvecraftExe" Argument="&quot;%1&quot;" />
</Extension>
@@ -94,7 +94,7 @@
<RegistryValue Root="HKLM" Key="Software\Classes\.stl\OpenWithProgids" Name="Solvecraft.Model" Value="" Type="string" />
<!-- "Open with" app list and the Run dialog (Win+R "solvecraft"). -->
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe" Name="FriendlyAppName" Value="SolveCraft" Type="string" />
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe" Name="FriendlyAppName" Value="SolidCraft" Type="string" />
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\shell\open\command" Value="&quot;[#SolvecraftExe]&quot; &quot;%1&quot;" Type="string" />
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".solvecraft" Value="" Type="string" />
<RegistryValue Root="HKLM" Key="Software\Classes\Applications\solvecraft.exe\SupportedTypes" Name=".step" Value="" Type="string" />
+3 -3
View File
@@ -120,9 +120,9 @@ fn licence_files(dir: &Path, p: &Value) -> Vec<(String, String)> {
fn render(crates: &[Crate]) -> String {
let rule = "=".repeat(80);
let mut out = format!(
"Third-party software in SolveCraft\n\nSolveCraft (the desktop app, solvecraft-cli and the web app) is built from the {} crates below.\n\
"Third-party software in SolidCraft\n\nSolidCraft (the desktop app, solvecraft-cli and the web app) is built from the {} crates below.\n\
Generated by `cargo xtask licences` from Cargo.lock and the crates' own licence files.\n\
SolveCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.\n\n",
SolidCraft's own licence is in LICENSE-MIT and LICENSE-APACHE, and NOTICE.\n\n",
crates.len()
);
for c in crates {
@@ -146,7 +146,7 @@ fn render(crates: &[Crate]) -> String {
}
if !without.is_empty() {
out += &format!(
"\n{rule}\nCrates whose package carries no licence file (their licence is named in Cargo.toml; the MIT and Apache-2.0\ntexts are as in SolveCraft's own LICENSE-MIT and LICENSE-APACHE)\n{rule}\n\n"
"\n{rule}\nCrates whose package carries no licence file (their licence is named in Cargo.toml; the MIT and Apache-2.0\ntexts are as in SolidCraft's own LICENSE-MIT and LICENSE-APACHE)\n{rule}\n\n"
);
for c in without {
out += &format!(
+4 -4
View File
@@ -171,7 +171,7 @@ fn cmd_oracle() -> Result<(), String> {
let pass = list.iter().filter(|r| r["pass"].as_bool().unwrap_or(false)).count();
let skipped = list.iter().filter(|r| r["skipped"].as_bool().unwrap_or(false)).count();
let mut md = format!(
"# Fusion oracle\n\nGenerated by `cargo xtask oracle`. Each case is a part built in Fusion (recipe and measurements kept locally in `plan/fusion/oracle/`, not committed). SolveCraft replays the recipe through its command engine and compares body count, volume and area (relative tolerance 1e-3) and face/edge/vertex counts (exact). SolveCraft counts each closed-surface patch once and ignores seam edges and vertices; when body count, volume and area pass but the raw counts differ, Fusion's counts are normalised the same way from its face, edge and vertex lists (faces cut from one cylinder, cone, sphere or torus and separated only by seams count once; an edge on that surface and no other is a seam and is dropped; a vertex left with two edges joins them) and compared exactly; the note gives both sides' raw counts.\n\n**{pass} / {} cases pass** ({skipped} need features not implemented yet).\n\n| Case | Result | Notes |\n|---|---|---|\n",
"# Fusion oracle\n\nGenerated by `cargo xtask oracle`. Each case is a part built in Fusion (recipe and measurements kept locally in `plan/fusion/oracle/`, not committed). SolidCraft replays the recipe through its command engine and compares body count, volume and area (relative tolerance 1e-3) and face/edge/vertex counts (exact). SolidCraft counts each closed-surface patch once and ignores seam edges and vertices; when body count, volume and area pass but the raw counts differ, Fusion's counts are normalised the same way from its face, edge and vertex lists (faces cut from one cylinder, cone, sphere or torus and separated only by seams count once; an edge on that surface and no other is a seam and is dropped; a vertex left with two edges joins them) and compared exactly; the note gives both sides' raw counts.\n\n**{pass} / {} cases pass** ({skipped} need features not implemented yet).\n\n| Case | Result | Notes |\n|---|---|---|\n",
list.len()
);
for r in &list {
@@ -265,7 +265,7 @@ fn cmd_step_corpus(dir: Option<&str>, iges: bool) -> Result<(), String> {
let mut md = if iges {
format!(
"# IGES import (Fusion corpus)\n\nGenerated by `cargo xtask step-corpus --iges`. Each case is a reference part exported from Fusion as IGES 5.3 \
(kept locally in `plan/fusion/oracle/`, never committed). SolveCraft opens `part.igs` exactly as File → Open does and compares with Fusion's \
(kept locally in `plan/fusion/oracle/`, never committed). SolidCraft opens `part.igs` exactly as File → Open does and compares with Fusion's \
measurements of the part it exported: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file. \
How IGES is read: see [STEP import](step-import.md#iges). 50-sweep-tight-bend is written by Fusion as five loose, partly degenerate surfaces \
(seams running into slits, a ring given twice); Fusion cannot read it back as a solid either.\n\n**{pass} / {} files pass.**\n\n\
@@ -275,7 +275,7 @@ How IGES is read: see [STEP import](step-import.md#iges). 50-sweep-tight-bend is
} else {
format!(
"# STEP import\n\n\
SolveCraft reads STEP (ISO 10303-21; AP203, AP214 and AP242 geometry) with its own reader in `crates/kernel/src/step_in` \
SolidCraft reads STEP (ISO 10303-21; AP203, AP214 and AP242 geometry) with its own reader in `crates/kernel/src/step_in` \
(written from the public standards; no third-party STEP code). A STEP file becomes an **Import** base feature on the \
timeline: it holds the file's B-rep (the STEP text is kept in the design, so the design reopens without the file) and \
later features — fillets, holes, cuts sketched on its faces, shells — build on its bodies.\n\n\
@@ -301,7 +301,7 @@ spheres, tori, rational B-spline surfaces, surfaces of revolution and tabulated
units; colours (314 and the predefined numbers); body names (name properties, else labels). Not read yet: ruled and \
offset surfaces, curves on surfaces given only in parameter space. Export (`file.export` to `.igs`) writes each body as \
a manifold solid B-rep on the same surfaces a STEP export uses, with its name and colour.\n\n\
## Fusion corpus\n\nGenerated by `cargo xtask step-corpus`. Each case is a part exported from Fusion as STEP (AP214; kept locally in `plan/fusion/oracle/`, never committed). SolveCraft opens `part.step` exactly as File → Open does (an Import base feature) and compares with Fusion's own measurements: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file.\n\n**{pass} / {} files pass.**\n\n| Case | Result | Bodies | Volume mm³ (got / Fusion) | Area mm² (got / Fusion) | Faces (got / Fusion) | ms | Notes |\n|---|---|---|---|---|---|---|---|\n",
## Fusion corpus\n\nGenerated by `cargo xtask step-corpus`. Each case is a part exported from Fusion as STEP (AP214; kept locally in `plan/fusion/oracle/`, never committed). SolidCraft opens `part.step` exactly as File → Open does (an Import base feature) and compares with Fusion's own measurements: body count, volume and area (relative tolerance 1e-3) and the number of faces read from the file.\n\n**{pass} / {} files pass.**\n\n| Case | Result | Bodies | Volume mm³ (got / Fusion) | Area mm² (got / Fusion) | Faces (got / Fusion) | ms | Notes |\n|---|---|---|---|---|---|---|---|\n",
list.len()
)
};
+5 -5
View File
@@ -3,8 +3,8 @@
//! `xtask/data/fusion-catalog.tsv` lists the toolbar commands of Fusion's Design workspace as
//! `tab \t panel \t command id \t command name` (ids and names only, nothing else). It is
//! derived from the local, uncommitted `plan/fusion/menu-tree.json` with `--refresh`. A catalog
//! entry is live when SolveCraft registers the command `xtask/data/fusion-ids.tsv` maps its id to
//! (SolveCraft uses its own ids).
//! entry is live when SolidCraft registers the command `xtask/data/fusion-ids.tsv` maps its id to
//! (SolidCraft uses its own ids).
use std::path::Path;
use std::process::Command;
@@ -32,10 +32,10 @@ const HEADLINE: &[&str] = &["SOLID", "SKETCH"];
/// figure; the rest of Fusion is deferred and listed separately.
const IN_SCOPE_TABS: &[&str] = &["SOLID", "SKETCH", "ASSEMBLY", "SHEET METAL"];
/// The PLASTIC commands in scope (enclosure features).
/// (SolveCraft ids, matched through the catalog-id map.)
/// (SolidCraft ids, matched through the catalog-id map.)
const PLASTIC_SUBSET: &[&str] = &["plastic.boss", "solid.rib", "solid.web", "plastic.lip", "plastic.snap_fit"];
/// Catalog id → SolveCraft id.
/// Catalog id → SolidCraft id.
fn id_map(s: &str) -> std::collections::HashMap<String, String> {
s.lines()
.filter(|l| !l.starts_with('#') && !l.trim().is_empty())
@@ -143,7 +143,7 @@ pub fn run(root: &Path, refresh: bool) -> Result<(), String> {
let scope: Vec<&Entry> = catalog.iter().filter(|e| in_scope(e)).collect();
let scope_live = scope.iter().filter(|e| live(e)).count();
let mut md = format!(
"# Fusion command parity\n\nGenerated by `cargo xtask parity` from `xtask/data/fusion-catalog.tsv` (the toolbar commands of Fusion's Design workspace, ids and names only) and the live command registry (`solvecraft-cli commands`). A command counts as live when SolveCraft registers the same command id. This measures breadth, not depth; see ROADMAP.md and docs/oracle.md for depth.\n\n**In scope (SOLID, SKETCH, ASSEMBLY, SHEET METAL, PLASTIC enclosure subset): {scope_live} / {} commands live ({:.0}%).** SOLID + SKETCH: {head_live} / {} ({:.0}%). Registered commands: {}.\n\nDeferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE beyond what solids need, PCB, render, animation, simulation, manufacture, drawings, MANAGE, UTILITIES and the rest of PLASTIC. All Design tabs together: {all_live} / {} ({:.0}%).\n\n| Tab | Panel | Live | Total | % |\n|---|---|---:|---:|---:|\n",
"# Fusion command parity\n\nGenerated by `cargo xtask parity` from `xtask/data/fusion-catalog.tsv` (the toolbar commands of Fusion's Design workspace, ids and names only) and the live command registry (`solvecraft-cli commands`). A command counts as live when SolidCraft registers the same command id. This measures breadth, not depth; see ROADMAP.md and docs/oracle.md for depth.\n\n**In scope (SOLID, SKETCH, ASSEMBLY, SHEET METAL, PLASTIC enclosure subset): {scope_live} / {} commands live ({:.0}%).** SOLID + SKETCH: {head_live} / {} ({:.0}%). Registered commands: {}.\n\nDeferred until further notice (not in the in-scope figure): MESH, FORM, SURFACE beyond what solids need, PCB, render, animation, simulation, manufacture, drawings, MANAGE, UTILITIES and the rest of PLASTIC. All Design tabs together: {all_live} / {} ({:.0}%).\n\n| Tab | Panel | Live | Total | % |\n|---|---|---:|---:|---:|\n",
scope.len(),
pct(scope_live, scope.len()),
head.len(),