Public reference tests: analytic and FreeCAD expected values (#310)
The oracle no longer uses Fusion measurements. tests/oracle/<case>/ now holds the 76 recipes (our own format, with recorded measurements and tool-specific fields removed) and an expected.json per case: - 55 analytic cases: closed-form values from tools/oracle-analytic/ (stdlib Python, formulas in each case's "method"). - 21 FreeCAD cases: the same recipe built with FreeCAD's public scripting API by tools/freecad-oracle/ (an external program, not a dependency), with a reference.stl for 20. solvecraft-cli oracle compares body count, volume, area, centre of mass, bbox, face counts where the geometry fixes them, and, against reference.stl, the sampled Hausdorff distance and the principal moments (spatial.rs from #301). cargo xtask oracle reads tests/oracle, writes docs/oracle.md with the source of each value, and supports known_failure marks for tracked bugs. CI runs it on every PR; a manual workflow regenerates the FreeCAD values. step-corpus, seam normalisation and the through-all emulation, which only existed to compare with Fusion, are removed. 75/76 pass; 75-sm-bracket-holes is a known failure (#296). Claude-Session: https://claude.ai/code/session_01RjGw4gA5ZvKrU8MQP6sBgT Co-authored-by: bherbruck <[email protected]> Co-authored-by: Claude Opus 5.5 <[email protected]>
This commit is contained in:
204 files changed
+27106
-932
No files matched your search
@@ -122,9 +122,41 @@ jobs:
|
||||
if: always()
|
||||
run: sccache --show-stats
|
||||
|
||||
oracle:
|
||||
name: Reference tests
|
||||
needs: changes
|
||||
if: needs.changes.outputs.native == 'true'
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
RUSTC_WRAPPER: sccache
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
SCCACHE_IGNORE_SERVER_IO_ERROR: "1"
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- uses: dtolnay/rust-toolchain@stable
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2.9.2
|
||||
with:
|
||||
key: oracle
|
||||
- name: Set up compiler cache
|
||||
uses: mozilla-actions/sccache-action@fc920bf0ec8de6ee65d409111f7ec508035751ba # v0.0.11
|
||||
with:
|
||||
version: v0.16.0
|
||||
- name: Build the command line
|
||||
run: cargo build -p solvecraft-cli --profile ci --locked
|
||||
- name: Replay tests/oracle and compare with expected.json
|
||||
run: cargo xtask oracle --cli target/ci/solvecraft-cli
|
||||
- name: Upload the report
|
||||
if: always()
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: oracle-report
|
||||
path: docs/oracle.md
|
||||
|
||||
# Preserve the existing check name for branch protection, including docs-only PRs.
|
||||
ci:
|
||||
needs: [changes, checks, clippy, tests]
|
||||
needs: [changes, checks, clippy, tests, oracle]
|
||||
if: always()
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
@@ -135,12 +167,14 @@ jobs:
|
||||
NATIVE: ${{ needs.changes.outputs.native }}
|
||||
CLIPPY: ${{ needs.clippy.result }}
|
||||
TESTS: ${{ needs.tests.result }}
|
||||
ORACLE: ${{ needs.oracle.result }}
|
||||
run: |
|
||||
test "$CHANGES" = success
|
||||
test "$CHECKS" = success
|
||||
if [ "$NATIVE" = true ]; then
|
||||
test "$CLIPPY" = success
|
||||
test "$TESTS" = success
|
||||
test "$ORACLE" = success
|
||||
fi
|
||||
|
||||
wasm:
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Optional, run by hand: rebuild the reference cases whose expected values come from FreeCAD
|
||||
# (tools/freecad-oracle/) and show what changed. FreeCAD runs as a separate program on the runner;
|
||||
# it is not a dependency of SolidCraft. Nothing is committed: review the diff and the uploaded
|
||||
# files, then commit them in a pull request if the change is intended.
|
||||
name: FreeCAD reference values
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
cases:
|
||||
description: "Case directories (space separated); empty = every case whose source is FreeCAD"
|
||||
type: string
|
||||
default: ""
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
regenerate:
|
||||
name: Regenerate FreeCAD expected values
|
||||
runs-on: ubuntu-24.04
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install FreeCAD
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends freecad
|
||||
freecadcmd --version || true
|
||||
- name: Rebuild the cases
|
||||
env:
|
||||
CASES: ${{ inputs.cases }}
|
||||
run: |
|
||||
if [ -z "$CASES" ]; then
|
||||
CASES=$(grep -l '"source": *"freecad' tests/oracle/*/expected.json | xargs -n1 dirname | tr '\n' ' ')
|
||||
fi
|
||||
# shellcheck disable=SC2086
|
||||
tools/freecad-oracle/run.sh --write $CASES
|
||||
- name: Show the changes
|
||||
run: git diff --stat && git diff -- tests/oracle/*/expected.json
|
||||
- name: Upload the regenerated files
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: freecad-expected
|
||||
path: |
|
||||
tests/oracle/*/expected.json
|
||||
tests/oracle/*/reference.stl
|
||||
@@ -17,8 +17,7 @@ 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/`
|
||||
(`menu-tree.json` is Fusion's toolbar dump; `oracle/*/` are parts built in Fusion with their
|
||||
recipes and measurements).
|
||||
(`menu-tree.json` is Fusion's toolbar dump).
|
||||
2. Kernel and library decisions: `plan/adr/0001-geometry-kernel.md` (what we may depend on, what
|
||||
is rejected for licence reasons).
|
||||
3. Follow the autonomous operation protocol (`plan/execution-plan.md` §6). Don't stop to ask
|
||||
@@ -49,7 +48,9 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
|
||||
Fusion. Command ids and command names are used as identifiers only (`xtask/data/fusion-catalog.tsv`
|
||||
holds ids and names, nothing else). File formats come from public specifications. **Never read,
|
||||
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
|
||||
CadQuery/OCP, LibreCAD, QCAD — don't even read their sources. (`tools/freecad-oracle/` runs
|
||||
FreeCAD as a separate program through its public scripting API to compute reference values;
|
||||
nothing from FreeCAD is linked, vendored or shipped.) The sketch solver and the blend
|
||||
operations are our own.
|
||||
- **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
|
||||
@@ -123,8 +124,9 @@ before the draft is verified.
|
||||
`ui.render` or `solvecraft-cli snapshot`.
|
||||
- Headless: `solvecraft-cli run script.json --out part.step`, `solvecraft-cli eval design.solvecraft`,
|
||||
`solvecraft-cli snapshot script.json --out shot.png`, `solvecraft-cli commands`.
|
||||
- Fusion oracle: `cargo xtask oracle` replays `plan/fusion/oracle/*/recipe.json` and compares
|
||||
with Fusion's `measure.json` (writes `docs/oracle.md`).
|
||||
- Reference tests: `cargo xtask oracle` replays `tests/oracle/*/recipe.json` and compares with
|
||||
each case's `expected.json` (analytic or FreeCAD values; writes `docs/oracle.md`). Expected
|
||||
values never come from Fusion or any other commercial CAD product.
|
||||
- Build with `CARGO_TARGET_DIR=<repo>/target`. Windows: `cargo xwin build --release --target x86_64-pc-windows-msvc -p solvecraft`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
@@ -27,6 +27,26 @@ Generated-in-code assets are original and have no file to list:
|
||||
| docs/book/src/generated/sheet-metal.png | SolveCraft contributors | rendered by `cargo xtask book` (`solvecraft-cli snapshot docs/book/scripts/sheet-metal.json`; gitignored) | MIT OR Apache-2.0 |
|
||||
| crates/sketch/fonts/LiberationSans-Regular.ttf | Red Hat, Inc. (Liberation Fonts 2.x; digitized data Google) | https://github.com/liberationfonts (Debian fonts-liberation), unmodified; licence text in crates/sketch/fonts/OFL.txt; used for sketch Text outlines | SIL OFL 1.1 |
|
||||
| apps/solvecraft-web/fonts/BIZUDPGothic-Regular.ttf | The BIZ UDGothic Project Authors | https://github.com/googlefonts/morisawa-biz-ud-gothic (Google Fonts), unmodified; licence text in apps/solvecraft-web/fonts/OFL.txt; used for web UI Japanese font | SIL OFL 1.1 |
|
||||
| tests/oracle/17-loft/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/17-loft/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/23-bracket-multifeature/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/23-bracket-multifeature/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/28-blind-pocket-fillets/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/28-blind-pocket-fillets/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/30-fillet-after-shell/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/30-fillet-after-shell/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/31-fillet-l-all-edges/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/31-fillet-l-all-edges/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/34-chamfer-then-fillet/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/34-chamfer-then-fillet/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/35-fillet-curved-curved/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/35-fillet-curved-curved/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/36-corner-blend-two-radii/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/36-corner-blend-two-radii/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/37-pyramid-apex-blend/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/37-pyramid-apex-blend/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/39-tee-pipe/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/39-tee-pipe/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/41-tube-radial-holes/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/41-tube-radial-holes/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/42-overlapping-patterns/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/42-overlapping-patterns/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/47-draft-curved-pocket/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/47-draft-curved-pocket/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/51-loft-rect-circle-offset/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/51-loft-rect-circle-offset/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/56-holes-on-curved-face/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/56-holes-on-curved-face/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/58-phone-stand/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/58-phone-stand/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/59-enclosure-base/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/59-enclosure-base/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/61-shaft-keyway/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/61-shaft-keyway/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/63-hinge-knuckle/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/63-hinge-knuckle/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
| tests/oracle/65-sketch-gear-tooth/reference.stl | SolidCraft contributors | triangulation of our own recipe (tests/oracle/65-sketch-gear-tooth/recipe.json) built with FreeCAD as an external tool by tools/freecad-oracle | MIT OR Apache-2.0 |
|
||||
|
||||
## Code adapted from sibling projects
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ 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/`
|
||||
(`menu-tree.json` is Fusion's toolbar dump; `oracle/*/` are parts built in Fusion with their
|
||||
recipes and measurements).
|
||||
(`menu-tree.json` is Fusion's toolbar dump).
|
||||
2. Kernel and library decisions: `plan/adr/0001-geometry-kernel.md` (what we may depend on, what
|
||||
is rejected for licence reasons).
|
||||
3. Follow the autonomous operation protocol (`plan/execution-plan.md` §6). Don't stop to ask
|
||||
@@ -49,7 +48,9 @@ People trust SolidCraft with their designs; a crash loses their work. **This out
|
||||
Fusion. Command ids and command names are used as identifiers only (`xtask/data/fusion-catalog.tsv`
|
||||
holds ids and names, nothing else). File formats come from public specifications. **Never read,
|
||||
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
|
||||
CadQuery/OCP, LibreCAD, QCAD — don't even read their sources. (`tools/freecad-oracle/` runs
|
||||
FreeCAD as a separate program through its public scripting API to compute reference values;
|
||||
nothing from FreeCAD is linked, vendored or shipped.) The sketch solver and the blend
|
||||
operations are our own.
|
||||
- **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
|
||||
@@ -100,8 +101,9 @@ before the draft is verified.
|
||||
`ui.render` or `solvecraft-cli snapshot`.
|
||||
- Headless: `solvecraft-cli run script.json --out part.step`, `solvecraft-cli eval design.solvecraft`,
|
||||
`solvecraft-cli snapshot script.json --out shot.png`, `solvecraft-cli commands`.
|
||||
- Fusion oracle: `cargo xtask oracle` replays `plan/fusion/oracle/*/recipe.json` and compares
|
||||
with Fusion's `measure.json` (writes `docs/oracle.md`).
|
||||
- Reference tests: `cargo xtask oracle` replays `tests/oracle/*/recipe.json` and compares with
|
||||
each case's `expected.json` (analytic or FreeCAD values; writes `docs/oracle.md`). Expected
|
||||
values never come from Fusion or any other commercial CAD product.
|
||||
- Build with `CARGO_TARGET_DIR=<repo>/target`. Windows: `cargo xwin build --release --target x86_64-pc-windows-msvc -p solvecraft`.
|
||||
|
||||
## Roadmap
|
||||
|
||||
+10
-6
@@ -119,7 +119,7 @@ the most complete description of the house style.
|
||||
|---|---|---|
|
||||
| Unit and engine tests | next to the code, or `*_tests.rs` files | `cargo xtask test` (or `cargo test --workspace --profile ci`) |
|
||||
| UI scenarios | `crates/ui-egui/tests/scenarios/*.json` | part of `cargo test` |
|
||||
| Fusion oracle | `cargo xtask oracle` | needs the local oracle data (see below) |
|
||||
| Reference tests | `cargo xtask oracle` | `tests/oracle/` (see below) |
|
||||
|
||||
**UI scenarios** drive the real app headless: start a tool, click a plane or a sketch point,
|
||||
press keys, then assert on the result.
|
||||
@@ -150,11 +150,15 @@ in the PR. `cargo xtask test` builds the tests with it and then runs every test
|
||||
`cargo xtask test -- shell`. Tests run beside each other on a busy machine, so a test must not
|
||||
depend on wall-clock timing.
|
||||
|
||||
**The Fusion oracle** is a set of 76 reference parts, each built in Fusion from a recipe.
|
||||
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.
|
||||
**The reference tests** are 76 parts in [`tests/oracle/`](tests/oracle/), each a `recipe.json`
|
||||
in our own format with an `expected.json` beside it. SolidCraft rebuilds the recipe and compares
|
||||
body count, volume, area, centre of mass and bounding box (and, where there is a `reference.stl`,
|
||||
the surface itself). Expected values are either worked out in closed form
|
||||
(`tools/oracle-analytic/`) or computed by building the same recipe in FreeCAD, run as a separate
|
||||
program (`tools/freecad-oracle/`); each file says which. [docs/oracle.md](docs/oracle.md) has
|
||||
the current results, and CI runs `cargo xtask oracle` on every pull request. A case that fails
|
||||
because of a known bug names its issue in `expected.json` (`"known_failure": "#123"`); never
|
||||
change an expected value to match SolidCraft's output.
|
||||
|
||||
## Sending a change
|
||||
|
||||
|
||||
@@ -17,8 +17,9 @@ developer guide and command reference; sources in [docs/book](docs/book)), and t
|
||||
Linux Flatpak installation needs the Freedesktop runtime from Flathub as well as the
|
||||
SolidCraft application feed. See [install and launch commands](https://bherbruck.github.io/solidcraft/guide/getting-started.html#linux-flatpak).
|
||||
|
||||
**Status: early but usable for simple parts.** The historical benchmark report records
|
||||
76 of 76 reference parts matching volume, area and topology ([docs/oracle.md](docs/oracle.md)).
|
||||
**Status: early but usable for simple parts.** 75 of 76 public reference parts
|
||||
match their analytic or FreeCAD-computed volume, area, centre of mass and bounding box
|
||||
([docs/oracle.md](docs/oracle.md)); the other is a tracked bug.
|
||||
These checks do not establish complete geometric or workflow equivalence. Feature coverage
|
||||
is tracked in [docs/parity.md](docs/parity.md) and [ROADMAP.md](ROADMAP.md).
|
||||
Open work and known bugs are in [GitHub issues](https://github.com/bherbruck/solidcraft/issues),
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ features → parametric timeline) as an open, pure-Rust, agent-drivable applicat
|
||||
|---|---|
|
||||
| Where are we? | **M0 and M13 done; M1, M2, M5–M7, M9, M10, M14 in progress.** Sketch with solver and the full SKETCH create/modify set; extrude, revolve (two sides, symmetric, to object), fillet/chamfer (variable radius, chord length, a radius per edge, corner patches), holes, threads (cosmetic and modelled), patterns, mirror, shell (inside/outside/both), draft, loft, sweep, coil, split body and split face, delete face, construction planes, axes and points; persistent naming of faces and edges; parameters and configurations; components, joints, motion studies, exploded views; sheet metal and a plastic enclosure subset; STEP/IGES/3MF/STL/OBJ/DXF; autosave and crash recovery; desktop and browser (wasm) app, CLI, MCP server, oracle harness. |
|
||||
| Command parity (in scope) | **335 / 541 (62%)** in-scope commands; SOLID + SKETCH **188 / 291 (65%)** — [docs/parity.md](docs/parity.md); registry breadth does not establish workflow or geometric parity |
|
||||
| Fusion oracle | **76 / 76 parts match** in the checked-in historical report — [docs/oracle.md](docs/oracle.md). The current sprint adds independent analytic and UI regressions; the local Fusion oracle corpus has not been rerun. |
|
||||
| Reference tests | **75 / 76 parts match** — [docs/oracle.md](docs/oracle.md): public cases in `tests/oracle/` with analytic (55) or FreeCAD-computed (21) expected values, run by CI on every PR; 75-sm-bracket-holes fails (#296). |
|
||||
| Tests | Solver, profiles, kernel booleans/blends/measures, expressions, timeline, persistent naming, file formats and migrations, assemblies, sheet metal, engine end-to-end, hostile-input fuzz over every command and headless UI scenarios. `cargo xtask ci` reports the current passing counts. |
|
||||
| Gates | `cargo xtask ci`: fmt, clippy -D warnings, tests, asset attribution, layering, wasm32 build — green |
|
||||
| Time to a useful alpha (M0–M6) | ≈ **82 agent hours** remain (sum of the M1–M6 estimates below) |
|
||||
@@ -50,6 +50,7 @@ headline counts in-scope tabs only and lists the deferred ones separately.
|
||||
| M2 | Robust modelling: coplanar booleans in the kernel, holes (placed on faces and on sketch points), threads (cosmetic, then modelled), patterns, mirror, shell, draft, split, construction geometry | in progress | 16 |
|
||||
| M3 | General fillets and chamfers (curved edges, chains, vertex blends); saddle rolling-ball surfaces now use continuous seam sections and arc-length fitting with shared contact boundaries and bounded accuracy checks | in progress | 30 |
|
||||
| M4 | Files: STEP import (done: all 29 Fusion STEP files — [docs/step-import.md](docs/step-import.md)), 3MF export and 3MF/STL import as mesh bodies (done), constant-section parametric mesh reconstruction, DXF sketches, OBJ import | in progress | 6 |
|
||||
| M4 | Files: STEP import (done — [docs/step-import.md](docs/step-import.md)), 3MF export and 3MF/STL import as mesh bodies (done), DXF sketches, OBJ import | in progress | 6 |
|
||||
| M5 | Persistent naming of faces and edges (plan/adr/0002): faces named from their history (sketch curve sides, caps, revolve sections, primitive sides, blends, pattern/mirror copies; split pieces numbered), edges by their faces; fillets/chamfers, sketches on faces, shell/draft/offset/replace faces, hole placements, joint origins and face appearances resolve by name first and warn when the entity split or vanished: done. sheet metal edges by base sketch line and flange side, shell inner faces, sweep and loft roles: done. Left: kernel-reported face history (exact through coincident surfaces) | in progress | 20 |
|
||||
| M6 | UI depth: selection feedback and origin (done), timeline editing (done: hover and selection highlight modifier faces, including fillets and chamfers; edit feature, history marker drag, reorder with dependency checks, suppress, rename, delete with dependents, re-resolved references; redefining selections refreshes persistent edge/face references while retaining feature identity and dimension names), right-docked dialogs (done), live previews for every feature dialog (done), keyboard-first commands with on-canvas value boxes and sketch inline dimensions (done), drag manipulators with automatic join/cut (done: extrude incl. two sides, fillet, shell, hole depth, move, pattern spacing, revolve rotator; taper handle open), dark theme (done), right-click menu with Repeat (done), measure tool (done), section analysis (done), selection filter (done), drag-solve of sketch points (done); appearance controls now preserve body/face/component targeting, named transparent looks, reuse/search and override removal; preferences | in progress | 6 |
|
||||
| M7 | Sweep, loft (incl. to a point), rib and web (to next / depth), emboss (planar faces and exact radial emboss/deboss of bounded orthogonal profiles on complete exterior cylinders/sleeves), thread (cosmetic, and modelled M3–M20 outside and inside: the ISO profile screwed in as a faceted cut), coil (smooth B-spline helical walls), pipe, pattern on path, patterns of bodies and components, replace face (parallel), align, remove, feature copy/paste: done. Unique eligible cylindrical targets are inferred for new Emboss features. Left: general curved-face emboss and exact (unfaceted) modelled threads | in progress | 12 |
|
||||
@@ -175,8 +176,7 @@ headline counts in-scope tabs only and lists the deferred ones separately.
|
||||
containment shortcut. Spheres are built from six pole-free patches.
|
||||
- Mass properties come from fine tessellation (curved faces within ~1e-4 relative); triangles
|
||||
that cut through curved faces (trimmed faces after booleans) are split onto the surface first.
|
||||
- STEP import (our own reader, crates/kernel/src/step_in; all 29 Fusion STEP files pass
|
||||
`cargo xtask step-corpus`) reads solids (B-rep with analytic and B-spline geometry), units,
|
||||
- STEP import (our own reader, crates/kernel/src/step_in) reads solids (B-rep with analytic and B-spline geometry), units,
|
||||
product names, colours and assemblies; analytic offsets on planes, cylinders, cones, spheres
|
||||
and regular tori are read exactly (signed/nested distances, units and face sense). Freeform
|
||||
offset surfaces and pcurve-only edges are not read yet,
|
||||
|
||||
@@ -8,8 +8,7 @@
|
||||
//! solvecraft-cli exec <command> [json-params] run one command on an empty design
|
||||
//! solvecraft-cli commands the command registry as JSON
|
||||
//! solvecraft-cli recipe <recipe.json> translate an oracle recipe to a script
|
||||
//! solvecraft-cli oracle <case-dir>... replay recipes and compare with measure.json
|
||||
//! solvecraft-cli step-corpus <case-dir>... import part.step and compare with measure.json
|
||||
//! solvecraft-cli oracle <case-dir>... replay recipes and compare with expected.json
|
||||
//! solvecraft-cli mcp [--in design|script] [--connect HOST:PORT] MCP server on stdio (docs/mcp.md)
|
||||
//! solvecraft-cli licences SolveCraft's licence and the third-party licences
|
||||
//! solvecraft-cli icon --out F.png|F.ico [--size N] the app icon (packaging; drawn, not a file)
|
||||
@@ -20,7 +19,7 @@
|
||||
mod fuzz;
|
||||
mod oracle;
|
||||
mod recipe;
|
||||
mod seams;
|
||||
mod spatial;
|
||||
|
||||
use std::process::ExitCode;
|
||||
|
||||
@@ -42,7 +41,6 @@ const USAGE: &str = "usage:
|
||||
solvecraft-cli recipe <recipe.json>
|
||||
solvecraft-cli oracle <case-dir>... [--json]
|
||||
solvecraft-cli fuzz [--from N] [--count N] [--jobs J] [--timeout S] [--steps N] [--minimise] [--out DIR] [--report FILE]
|
||||
solvecraft-cli step-corpus <case-dir>... [--json] [--iges]
|
||||
solvecraft-cli mcp [--in design.solvecraft|script.json] [--connect 127.0.0.1:PORT]
|
||||
solvecraft-cli licences
|
||||
solvecraft-cli icon --out icon.png [--size 256] | --out icon.ico
|
||||
@@ -73,7 +71,6 @@ fn main() -> ExitCode {
|
||||
recipe::to_script(&v).map(|s| println!("{}", pretty(&s)))
|
||||
}),
|
||||
Some("oracle") => oracle::run(&args[1..]),
|
||||
Some("step-corpus") => oracle::step_corpus(&args[1..]),
|
||||
Some("fuzz") => fuzz::run(&args[1..]),
|
||||
Some("fuzz-one") => fuzz::one(&args[1..]),
|
||||
Some("mcp") => cmd_mcp(&args[1..]),
|
||||
|
||||
+153
-167
@@ -1,129 +1,149 @@
|
||||
//! Replay oracle recipes and compare with Fusion's measurements; import oracle STEP files and
|
||||
//! compare them with the same measurements.
|
||||
//! Reference tests: replay `tests/oracle/<case>/recipe.json` through the command engine and
|
||||
//! compare the part with `expected.json` (closed-form values, or values from an independent
|
||||
//! kernel; see tests/oracle/README.md), and with `reference.stl` where one exists.
|
||||
|
||||
#![allow(clippy::needless_range_loop)]
|
||||
|
||||
use serde_json::{Value, json};
|
||||
use solvecraft_engine::geom::Vec3;
|
||||
|
||||
use crate::{read_json, recipe, seams};
|
||||
use crate::spatial::{self, Tri};
|
||||
use crate::{read_json, recipe};
|
||||
|
||||
/// Relative tolerance for volume and area (our curved-face tessellation is within ~2e-4).
|
||||
/// Default relative tolerance for volume and area (our curved-face tessellation is within ~2e-4).
|
||||
pub const REL_TOL: f64 = 1e-3;
|
||||
/// Default tolerance for centre of mass and bounding box, as a fraction of the bbox diagonal.
|
||||
const ABS_FRAC: f64 = 1e-3;
|
||||
/// Chord tolerance used to triangulate our part for the surface-distance check (mm).
|
||||
const MESH_TOL: f64 = 0.01;
|
||||
/// Relative tolerance on the principal second moments of the two triangulations.
|
||||
const INERTIA_REL: f64 = 2e-3;
|
||||
|
||||
/// Cases compared loosely (relative tolerance on volume and area, topology not compared), and why.
|
||||
const APPROXIMATE: [(&str, f64, &str); 1] = [(
|
||||
"49-helical-sweep",
|
||||
5e-3,
|
||||
"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",
|
||||
)];
|
||||
/// What SolidCraft built: totals over every body.
|
||||
struct Got {
|
||||
body_count: usize,
|
||||
volume: f64,
|
||||
area: f64,
|
||||
com: [f64; 3],
|
||||
bbox: ([f64; 3], [f64; 3]),
|
||||
faces: u64,
|
||||
}
|
||||
|
||||
fn v3(v: &Value) -> Option<[f64; 3]> {
|
||||
let a = v.as_array()?;
|
||||
Some([a.first()?.as_f64()?, a.get(1)?.as_f64()?, a.get(2)?.as_f64()?])
|
||||
}
|
||||
|
||||
fn totals(m: &Value) -> Got {
|
||||
let bodies = m["bodies"].as_array().cloned().unwrap_or_default();
|
||||
let (mut volume, mut area, mut faces) = (0.0, 0.0, 0);
|
||||
let mut moment = [0.0; 3];
|
||||
let mut lo = [f64::INFINITY; 3];
|
||||
let mut hi = [f64::NEG_INFINITY; 3];
|
||||
for b in &bodies {
|
||||
let v = b["volume_mm3"].as_f64().unwrap_or(f64::NAN);
|
||||
volume += v;
|
||||
area += b["area_mm2"].as_f64().unwrap_or(f64::NAN);
|
||||
faces += b["faces"].as_u64().unwrap_or(0);
|
||||
let c = v3(&b["center_of_mass"]).unwrap_or([f64::NAN; 3]);
|
||||
let (bl, bh) = (v3(&b["bbox"]["min"]).unwrap_or([f64::NAN; 3]), v3(&b["bbox"]["max"]).unwrap_or([f64::NAN; 3]));
|
||||
for k in 0..3 {
|
||||
moment[k] += v * c[k];
|
||||
lo[k] = lo[k].min(bl[k]);
|
||||
hi[k] = hi[k].max(bh[k]);
|
||||
}
|
||||
}
|
||||
let com = moment.map(|x| x / volume);
|
||||
Got { body_count: bodies.len(), volume, area, com, bbox: (lo, hi), faces }
|
||||
}
|
||||
|
||||
struct Checks {
|
||||
list: Vec<Value>,
|
||||
pass: bool,
|
||||
}
|
||||
|
||||
impl Checks {
|
||||
fn push(&mut self, what: &str, got: Value, want: Value, ok: bool) {
|
||||
self.pass &= ok;
|
||||
self.list.push(json!({"check": what, "got": got, "want": want, "ok": ok}));
|
||||
}
|
||||
fn rel(&mut self, what: &str, g: f64, w: Option<f64>, rel: f64) {
|
||||
let ok = w.is_some_and(|w| g.is_finite() && (g - w).abs() <= rel * w.abs().max(1.0));
|
||||
self.push(what, json!(g), json!(w), ok);
|
||||
}
|
||||
fn vec(&mut self, what: &str, g: [f64; 3], w: Option<[f64; 3]>, tol: f64) {
|
||||
let ok = w.is_some_and(|w| g.iter().zip(w).all(|(g, w)| g.is_finite() && (g - w).abs() <= tol));
|
||||
self.push(what, json!(g), json!(w), ok);
|
||||
}
|
||||
}
|
||||
|
||||
fn check_case(dir: &str) -> Value {
|
||||
let name = std::path::Path::new(dir).file_name().map(|x| x.to_string_lossy().to_string()).unwrap_or_default();
|
||||
let rp = format!("{dir}/recipe.json");
|
||||
let mp = format!("{dir}/measure.json");
|
||||
let (recipe, want) = match (read_json(&rp), read_json(&mp)) {
|
||||
(Ok(r), Ok(m)) => (r, m),
|
||||
(Err(e), _) | (_, Err(e)) => return json!({"case": name, "pass": false, "error": e}),
|
||||
let recipe = match read_json(&format!("{dir}/recipe.json")) {
|
||||
Ok(r) => r,
|
||||
Err(e) => return json!({"case": name, "pass": false, "error": e}),
|
||||
};
|
||||
let want = read_json(&format!("{dir}/expected.json")).ok();
|
||||
let script = match recipe::to_script(&recipe) {
|
||||
Ok(s) => s,
|
||||
Err(e) => return json!({"case": name, "pass": false, "skipped": true, "error": e}),
|
||||
};
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
if let Err(e) = s.run_script(&script) {
|
||||
return json!({"case": name, "pass": false, "error": e.to_string()});
|
||||
return json!({"case": name, "pass": false, "error": e.to_string(), "source": want.as_ref().map(|w| w["source"].clone())});
|
||||
}
|
||||
let got = match s.execute("inspect.measure", &json!({})) {
|
||||
Ok(m) => m,
|
||||
Ok(m) => totals(&m),
|
||||
Err(e) => return json!({"case": name, "pass": false, "error": e.to_string()}),
|
||||
};
|
||||
let mut checks = Vec::new();
|
||||
let mut pass = true;
|
||||
let mut num = |what: &str, g: Option<f64>, w: Option<f64>, rel: f64| {
|
||||
let ok = match (g, w) {
|
||||
(Some(g), Some(w)) => (g - w).abs() <= rel * w.abs().max(1.0),
|
||||
_ => false,
|
||||
};
|
||||
pass &= ok;
|
||||
checks.push(json!({"check": what, "got": g, "want": w, "ok": ok}));
|
||||
let got_json = json!({
|
||||
"body_count": got.body_count, "volume_mm3": got.volume, "area_mm2": got.area,
|
||||
"center_of_mass": got.com, "bbox": {"min": got.bbox.0, "max": got.bbox.1}, "faces": got.faces,
|
||||
});
|
||||
let Some(want) = want else {
|
||||
return json!({"case": name, "pass": false, "skipped": true, "error": "no expected.json yet", "got": got_json});
|
||||
};
|
||||
// Cases where Fusion's part is itself an approximation: compare size loosely, not topology.
|
||||
let approx = APPROXIMATE.iter().find(|(c, _, _)| *c == name);
|
||||
let tol = approx.map(|(_, t, _)| *t).unwrap_or(REL_TOL);
|
||||
let within = |k: &str, rel: f64| match (got["total"][k].as_f64(), want["total"][k].as_f64()) {
|
||||
(Some(g), Some(w)) => (g - w).abs() <= rel * w.abs().max(1.0),
|
||||
_ => false,
|
||||
};
|
||||
let size_ok =
|
||||
got["body_count"] == want["body_count"] && within("volume_mm3", tol) && (FUSION_AREA_OFF.contains(&name.as_str()) || within("area_mm2", tol));
|
||||
num("body_count", got["body_count"].as_f64(), want["body_count"].as_f64(), 0.0);
|
||||
num("volume_mm3", got["total"]["volume_mm3"].as_f64(), want["total"]["volume_mm3"].as_f64(), tol);
|
||||
let area_off = FUSION_AREA_OFF.contains(&name.as_str());
|
||||
if !area_off {
|
||||
num("area_mm2", got["total"]["area_mm2"].as_f64(), want["total"]["area_mm2"].as_f64(), tol);
|
||||
let only: Option<Vec<String>> = want["compare"].as_array().map(|a| a.iter().filter_map(|x| x.as_str().map(str::to_string)).collect());
|
||||
let wanted = |k: &str| want.get(k).is_some_and(|v| !v.is_null()) && only.as_ref().is_none_or(|o| o.iter().any(|x| x == k));
|
||||
let rel = want["tolerance"]["rel"].as_f64().unwrap_or(REL_TOL);
|
||||
let diag = |b: ([f64; 3], [f64; 3])| ((b.1[0] - b.0[0]).powi(2) + (b.1[1] - b.0[1]).powi(2) + (b.1[2] - b.0[2]).powi(2)).sqrt();
|
||||
let want_bbox = v3(&want["bbox"]["min"]).zip(v3(&want["bbox"]["max"]));
|
||||
let abs = want["tolerance"]["abs_mm"].as_f64().unwrap_or_else(|| ABS_FRAC * want_bbox.map(diag).unwrap_or_else(|| diag(got.bbox)).max(1.0));
|
||||
let mut c = Checks { list: Vec::new(), pass: true };
|
||||
if wanted("body_count") {
|
||||
let ok = want["body_count"].as_u64() == Some(got.body_count as u64);
|
||||
c.push("body_count", json!(got.body_count), want["body_count"].clone(), ok);
|
||||
}
|
||||
let mut seam_note = None;
|
||||
if approx.is_none() {
|
||||
let exact = ["faces", "edges", "vertices"].iter().all(|k| got["total"][k].as_f64().is_some() && got["total"][k] == want["total"][k]);
|
||||
let fusion_seams = want["bodies"]
|
||||
.as_array()
|
||||
.and_then(|bs| bs.iter().map(seams::normalised).try_fold((0, 0, 0), |a, n| n.map(|n| (a.0 + n.0, a.1 + n.1, a.2 + n.2))));
|
||||
match fusion_seams {
|
||||
// Size matches and only the topology differs: compare with Fusion's seam splits merged.
|
||||
Some((f, e, v)) if size_ok && !exact => {
|
||||
let raw: Vec<String> = ["faces", "edges", "vertices"]
|
||||
.iter()
|
||||
.map(|k| {
|
||||
let ours: i64 = got["bodies"].as_array().into_iter().flatten().filter_map(|b| b["kernel"][k].as_i64()).sum();
|
||||
format!("{k} {ours}/{}", want["total"][k])
|
||||
})
|
||||
.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 SolidCraft/Fusion: {})",
|
||||
want["total"]["faces"],
|
||||
want["total"]["edges"],
|
||||
want["total"]["vertices"],
|
||||
raw.join(", ")
|
||||
));
|
||||
num("faces (seam-normalised)", got["total"]["faces"].as_f64(), Some(f as f64), 0.0);
|
||||
num("edges (seam-normalised)", got["total"]["edges"].as_f64(), Some(e as f64), 0.0);
|
||||
num("vertices (seam-normalised)", got["total"]["vertices"].as_f64(), Some(v as f64), 0.0);
|
||||
}
|
||||
_ => {
|
||||
num("faces", got["total"]["faces"].as_f64(), want["total"]["faces"].as_f64(), 0.0);
|
||||
num("edges", got["total"]["edges"].as_f64(), want["total"]["edges"].as_f64(), 0.0);
|
||||
num("vertices", got["total"]["vertices"].as_f64(), want["total"]["vertices"].as_f64(), 0.0);
|
||||
}
|
||||
}
|
||||
if wanted("volume_mm3") {
|
||||
c.rel("volume_mm3", got.volume, want["volume_mm3"].as_f64(), rel);
|
||||
}
|
||||
if wanted("area_mm2") {
|
||||
c.rel("area_mm2", got.area, want["area_mm2"].as_f64(), rel);
|
||||
}
|
||||
if wanted("center_of_mass") {
|
||||
c.vec("center_of_mass", got.com, v3(&want["center_of_mass"]), abs);
|
||||
}
|
||||
if wanted("bbox") {
|
||||
c.vec("bbox.min", got.bbox.0, want_bbox.map(|b| b.0), abs);
|
||||
c.vec("bbox.max", got.bbox.1, want_bbox.map(|b| b.1), abs);
|
||||
}
|
||||
if wanted("faces") {
|
||||
let ok = want["faces"].as_u64() == Some(got.faces);
|
||||
c.push("faces", json!(got.faces), want["faces"].clone(), ok);
|
||||
}
|
||||
let stl = format!("{dir}/reference.stl");
|
||||
if std::path::Path::new(&stl).is_file() && only.as_ref().is_none_or(|o| o.iter().any(|x| x == "surface")) {
|
||||
let tol = want["tolerance"]["surface_mm"].as_f64().unwrap_or(0.05);
|
||||
reference_checks(&s, &stl, tol, &mut c);
|
||||
}
|
||||
// Our STEP export of the rebuilt part: well formed, and read back with no warnings and the
|
||||
// same volume.
|
||||
let (ok, note) = step_export_check(&s, got["total"]["volume_mm3"].as_f64().unwrap_or(0.0));
|
||||
pass &= ok;
|
||||
checks.push(json!({"check": "step_export", "got": note, "want": "valid, re-imports cleanly", "ok": ok}));
|
||||
let mut out = json!({"case": name, "pass": pass, "checks": checks});
|
||||
if let Some((_, _, why)) = approx {
|
||||
out["note"] = json!(why);
|
||||
}
|
||||
if let Some(n) = seam_note {
|
||||
let (ok, note) = step_export_check(&s, got.volume);
|
||||
c.push("step_export", json!(note), json!("valid, re-imports cleanly"), ok);
|
||||
let mut out = json!({"case": name, "pass": c.pass, "source": want["source"], "checks": c.list, "got": got_json});
|
||||
if let Some(n) = want["note"].as_str() {
|
||||
out["note"] = json!(n);
|
||||
}
|
||||
if area_off {
|
||||
out["note"] = json!(
|
||||
"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"
|
||||
);
|
||||
}
|
||||
let effective: Vec<String> = recipe["features"]
|
||||
.as_array()
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter(|f| f.get("fusion_effective_extent").is_some())
|
||||
.map(|f| f["name"].as_str().unwrap_or("?").to_string())
|
||||
.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; SolidCraft's through-all goes truly through)",
|
||||
effective.join(", ")
|
||||
));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
@@ -163,77 +183,43 @@ fn step_export_check(s: &solvecraft_engine::Session, volume: f64) -> (bool, Stri
|
||||
}
|
||||
}
|
||||
|
||||
/// Cases whose Fusion area is not the geometric one: Fusion reports the horn torus face of a
|
||||
/// tight sweep (tube touching its axis) at three times π²·R·r, its quarter-turn area; the
|
||||
/// volumes agree. Their area is not compared.
|
||||
const FUSION_AREA_OFF: &[&str] = &["50-sweep-tight-bend"];
|
||||
|
||||
/// Import a case's `part.step` (as File → Open does) and compare with Fusion's measurements:
|
||||
/// body count, volume and area (relative 1e-3) and the faces read from the file.
|
||||
/// Open the case's `part.step` (or `part.igs` with `iges`) and compare with measure.json.
|
||||
fn check_step_case(dir: &str, iges: bool) -> Value {
|
||||
let name = std::path::Path::new(dir).file_name().map(|x| x.to_string_lossy().to_string()).unwrap_or_default();
|
||||
let sp = format!("{dir}/{}", if iges { "part.igs" } else { "part.step" });
|
||||
let want = match read_json(&format!("{dir}/measure.json")) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return json!({"case": name, "pass": false, "error": e}),
|
||||
};
|
||||
let t0 = std::time::Instant::now();
|
||||
let mut s = solvecraft_engine::Session::default();
|
||||
let opened = match s.execute("doc.open", &json!({"path": sp})) {
|
||||
Ok(v) => v,
|
||||
Err(e) => return json!({"case": name, "pass": false, "error": e.to_string()}),
|
||||
};
|
||||
let got = match s.execute("inspect.measure", &json!({})) {
|
||||
Ok(m) => m,
|
||||
Err(e) => return json!({"case": name, "pass": false, "error": e.to_string()}),
|
||||
};
|
||||
let ms = t0.elapsed().as_secs_f64() * 1000.0;
|
||||
// Faces read from the file (the kernel may split a face that wraps around a closed surface).
|
||||
let file_faces = std::fs::read(&sp)
|
||||
.ok()
|
||||
.and_then(|b| {
|
||||
let text = String::from_utf8_lossy(&b);
|
||||
if iges {
|
||||
solvecraft_engine::kernel::iges_import(&text).ok().map(std::sync::Arc::new)
|
||||
} else {
|
||||
solvecraft_engine::kernel::step_import_shared(&text).ok()
|
||||
/// Our part as a triangle soup (curved faces refined onto the surface).
|
||||
fn our_mesh(s: &solvecraft_engine::Session) -> Result<Vec<Tri>, String> {
|
||||
let mut ours: Vec<Tri> = Vec::new();
|
||||
for b in &s.world_state().bodies {
|
||||
let m = b.body.tessellate_with(MESH_TOL, true).map_err(|e| e.to_string())?;
|
||||
let p = |i: u32| m.positions.get(i as usize).map(|v: &Vec3| [v.x, v.y, v.z]);
|
||||
for t in &m.triangles {
|
||||
if let (Some(a), Some(b), Some(c)) = (p(t[0]), p(t[1]), p(t[2])) {
|
||||
ours.push([a, b, c]);
|
||||
}
|
||||
})
|
||||
.map(|imp| imp.bodies.iter().map(|b| b.file_faces).sum::<usize>() as f64);
|
||||
let mut checks = Vec::new();
|
||||
let mut pass = true;
|
||||
let mut num = |what: &str, g: Option<f64>, w: Option<f64>, rel: f64| {
|
||||
let ok = match (g, w) {
|
||||
(Some(g), Some(w)) => (g - w).abs() <= rel * w.abs().max(1.0),
|
||||
_ => false,
|
||||
};
|
||||
pass &= ok;
|
||||
checks.push(json!({"check": what, "got": g, "want": w, "ok": ok}));
|
||||
};
|
||||
num("body_count", got["body_count"].as_f64(), want["body_count"].as_f64(), 0.0);
|
||||
num("volume_mm3", got["total"]["volume_mm3"].as_f64(), want["total"]["volume_mm3"].as_f64(), REL_TOL);
|
||||
if !FUSION_AREA_OFF.contains(&name.as_str()) {
|
||||
num("area_mm2", got["total"]["area_mm2"].as_f64(), want["total"]["area_mm2"].as_f64(), REL_TOL);
|
||||
}
|
||||
}
|
||||
num("faces", file_faces, want["total"]["faces"].as_f64(), 0.0);
|
||||
json!({
|
||||
"case": name, "pass": pass, "checks": checks, "ms": ms,
|
||||
"bodies": opened["bodies"], "warnings": opened["warnings"],
|
||||
"info": {"kernel_faces_merged": got["total"]["faces"], "edges": got["total"]["edges"], "want_edges": want["total"]["edges"], "vertices": got["total"]["vertices"], "want_vertices": want["total"]["vertices"]},
|
||||
})
|
||||
if ours.is_empty() { Err("empty mesh".into()) } else { Ok(ours) }
|
||||
}
|
||||
|
||||
pub fn step_corpus(args: &[String]) -> Result<(), String> {
|
||||
let dirs: Vec<&String> = args.iter().filter(|a| !a.starts_with("--")).collect();
|
||||
if dirs.is_empty() {
|
||||
return Err("step-corpus: give one or more case directories (each with part.step and measure.json)".into());
|
||||
}
|
||||
let iges = args.iter().any(|a| a == "--iges");
|
||||
let results: Vec<Value> = dirs.iter().map(|d| check_step_case(d, iges)).collect();
|
||||
report(args, &results);
|
||||
let failed = results.iter().filter(|r| !r["pass"].as_bool().unwrap_or(false)).count();
|
||||
if failed > 0 { Err(format!("{failed} STEP case(s) failed")) } else { Ok(()) }
|
||||
/// Compare our part with the reference triangulation: the symmetric surface distance, and the
|
||||
/// principal second moments of volume of both meshes (the shape's inertia, independent of how it
|
||||
/// sits in space).
|
||||
fn reference_checks(s: &solvecraft_engine::Session, stl: &str, tol: f64, c: &mut Checks) {
|
||||
let (ours, reference) = match (our_mesh(s), spatial::read_stl(stl)) {
|
||||
(Ok(o), Ok(r)) if !r.is_empty() => (o, r),
|
||||
(Err(e), _) | (_, Err(e)) => return c.push("surface_distance_mm", json!(e), json!(format!("<= {tol}")), false),
|
||||
_ => return c.push("surface_distance_mm", json!("empty reference.stl"), json!(format!("<= {tol}")), false),
|
||||
};
|
||||
let (lo, hi) = spatial::bounds(&reference);
|
||||
let size = (0..3).map(|k| hi[k] - lo[k]).fold(0.0, f64::max);
|
||||
let h = spatial::hausdorff(&ours, &reference, size.max(1.0));
|
||||
let worst = h.a_to_b.max(h.b_to_a);
|
||||
c.push(
|
||||
"surface_distance_mm",
|
||||
json!({"max": worst, "mean": h.mean, "ours_to_reference": h.a_to_b, "reference_to_ours": h.b_to_a, "worst_at": h.worst_at}),
|
||||
json!(format!("<= {tol}")),
|
||||
worst <= tol,
|
||||
);
|
||||
let ((_, _, pa), (_, _, pb)) = (spatial::mass_properties(&ours), spatial::mass_properties(&reference));
|
||||
let ok = pa.iter().zip(pb).all(|(a, b)| (a - b).abs() <= INERTIA_REL * b.abs().max(1.0));
|
||||
c.push("principal_moments_mm5", json!(pa), json!(pb), ok);
|
||||
}
|
||||
|
||||
fn report(args: &[String], results: &[Value]) {
|
||||
@@ -268,7 +254,7 @@ fn report(args: &[String], results: &[Value]) {
|
||||
pub fn run(args: &[String]) -> Result<(), String> {
|
||||
let dirs: Vec<&String> = args.iter().filter(|a| !a.starts_with("--")).collect();
|
||||
if dirs.is_empty() {
|
||||
return Err("oracle: give one or more case directories".into());
|
||||
return Err("oracle: give one or more case directories (each with recipe.json and expected.json)".into());
|
||||
}
|
||||
let results: Vec<Value> = dirs.iter().map(|d| check_case(d)).collect();
|
||||
report(args, &results);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Oracle recipes (`plan/fusion/oracle/*/recipe.json`) → SolveCraft command scripts.
|
||||
//! Reference-test recipes (`tests/oracle/*/recipe.json`) → SolidCraft command scripts.
|
||||
//!
|
||||
//! A recipe lists features with sketch entities in sketch-local coordinates; point references
|
||||
//! (`l1.end`) share points. Each feature becomes one or more commands.
|
||||
@@ -85,14 +85,6 @@ fn sketch_cmds(f: &Value, out: &mut Vec<Value>) -> Result<(), String> {
|
||||
|
||||
/// Extent parameters for the Extrude command.
|
||||
fn extent(f: &Value, p: &mut Value) {
|
||||
// Where Fusion's own result differs from the geometric meaning (its through-all is sized
|
||||
// from vertices), the recipe records the extent Fusion actually used: replay that.
|
||||
if let Some(fx) = f.get("fusion_effective_extent").filter(|x| x.get("type").and_then(Value::as_str) == Some("distance")) {
|
||||
let dir = fx.get("direction").and_then(Value::as_str).unwrap_or("positive");
|
||||
p["distance"] = fx.get("distance").cloned().unwrap_or(Value::Null);
|
||||
p["direction"] = json!(if dir.starts_with("negative") { "negative" } else { "positive" });
|
||||
return;
|
||||
}
|
||||
let e = f.get("extent").cloned().unwrap_or(Value::Null);
|
||||
let ty = e.get("type").and_then(Value::as_str).unwrap_or("distance");
|
||||
let dist = e.get("distance").cloned().or_else(|| f.get("distance").cloned()).unwrap_or(Value::Null);
|
||||
@@ -545,8 +537,8 @@ fn edge_points(f: &Value) -> Value {
|
||||
)
|
||||
}
|
||||
|
||||
/// Fusion names a pattern's source features with an occurrence suffix ("Hole1 (1)"); the
|
||||
/// features are the recipe's own names.
|
||||
/// A pattern's source features may carry an occurrence suffix ("Hole1 (1)"); the features are
|
||||
/// the recipe's own names.
|
||||
fn feature_refs(v: Option<&Value>) -> Value {
|
||||
let list: Vec<Value> = v
|
||||
.and_then(Value::as_array)
|
||||
|
||||
@@ -1,321 +0,0 @@
|
||||
//! Seam-normalised topology of Fusion's measurements.
|
||||
//!
|
||||
//! Fusion splits a closed surface along seams: a full cylinder can be two half faces, and a
|
||||
//! closed curve crossing a seam becomes two edges with a vertex on the seam. SolveCraft's merged
|
||||
//! counts already count each closed surface patch once and ignore seam edges and vertices, so
|
||||
//! the oracle normalises Fusion's side the same way from its face, edge and vertex lists:
|
||||
//! - faces cut from one periodic surface (cylinder, cone, sphere, torus) and separated only by
|
||||
//! seam edges count once;
|
||||
//! - a seam edge lies on that surface and on no other face's surface: it is dropped;
|
||||
//! - a vertex left with exactly two edges is no corner (a solid's corners have three or more):
|
||||
//! the two edges join, and a curve that closes on itself keeps one vertex.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
use serde_json::Value;
|
||||
|
||||
type V3 = [f64; 3];
|
||||
|
||||
const TOL: f64 = 1e-4;
|
||||
|
||||
fn v3(v: &Value) -> Option<V3> {
|
||||
let a = v.as_array()?;
|
||||
Some([a.first()?.as_f64()?, a.get(1)?.as_f64()?, a.get(2)?.as_f64()?])
|
||||
}
|
||||
fn sub(a: V3, b: V3) -> V3 {
|
||||
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
|
||||
}
|
||||
fn add(a: V3, b: V3) -> V3 {
|
||||
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
|
||||
}
|
||||
fn mul(a: V3, s: f64) -> V3 {
|
||||
[a[0] * s, a[1] * s, a[2] * s]
|
||||
}
|
||||
fn dot(a: V3, b: V3) -> f64 {
|
||||
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||
}
|
||||
fn cross(a: V3, b: V3) -> V3 {
|
||||
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
|
||||
}
|
||||
fn len(a: V3) -> f64 {
|
||||
dot(a, a).sqrt()
|
||||
}
|
||||
fn unit(a: V3) -> Option<V3> {
|
||||
let l = len(a);
|
||||
(l > 1e-12).then(|| mul(a, 1.0 / l))
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
enum Surf {
|
||||
Plane { p: V3, n: V3 },
|
||||
Cylinder { o: V3, a: V3, r: f64 },
|
||||
Cone { o: V3, a: V3, r: f64, tan: f64 },
|
||||
Sphere { c: V3, r: f64 },
|
||||
Torus { c: V3, a: V3, big: f64, small: f64 },
|
||||
Other,
|
||||
}
|
||||
|
||||
impl Surf {
|
||||
fn of(f: &Value) -> Surf {
|
||||
let g = |k: &str| v3(&f[k]);
|
||||
let n = |k: &str| f[k].as_f64();
|
||||
let s = match f["type"].as_str().unwrap_or_default() {
|
||||
"plane" => g("centroid").zip(g("plane_normal_outward").and_then(unit)).map(|(p, n)| Surf::Plane { p, n }),
|
||||
"cylinder" => (|| Some(Surf::Cylinder { o: g("axis_origin")?, a: unit(g("axis")?)?, r: n("radius_mm")? }))(),
|
||||
"cone" => {
|
||||
(|| Some(Surf::Cone { o: g("axis_origin")?, a: unit(g("axis")?)?, r: n("radius_mm")?, tan: n("half_angle_deg")?.to_radians().tan() }))(
|
||||
)
|
||||
}
|
||||
"sphere" => (|| Some(Surf::Sphere { c: g("center")?, r: n("radius_mm")? }))(),
|
||||
"torus" => (|| Some(Surf::Torus { c: g("center")?, a: unit(g("axis")?)?, big: n("major_radius_mm")?, small: n("minor_radius_mm")? }))(),
|
||||
_ => None,
|
||||
};
|
||||
s.unwrap_or(Surf::Other)
|
||||
}
|
||||
|
||||
fn periodic(&self) -> bool {
|
||||
matches!(self, Surf::Cylinder { .. } | Surf::Cone { .. } | Surf::Sphere { .. } | Surf::Torus { .. })
|
||||
}
|
||||
|
||||
/// Whether the point lies on the surface; `None` when that can't be told (free-form faces).
|
||||
fn contains(&self, p: V3) -> Option<bool> {
|
||||
let radial = |o: V3, a: V3| {
|
||||
let d = sub(p, o);
|
||||
let t = dot(d, a);
|
||||
(t, len(sub(d, mul(a, t))))
|
||||
};
|
||||
Some(match *self {
|
||||
Surf::Plane { p: q, n } => dot(sub(p, q), n).abs() < TOL,
|
||||
Surf::Cylinder { o, a, r } => (radial(o, a).1 - r).abs() < TOL,
|
||||
Surf::Cone { o, a, r, tan } => {
|
||||
let (t, rho) = radial(o, a);
|
||||
(rho - (r + t * tan)).abs() < TOL || (rho - (r - t * tan)).abs() < TOL
|
||||
}
|
||||
Surf::Sphere { c, r } => (len(sub(p, c)) - r).abs() < TOL,
|
||||
Surf::Torus { c, a, big, small } => {
|
||||
let (t, rho) = radial(c, a);
|
||||
((rho - big).hypot(t) - small).abs() < TOL
|
||||
}
|
||||
Surf::Other => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn same(&self, o: &Surf) -> bool {
|
||||
let close = |a: f64, b: f64| (a - b).abs() < TOL;
|
||||
let pt = |a: V3, b: V3| len(sub(a, b)) < TOL;
|
||||
let par = |a: V3, b: V3| len(cross(a, b)) < TOL;
|
||||
// Same axis line: parallel, and either origin on the other's axis.
|
||||
let axis = |o1: V3, a1: V3, o2: V3, a2: V3| par(a1, a2) && len(cross(sub(o2, o1), a1)) < TOL;
|
||||
match (*self, *o) {
|
||||
(Surf::Cylinder { o: o1, a: a1, r: r1 }, Surf::Cylinder { o: o2, a: a2, r: r2 }) => axis(o1, a1, o2, a2) && close(r1, r2),
|
||||
(Surf::Cone { o: o1, a: a1, r: r1, tan: t1 }, Surf::Cone { .. }) => {
|
||||
// Same cone: the other's frame circle lies on this one, same opening angle.
|
||||
let Surf::Cone { o: o2, a: a2, r: r2, tan: t2 } = *o else { return false };
|
||||
axis(o1, a1, o2, a2) && close(t1, t2) && {
|
||||
let t = dot(sub(o2, o1), a1) * dot(a1, a2).signum();
|
||||
close(r2, r1 + t * t1) || close(r2, r1 - t * t1)
|
||||
}
|
||||
}
|
||||
(Surf::Sphere { c: c1, r: r1 }, Surf::Sphere { c: c2, r: r2 }) => pt(c1, c2) && close(r1, r2),
|
||||
(Surf::Torus { c: c1, a: a1, big: b1, small: s1 }, Surf::Torus { c: c2, a: a2, big: b2, small: s2 }) => {
|
||||
pt(c1, c2) && par(a1, a2) && close(b1, b2) && close(s1, s2)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Points along an edge from Fusion's edge list.
|
||||
fn edge_points(e: &Value) -> Vec<V3> {
|
||||
let (s, t) = (v3(&e["start"]), v3(&e["end"]));
|
||||
let mut pts: Vec<V3> = s.into_iter().chain(t).collect();
|
||||
let (c, r, a) = (v3(&e["center"]), e["radius_mm"].as_f64(), v3(&e["axis"]).and_then(unit));
|
||||
match (e["type"].as_str().unwrap_or_default(), s, t) {
|
||||
("line", Some(s), Some(t)) => pts.push(mul(add(s, t), 0.5)),
|
||||
("arc", Some(s), Some(t)) => {
|
||||
if let (Some(c), Some(r)) = (c, r)
|
||||
&& let Some(m) = unit(add(sub(s, c), sub(t, c)))
|
||||
{
|
||||
let long = e["length_mm"].as_f64().is_some_and(|l| l > std::f64::consts::PI * r);
|
||||
pts.push(add(c, mul(m, if long { -r } else { r })));
|
||||
}
|
||||
}
|
||||
("circle", Some(s), _) => {
|
||||
if let (Some(c), Some(a)) = (c, a) {
|
||||
let d = sub(s, c);
|
||||
for th in [2.0 * std::f64::consts::FRAC_PI_3, 4.0 * std::f64::consts::FRAC_PI_3] {
|
||||
pts.push(add(c, add(mul(d, th.cos()), mul(cross(a, d), th.sin()))));
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
pts
|
||||
}
|
||||
|
||||
fn key(p: V3) -> [i64; 3] {
|
||||
p.map(|x| (x * 1e6).round() as i64)
|
||||
}
|
||||
|
||||
fn find(p: &mut [usize], mut i: usize) -> usize {
|
||||
while let Some(&q) = p.get(i) {
|
||||
if q == i {
|
||||
break;
|
||||
}
|
||||
i = q;
|
||||
}
|
||||
i
|
||||
}
|
||||
|
||||
/// Faces, edges and vertices of one Fusion body after merging seam splits; `None` when the
|
||||
/// lists are missing.
|
||||
pub fn normalised(body: &Value) -> Option<(usize, usize, usize)> {
|
||||
let faces = body["face_list"].as_array()?;
|
||||
let edges = body["edge_list"].as_array()?;
|
||||
let surfs: Vec<Surf> = faces.iter().map(Surf::of).collect();
|
||||
// Group faces cut from one periodic surface.
|
||||
let mut group: Vec<usize> = (0..surfs.len()).collect();
|
||||
for i in 0..surfs.len() {
|
||||
for j in 0..i {
|
||||
if surfs[i].periodic() && surfs[i].same(&surfs[j]) {
|
||||
group[i] = group[j];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
let size = |g: usize| group.iter().filter(|x| **x == g).count();
|
||||
// Seam edges: on a split group's surface and on no other face's surface.
|
||||
let mut seams_in: HashMap<usize, usize> = HashMap::new();
|
||||
let mut seam = vec![false; edges.len()];
|
||||
for (ei, e) in edges.iter().enumerate() {
|
||||
let pts = edge_points(e);
|
||||
if pts.is_empty() {
|
||||
continue;
|
||||
}
|
||||
let on = |s: &Surf| pts.iter().map(|p| s.contains(*p)).try_fold(true, |acc, x| Some(acc && x?));
|
||||
let carriers: Vec<Option<bool>> = surfs.iter().map(on).collect();
|
||||
if carriers.contains(&None) {
|
||||
continue;
|
||||
}
|
||||
let groups: Vec<usize> = (0..surfs.len()).filter(|f| carriers[*f] == Some(true)).map(|f| group[f]).collect();
|
||||
if let Some(&g) = groups.first()
|
||||
&& groups.iter().all(|x| *x == g)
|
||||
&& surfs[g].periodic()
|
||||
&& size(g) > 1
|
||||
{
|
||||
seam[ei] = true;
|
||||
*seams_in.entry(g).or_insert(0) += 1;
|
||||
}
|
||||
}
|
||||
let mut face_count = 0;
|
||||
for g in 0..surfs.len() {
|
||||
let k = size(g);
|
||||
if k > 0 {
|
||||
face_count += k.saturating_sub(seams_in.get(&g).copied().unwrap_or(0)).max(1);
|
||||
}
|
||||
}
|
||||
// Vertex degrees over the remaining edges; a closed edge counts once at its vertex.
|
||||
let mut ends: Vec<(usize, Option<[i64; 3]>, Option<[i64; 3]>)> = Vec::new();
|
||||
for (ei, e) in edges.iter().enumerate() {
|
||||
if seam[ei] {
|
||||
continue;
|
||||
}
|
||||
let (s, t) = (v3(&e["start"]).map(key), v3(&e["end"]).map(key));
|
||||
let t = if t == s { None } else { t };
|
||||
ends.push((ends.len(), s, t));
|
||||
}
|
||||
// A seam between two closed curves (a closed face's seam whose surface we can't evaluate):
|
||||
// an open edge whose ends each meet only a closed edge. At a real vertex three edges meet.
|
||||
let closed_at = |v: &[i64; 3], ends: &[(usize, Option<[i64; 3]>, Option<[i64; 3]>)]| {
|
||||
let closed = ends.iter().filter(|(_, s, t)| t.is_none() && s.as_ref() == Some(v)).count();
|
||||
let open = ends.iter().filter(|(_, s, t)| t.is_some() && (s.as_ref() == Some(v) || t.as_ref() == Some(v))).count();
|
||||
closed == 1 && open == 1
|
||||
};
|
||||
let loop_seams: Vec<usize> =
|
||||
ends.iter().filter(|(_, s, t)| matches!((s, t), (Some(s), Some(t)) if closed_at(s, &ends) && closed_at(t, &ends))).map(|e| e.0).collect();
|
||||
ends.retain(|e| !loop_seams.contains(&e.0));
|
||||
for (k, e) in ends.iter_mut().enumerate() {
|
||||
e.0 = k;
|
||||
}
|
||||
let mut deg: HashMap<[i64; 3], Vec<usize>> = HashMap::new();
|
||||
for (i, s, t) in &ends {
|
||||
for v in [s, t].into_iter().flatten() {
|
||||
deg.entry(*v).or_default().push(*i);
|
||||
}
|
||||
}
|
||||
let mut ep: Vec<usize> = (0..ends.len()).collect();
|
||||
let mut merged: Vec<[i64; 3]> = Vec::new();
|
||||
for (v, es) in ° {
|
||||
// Two open edges meeting: one curve split at a seam.
|
||||
if let [i, j] = es[..]
|
||||
&& i != j
|
||||
&& ends.get(i).is_some_and(|e| e.2.is_some())
|
||||
&& ends.get(j).is_some_and(|e| e.2.is_some())
|
||||
{
|
||||
let (ri, rj) = (find(&mut ep, i), find(&mut ep, j));
|
||||
if let Some(x) = ep.get_mut(ri) {
|
||||
*x = rj;
|
||||
}
|
||||
merged.push(*v);
|
||||
}
|
||||
}
|
||||
let mut real: HashMap<usize, bool> = HashMap::new();
|
||||
for (i, s, t) in &ends {
|
||||
let r = find(&mut ep, *i);
|
||||
let has_real = [s, t].into_iter().flatten().any(|v| !merged.contains(v)) || (s.is_none() && t.is_none());
|
||||
*real.entry(r).or_insert(false) |= has_real;
|
||||
}
|
||||
let closed = real.values().filter(|r| !**r).count();
|
||||
let vertices = deg.keys().filter(|v| !merged.contains(v)).count() + closed;
|
||||
Some((face_count, real.len(), vertices))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
/// A cylinder (r 5, height 10) split by Fusion into two halves along two seams.
|
||||
#[test]
|
||||
fn split_cylinder_counts_once() {
|
||||
let cyl = json!({"type": "cylinder", "axis_origin": [0, 0, 0], "axis": [0, 0, 1], "radius_mm": 5.0});
|
||||
let plane = |z: f64, n: f64| json!({"type": "plane", "centroid": [0, 0, z], "plane_normal_outward": [0, 0, n]});
|
||||
let arc = |z: f64, s: [f64; 3], t: [f64; 3]| json!({"type": "arc", "start": s, "end": t, "center": [0, 0, z], "radius_mm": 5.0, "axis": [0, 0, 1], "length_mm": 15.707963});
|
||||
let line = |x: f64| json!({"type": "line", "start": [x, 0, 0], "end": [x, 0, 10]});
|
||||
let body = json!({
|
||||
"face_list": [cyl.clone(), cyl, plane(0.0, -1.0), plane(10.0, 1.0)],
|
||||
"edge_list": [line(5.0), line(-5.0), arc(0.0, [5.0, 0.0, 0.0], [-5.0, 0.0, 0.0]), arc(0.0, [-5.0, 0.0, 0.0], [5.0, 0.0, 0.0]), arc(10.0, [5.0, 0.0, 10.0], [-5.0, 0.0, 10.0]), arc(10.0, [-5.0, 0.0, 10.0], [5.0, 0.0, 10.0])],
|
||||
});
|
||||
assert_eq!(normalised(&body), Some((3, 2, 2)));
|
||||
}
|
||||
|
||||
/// Oracle 35: a fillet (free-form face, surface unknown) between two cylinders is bounded by
|
||||
/// two closed contact curves joined by its seam arc. The arc goes; the curves and circles stay.
|
||||
#[test]
|
||||
fn seam_between_closed_curves_on_an_unknown_surface() {
|
||||
let circle =
|
||||
|s: [f64; 3], c: [f64; 3], a: [f64; 3]| json!({"type": "circle", "start": s, "end": s, "center": c, "radius_mm": 1.0, "axis": a});
|
||||
let lp = |p: [f64; 3]| json!({"type": "nurbs", "start": p, "end": p});
|
||||
let (p, q) = ([0.0, -10.833333333, 10.374916332], [0.0, -10.0, 12.449899598]);
|
||||
let body = json!({
|
||||
"face_list": [{"type": "nurbs"}],
|
||||
"edge_list": [
|
||||
{"type": "arc", "start": p, "end": q, "center": [0.0, -13.0, 12.449899598], "radius_mm": 3.0, "axis": [1, 0, 0]},
|
||||
circle([-40.0, 0.0, 15.0], [-40.0, 0.0, 0.0], [-1.0, 0.0, 0.0]),
|
||||
circle([-10.0, 0.0, 35.0], [0.0, 0.0, 35.0], [0.0, 0.0, 1.0]),
|
||||
circle([40.0, 0.0, 15.0], [40.0, 0.0, 0.0], [1.0, 0.0, 0.0]),
|
||||
lp(p),
|
||||
lp(q),
|
||||
],
|
||||
});
|
||||
assert_eq!(normalised(&body), Some((1, 5, 5)));
|
||||
}
|
||||
|
||||
/// A closed curve split into two edges at a seam vertex joins again (Fusion part 61's keyway).
|
||||
#[test]
|
||||
fn degree_two_vertices_join() {
|
||||
let n = |s: [f64; 3], t: [f64; 3]| json!({"type": "nurbs", "start": s, "end": t});
|
||||
let body = json!({"face_list": [], "edge_list": [n([0.0, 0.0, 0.0], [1.0, 0.0, 0.0]), n([1.0, 0.0, 0.0], [0.0, 0.0, 0.0])]});
|
||||
assert_eq!(normalised(&body), Some((0, 1, 1)));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
//! Geometry comparison helpers: an STL reader, bounding boxes, volume, centre of mass and principal
|
||||
//! second moments of a closed triangle mesh, and a sampled symmetric Hausdorff distance between two
|
||||
//! triangle meshes.
|
||||
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// A triangle soup in mm.
|
||||
pub type Tri = [[f64; 3]; 3];
|
||||
|
||||
fn sub(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
|
||||
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
|
||||
}
|
||||
fn add(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
|
||||
[a[0] + b[0], a[1] + b[1], a[2] + b[2]]
|
||||
}
|
||||
fn scale(a: [f64; 3], s: f64) -> [f64; 3] {
|
||||
[a[0] * s, a[1] * s, a[2] * s]
|
||||
}
|
||||
fn dot(a: [f64; 3], b: [f64; 3]) -> f64 {
|
||||
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
|
||||
}
|
||||
fn cross(a: [f64; 3], b: [f64; 3]) -> [f64; 3] {
|
||||
[a[1] * b[2] - a[2] * b[1], a[2] * b[0] - a[0] * b[2], a[0] * b[1] - a[1] * b[0]]
|
||||
}
|
||||
|
||||
/// Read a binary (or ASCII) STL file.
|
||||
pub fn read_stl(path: &str) -> Result<Vec<Tri>, String> {
|
||||
let b = std::fs::read(path).map_err(|e| format!("{path}: {e}"))?;
|
||||
if b.len() >= 84 {
|
||||
let n = u32::from_le_bytes([b[80], b[81], b[82], b[83]]) as usize;
|
||||
if b.len() == 84 + 50 * n {
|
||||
let f = |o: usize| f64::from(f32::from_le_bytes([b[o], b[o + 1], b[o + 2], b[o + 3]]));
|
||||
return Ok((0..n)
|
||||
.map(|i| {
|
||||
let o = 84 + 50 * i + 12;
|
||||
let v = |k: usize| [f(o + 12 * k), f(o + 12 * k + 4), f(o + 12 * k + 8)];
|
||||
[v(0), v(1), v(2)]
|
||||
})
|
||||
.collect());
|
||||
}
|
||||
}
|
||||
let text = String::from_utf8_lossy(&b);
|
||||
let mut verts = Vec::new();
|
||||
for line in text.lines() {
|
||||
let mut it = line.split_whitespace();
|
||||
if it.next() == Some("vertex") {
|
||||
let c: Vec<f64> = it.filter_map(|x| x.parse().ok()).collect();
|
||||
if c.len() == 3 {
|
||||
verts.push([c[0], c[1], c[2]]);
|
||||
}
|
||||
}
|
||||
}
|
||||
if verts.is_empty() || verts.len() % 3 != 0 {
|
||||
return Err(format!("{path}: not an STL file"));
|
||||
}
|
||||
Ok(verts.chunks(3).map(|c| [c[0], c[1], c[2]]).collect())
|
||||
}
|
||||
|
||||
/// Volume, centre of mass and the principal second moments of volume (mm^5, sorted ascending) of a
|
||||
/// closed, outward-oriented triangle soup, by signed tetrahedra from the origin.
|
||||
pub fn mass_properties(tris: &[Tri]) -> (f64, [f64; 3], [f64; 3]) {
|
||||
let (mut v6, mut m1) = (0.0, [0.0; 3]);
|
||||
let mut c = [[0.0; 3]; 3]; // ∫ x xᵀ dV
|
||||
for t in tris {
|
||||
let d = dot(t[0], cross(t[1], t[2]));
|
||||
v6 += d;
|
||||
let s = add(add(t[0], t[1]), t[2]);
|
||||
m1 = add(m1, scale(s, d / 24.0));
|
||||
for (i, row) in c.iter_mut().enumerate() {
|
||||
for (j, cell) in row.iter_mut().enumerate() {
|
||||
let vv: f64 = t.iter().map(|p| p[i] * p[j]).sum::<f64>() + s[i] * s[j];
|
||||
*cell += d / 120.0 * vv;
|
||||
}
|
||||
}
|
||||
}
|
||||
let vol = v6 / 6.0;
|
||||
if vol.abs() < 1e-12 {
|
||||
return (0.0, [0.0; 3], [0.0; 3]);
|
||||
}
|
||||
let com = scale(m1, 1.0 / vol);
|
||||
// Central covariance and inertia tensor I = tr(C)·1 − C.
|
||||
let mut cc = c;
|
||||
for (i, row) in cc.iter_mut().enumerate() {
|
||||
for (j, cell) in row.iter_mut().enumerate() {
|
||||
*cell -= vol * com[i] * com[j];
|
||||
}
|
||||
}
|
||||
let tr = cc[0][0] + cc[1][1] + cc[2][2];
|
||||
let mut inertia = [[0.0; 3]; 3];
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
inertia[i][j] = if i == j { tr - cc[i][j] } else { -cc[i][j] };
|
||||
}
|
||||
}
|
||||
let mut ev = eigen_sym3(inertia);
|
||||
ev.sort_by(f64::total_cmp);
|
||||
(vol, com, ev)
|
||||
}
|
||||
|
||||
/// Eigenvalues of a symmetric 3×3 matrix (Jacobi rotations).
|
||||
fn eigen_sym3(mut a: [[f64; 3]; 3]) -> [f64; 3] {
|
||||
for _ in 0..50 {
|
||||
let (mut p, mut q, mut big) = (0, 1, 0.0);
|
||||
for (i, j) in [(0, 1), (0, 2), (1, 2)] {
|
||||
if a[i][j].abs() > big {
|
||||
(p, q, big) = (i, j, a[i][j].abs());
|
||||
}
|
||||
}
|
||||
if big <= 1e-15 * (a[0][0].abs() + a[1][1].abs() + a[2][2].abs()).max(1e-300) {
|
||||
break;
|
||||
}
|
||||
let theta = (a[q][q] - a[p][p]) / (2.0 * a[p][q]);
|
||||
let t = theta.signum() / (theta.abs() + (theta * theta + 1.0).sqrt());
|
||||
let t = if theta == 0.0 { 1.0 } else { t };
|
||||
let (cs, sn) = (1.0 / (t * t + 1.0).sqrt(), t / (t * t + 1.0).sqrt());
|
||||
let mut b = a;
|
||||
for k in 0..3 {
|
||||
b[k][p] = cs * a[k][p] - sn * a[k][q];
|
||||
b[k][q] = sn * a[k][p] + cs * a[k][q];
|
||||
}
|
||||
let mut c2 = b;
|
||||
for k in 0..3 {
|
||||
c2[p][k] = cs * b[p][k] - sn * b[q][k];
|
||||
c2[q][k] = sn * b[p][k] + cs * b[q][k];
|
||||
}
|
||||
a = c2;
|
||||
}
|
||||
[a[0][0], a[1][1], a[2][2]]
|
||||
}
|
||||
|
||||
/// Axis-aligned bounds of a triangle soup.
|
||||
pub fn bounds(tris: &[Tri]) -> ([f64; 3], [f64; 3]) {
|
||||
let (mut lo, mut hi) = ([f64::INFINITY; 3], [f64::NEG_INFINITY; 3]);
|
||||
for p in tris.iter().flatten() {
|
||||
for k in 0..3 {
|
||||
lo[k] = lo[k].min(p[k]);
|
||||
hi[k] = hi[k].max(p[k]);
|
||||
}
|
||||
}
|
||||
(lo, hi)
|
||||
}
|
||||
|
||||
/// Distance from a point to a triangle (Ericson, Real-Time Collision Detection 5.1.5).
|
||||
fn point_tri(p: [f64; 3], t: &Tri) -> f64 {
|
||||
let (a, b, c) = (t[0], t[1], t[2]);
|
||||
let (ab, ac, ap) = (sub(b, a), sub(c, a), sub(p, a));
|
||||
let (d1, d2) = (dot(ab, ap), dot(ac, ap));
|
||||
let q = if d1 <= 0.0 && d2 <= 0.0 {
|
||||
a
|
||||
} else {
|
||||
let bp = sub(p, b);
|
||||
let (d3, d4) = (dot(ab, bp), dot(ac, bp));
|
||||
if d3 >= 0.0 && d4 <= d3 {
|
||||
b
|
||||
} else {
|
||||
let vc = d1 * d4 - d3 * d2;
|
||||
if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
|
||||
add(a, scale(ab, d1 / (d1 - d3)))
|
||||
} else {
|
||||
let cp = sub(p, c);
|
||||
let (d5, d6) = (dot(ab, cp), dot(ac, cp));
|
||||
if d6 >= 0.0 && d5 <= d6 {
|
||||
c
|
||||
} else {
|
||||
let vb = d5 * d2 - d1 * d6;
|
||||
if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
|
||||
add(a, scale(ac, d2 / (d2 - d6)))
|
||||
} else {
|
||||
let va = d3 * d6 - d5 * d4;
|
||||
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
|
||||
add(b, scale(sub(c, b), (d4 - d3) / ((d4 - d3) + (d5 - d6))))
|
||||
} else {
|
||||
let den = va + vb + vc;
|
||||
if den.abs() < 1e-300 { a } else { add(a, add(scale(ab, vb / den), scale(ac, vc / den))) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
let d = sub(p, q);
|
||||
dot(d, d).sqrt()
|
||||
}
|
||||
|
||||
/// Uniform grid of triangles for distance queries up to `cap`.
|
||||
struct Grid<'a> {
|
||||
tris: &'a [Tri],
|
||||
cell: f64,
|
||||
cells: HashMap<(i64, i64, i64), Vec<u32>>,
|
||||
}
|
||||
|
||||
impl<'a> Grid<'a> {
|
||||
fn new(tris: &'a [Tri], cell: f64) -> Self {
|
||||
let mut cells: HashMap<(i64, i64, i64), Vec<u32>> = HashMap::new();
|
||||
let key = |x: f64| (x / cell).floor() as i64;
|
||||
for (i, t) in tris.iter().enumerate() {
|
||||
let (lo, hi) = bounds(std::slice::from_ref(t));
|
||||
for x in key(lo[0])..=key(hi[0]) {
|
||||
for y in key(lo[1])..=key(hi[1]) {
|
||||
for z in key(lo[2])..=key(hi[2]) {
|
||||
cells.entry((x, y, z)).or_default().push(i as u32);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Grid { tris, cell, cells }
|
||||
}
|
||||
|
||||
/// Distance from `p` to the nearest triangle, or `cap` if none is that close. Searches shells
|
||||
/// of cells outwards and stops once the nearest hit is closer than the unsearched shells.
|
||||
fn distance(&self, p: [f64; 3], cap: f64, seen: &mut [u32], stamp: u32) -> f64 {
|
||||
let rmax = (cap / self.cell).ceil() as i64;
|
||||
let k = |x: f64| (x / self.cell).floor() as i64;
|
||||
let (cx, cy, cz) = (k(p[0]), k(p[1]), k(p[2]));
|
||||
let mut best = cap;
|
||||
for r in 0..=rmax {
|
||||
for x in cx - r..=cx + r {
|
||||
for y in cy - r..=cy + r {
|
||||
for z in cz - r..=cz + r {
|
||||
// only the shell at Chebyshev distance r
|
||||
if (x - cx).abs() != r && (y - cy).abs() != r && (z - cz).abs() != r {
|
||||
continue;
|
||||
}
|
||||
let Some(list) = self.cells.get(&(x, y, z)) else { continue };
|
||||
for &i in list {
|
||||
let Some(slot) = seen.get_mut(i as usize) else { continue };
|
||||
if *slot == stamp {
|
||||
continue;
|
||||
}
|
||||
*slot = stamp;
|
||||
if let Some(t) = self.tris.get(i as usize) {
|
||||
best = best.min(point_tri(p, t));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Everything not yet searched is at least r·cell away from p.
|
||||
if best <= r as f64 * self.cell {
|
||||
break;
|
||||
}
|
||||
}
|
||||
best
|
||||
}
|
||||
}
|
||||
|
||||
/// At most this many sample points per side (larger meshes are sampled with an even stride).
|
||||
const MAX_SAMPLES: usize = 250_000;
|
||||
|
||||
/// Sample points of a triangle soup: its vertices and triangle centroids (every k-th triangle for
|
||||
/// very large meshes).
|
||||
fn samples(tris: &[Tri]) -> Vec<[f64; 3]> {
|
||||
let stride = (tris.len() * 4).div_ceil(MAX_SAMPLES).max(1);
|
||||
let mut out: Vec<[f64; 3]> = Vec::with_capacity(tris.len() * 4 / stride + 4);
|
||||
for t in tris.iter().step_by(stride) {
|
||||
out.extend_from_slice(t);
|
||||
out.push(scale(add(add(t[0], t[1]), t[2]), 1.0 / 3.0));
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
/// One-sided sampled distance from `from` to `to`: (max, mean, worst sample point). Distances are
|
||||
/// capped at `cap`.
|
||||
fn one_sided(from: &[Tri], to: &[Tri], cap: f64) -> (f64, f64, [f64; 3]) {
|
||||
let (lo, hi) = bounds(to);
|
||||
let diag = dot(sub(hi, lo), sub(hi, lo)).sqrt();
|
||||
// Cells about twice the typical triangle size (so a cell holds a handful of triangles), but no
|
||||
// finer than diag/400 (so big flat triangles don't fill too many cells).
|
||||
let area: f64 = to.iter().map(|t| dot(cross(sub(t[1], t[0]), sub(t[2], t[0])), cross(sub(t[1], t[0]), sub(t[2], t[0]))).sqrt() / 2.0).sum();
|
||||
let typical = (area / to.len().max(1) as f64).sqrt();
|
||||
let grid = Grid::new(to, (2.0 * typical).max(diag / 400.0).max(1e-3));
|
||||
let mut seen = vec![0u32; to.len()];
|
||||
let (mut max, mut sum, mut worst, mut n) = (0.0f64, 0.0, [0.0; 3], 0usize);
|
||||
for (i, p) in samples(from).into_iter().enumerate() {
|
||||
let d = grid.distance(p, cap, &mut seen, i as u32 + 1);
|
||||
sum += d;
|
||||
n += 1;
|
||||
if d > max {
|
||||
max = d;
|
||||
worst = p;
|
||||
}
|
||||
}
|
||||
(max, if n > 0 { sum / n as f64 } else { 0.0 }, worst)
|
||||
}
|
||||
|
||||
/// Sampled symmetric Hausdorff distance between two triangle soups (distances capped at `cap`).
|
||||
pub struct Hausdorff {
|
||||
pub a_to_b: f64,
|
||||
pub b_to_a: f64,
|
||||
pub mean: f64,
|
||||
/// Where the larger one-sided distance was found (mm, world).
|
||||
pub worst_at: [f64; 3],
|
||||
}
|
||||
|
||||
pub fn hausdorff(a_mesh: &[Tri], b_mesh: &[Tri], cap: f64) -> Hausdorff {
|
||||
let (a, am, ap) = one_sided(a_mesh, b_mesh, cap);
|
||||
let (b, bm, bp) = one_sided(b_mesh, a_mesh, cap);
|
||||
Hausdorff { a_to_b: a, b_to_a: b, mean: (am + bm) / 2.0, worst_at: if a >= b { ap } else { bp } }
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
fn cube(s: f64, o: [f64; 3]) -> Vec<Tri> {
|
||||
let v = |x: f64, y: f64, z: f64| [o[0] + x * s, o[1] + y * s, o[2] + z * s];
|
||||
let q = |a: [f64; 3], b: [f64; 3], c: [f64; 3], d: [f64; 3]| vec![[a, b, c], [a, c, d]];
|
||||
let mut t = Vec::new();
|
||||
t.extend(q(v(0., 0., 0.), v(0., 1., 0.), v(1., 1., 0.), v(1., 0., 0.)));
|
||||
t.extend(q(v(0., 0., 1.), v(1., 0., 1.), v(1., 1., 1.), v(0., 1., 1.)));
|
||||
t.extend(q(v(0., 0., 0.), v(1., 0., 0.), v(1., 0., 1.), v(0., 0., 1.)));
|
||||
t.extend(q(v(0., 1., 0.), v(0., 1., 1.), v(1., 1., 1.), v(1., 1., 0.)));
|
||||
t.extend(q(v(0., 0., 0.), v(0., 0., 1.), v(0., 1., 1.), v(0., 1., 0.)));
|
||||
t.extend(q(v(1., 0., 0.), v(1., 1., 0.), v(1., 1., 1.), v(1., 0., 1.)));
|
||||
t
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn box_moments_and_distance() {
|
||||
let c = cube(10.0, [5.0, -3.0, 2.0]);
|
||||
let (v, com, pm) = mass_properties(&c);
|
||||
assert!((v - 1000.0).abs() < 1e-9 && (com[0] - 10.0).abs() < 1e-9 && (com[2] - 7.0).abs() < 1e-9);
|
||||
// V·(a²+b²)/12 for a cube of side 10: 1000·200/12.
|
||||
for m in pm {
|
||||
assert!((m - 1000.0 * 200.0 / 12.0).abs() < 1e-6, "{pm:?}");
|
||||
}
|
||||
let moved = cube(10.0, [5.0, -3.0, 2.3]);
|
||||
let h = hausdorff(&c, &moved, 1.0);
|
||||
assert!((h.a_to_b - 0.3).abs() < 1e-9 && (h.b_to_a - 0.3).abs() < 1e-9);
|
||||
let same = hausdorff(&c, &c, 1.0);
|
||||
assert!(same.a_to_b < 1e-12);
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,7 @@
|
||||
- [Persistent naming](dev/naming.md)
|
||||
- [Test harnesses](dev/testing.md)
|
||||
- [Control protocol](dev/control-protocol.md)
|
||||
- [Fusion oracle](dev/oracle.md)
|
||||
- [Reference tests](dev/oracle.md)
|
||||
- [Command parity](dev/parity.md)
|
||||
- [STEP import](dev/step-import.md)
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ SolidCraft is a Cargo workspace in layers. A crate only depends on crates in low
|
||||
|
||||
The web app, `apps/solvecraft-web`, is the desktop front end compiled to wasm with
|
||||
[trunk](https://trunkrs.dev). `xtask` holds the repository's own tooling: `cargo xtask ci`,
|
||||
`layers`, `assets`, `oracle`, `parity`, `step-corpus` and `book`.
|
||||
`layers`, `assets`, `oracle`, `parity` and `book`.
|
||||
|
||||
A few rules follow from the layering:
|
||||
|
||||
|
||||
@@ -13,8 +13,7 @@ profile and the wasm build check.
|
||||
| Layout tests | `crates/ui-egui/tests/ui_layout.rs`, `window_widths.rs` | panels and dialogs at every window width |
|
||||
| Design-file corpus | `apps/solvecraft-cli/tests/corpus` | every saved file version still opens and evaluates the same |
|
||||
| Kill mid-save | `apps/solvecraft-cli/tests/kill_mid_save.rs` | a save killed at any moment leaves a readable file |
|
||||
| Fusion oracle | `cargo xtask oracle` → [docs/oracle.md](oracle.md) | parts rebuilt from recipes, compared with Fusion's measurements |
|
||||
| STEP corpus | `cargo xtask step-corpus` → [docs/step-import.md](step-import.md) | STEP files from Fusion opened and measured |
|
||||
| Reference tests | `cargo xtask oracle` → [docs/oracle.md](oracle.md) | parts rebuilt from `tests/oracle/*/recipe.json`, compared with analytic or FreeCAD expected values |
|
||||
|
||||
## UI scenarios
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ also script, test or hand to an agent.
|
||||
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/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
|
||||
[command parity](dev/parity.md) and the [reference tests](dev/oracle.md) measure how far along it
|
||||
is.
|
||||
|
||||
Existing `.solvecraft` designs and `solvecraft-cli` commands keep their names and behavior.
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
# 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). 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.**
|
||||
|
||||
| Case | Result | Bodies | Volume mm³ (got / Fusion) | Area mm² (got / Fusion) | Faces (got / Fusion) | ms | Notes |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 01-box | pass | Body1 | 24000.000 / 24000.000 | 5200.000 / 5200.000 | 6 / 6 | 35 | |
|
||||
| 02-cylinder | pass | Body1 | 35339.346 / 35342.917 | 6125.844 / 6126.106 | 3 / 3 | 399 | |
|
||||
| 03-box-through-hole | pass | Body1 | 45738.459 / 45738.053 | 9327.794 / 9327.788 | 7 / 7 | 292 | |
|
||||
| 04-revolve-vase | pass | Body1 | 112488.577 / 112499.479 | 13156.092 / 13156.615 | 5 / 5 | 1515 | |
|
||||
| 05-taper-extrude | pass | Body1 | 61814.826 / 61814.826 | 9676.915 / 9676.915 | 6 / 6 | 28 | |
|
||||
| 06-symmetric-extrude | pass | Body1 | 30000.000 / 30000.000 | 6200.000 / 6200.000 | 6 / 6 | 33 | |
|
||||
| 07-fillet-all-edges | pass | Body1 | 23340.463 / 23340.849 | 4726.195 / 4726.265 | 26 / 26 | 1285 | |
|
||||
| 08-chamfer | pass | Body1 | 23730.667 / 23730.667 | 5029.352 / 5029.352 | 10 / 10 | 64 | |
|
||||
| 09-shell | pass | Body1 | 13632.000 / 13632.000 | 13992.000 / 13992.000 | 11 / 11 | 76 | |
|
||||
| 10-rect-pattern-holes | pass | Body1 | 29803.844 / 29803.540 | 14652.460 / 14652.389 | 18 / 18 | 1988 | |
|
||||
| 11-circular-pattern-flange | pass | Body1 | 66758.418 / 66758.844 | 19320.582 / 19320.795 | 10 / 10 | 2360 | |
|
||||
| 12-mirror-feature | pass | Body1 | 21392.312 / 21392.920 | 6530.923 / 6530.973 | 10 / 10 | 753 | |
|
||||
| 13-combine-join | pass | Body1 | 45569.249 / 45571.680 | 8634.154 / 8634.336 | 9 / 9 | 383 | |
|
||||
| 14-combine-cut | pass | Body1 | 42932.838 / 42931.417 | 9706.788 / 9706.858 | 7 / 7 | 1401 | |
|
||||
| 15-combine-intersect | pass | Body1 | 54451.677 / 54454.273 | 7382.501 / 7382.743 | 7 / 7 | 2559 | solid 'Body1': face wrapping around a closed surface kept whole: unsupported seam loop layout (8 runs) |
|
||||
| 16-sweep | pass | Body1 | 2584.300 / 2584.724 | 1779.617 / 1779.698 | 5 / 5 | 1957 | |
|
||||
| 17-loft | pass | Body1 | 35120.291 / 35125.498 | 6495.290 / 6495.000 | 6 / 6 | 601 | |
|
||||
| 18-hole-features | pass | Body1 | 33890.962 / 33890.710 | 8568.687 / 8568.696 | 13 / 13 | 1552 | |
|
||||
| 19-l-bracket-constrained | pass | Body1 | 14720.000 / 14720.000 | 5472.000 / 5472.000 | 8 / 8 | 39 | |
|
||||
| 20-slot-constrained | pass | Body1 | 2965.385 / 2965.487 | 1774.641 / 1774.690 | 6 / 6 | 88 | |
|
||||
| 21-draft | pass | Body1 | 54574.737 / 54574.737 | 8781.998 / 8781.998 | 6 / 6 | 38 | |
|
||||
| 22-split-body | pass | Body1, Body2 | 75390.604 / 75398.224 | 12954.438 / 12955.175 | 6 / 6 | 802 | |
|
||||
| 23-bracket-multifeature | pass | Body1 | 59331.547 / 59331.129 | 18290.759 / 18290.737 | 19 / 19 | 1441 | |
|
||||
| 24-user-parameters | pass | Body1 | 34822.114 / 34821.903 | 8114.166 / 8114.159 | 7 / 7 | 437 | |
|
||||
| 25-sketch-triangle | pass | Body1 | 4032.155 / 4032.155 | 2274.099 / 2274.099 | 5 / 5 | 51 | |
|
||||
| 26-sketch-belt | pass | Body1 | 11326.049 / 11326.170 | 5637.345 / 5637.404 | 7 / 7 | 599 | |
|
||||
| 27-sketch-trapezoid | pass | Body1 | 5342.307 / 5342.257 | 3066.958 / 3066.946 | 8 / 8 | 832 | |
|
||||
| 28-blind-pocket-fillets | pass | Body1 | 84299.377 / 84299.249 | 16279.553 / 16279.572 | 23 / 23 | 2087 | |
|
||||
| 29-two-sided-and-partial-revolve | pass | Body1 | 25731.359 / 25731.307 | 6312.589 / 6312.602 | 12 / 12 | 229 | |
|
||||
| 30-fillet-after-shell | pass | Body1 | 21745.916 / 21745.876 | 14968.283 / 14968.332 | 27 / 27 | 1298 | |
|
||||
| 31-fillet-l-all-edges | pass | Body1 | 14321.380 / 14321.626 | 5023.977 / 5024.045 | 38 / 38 | 2673 | |
|
||||
| 32-boss-plate-fillets | pass | Body1 | 33590.273 / 33590.350 | 9929.855 / 9929.864 | 10 / 10 | 3878 | |
|
||||
| 33-fillet-tangent-chain | pass | Body1 | 8236.989 / 8237.122 | 2793.535 / 2793.569 | 14 / 14 | 1687 | |
|
||||
| 34-chamfer-then-fillet | pass | Body1 | 22892.359 / 22892.421 | 4753.672 / 4753.688 | 18 / 18 | 577 | |
|
||||
| 35-fillet-curved-curved | pass | Body1 | 63169.296 / 63171.443 | 10269.098 / 10269.348 | 6 / 6 | 9098 | solid 'Body1': face wrapping around a closed surface kept whole: split line does not reach the rings (×2); solid 'Body1': face wrapping around a closed surface kept whole: unsupported seam loop layout (8 runs) |
|
||||
| 36-corner-blend-two-radii | pass | Body1 | 57799.380 / 57800.240 | 8585.423 / 8585.524 | 18 / 18 | 3015 | |
|
||||
| 37-pyramid-apex-blend | pass | Body1 | 15465.384 / 15465.575 | 3914.545 / 3914.581 | 18 / 18 | 5090 | |
|
||||
| 38-coincident-cylinders | pass | Body1 | 15706.154 / 15707.963 | 3769.748 / 3769.911 | 3 / 3 | 354 | |
|
||||
| 39-tee-pipe | pass | Body1 | 24095.711 / 24096.237 | 16709.770 / 16710.048 | 7 / 7 | 2800 | solid 'Body1': face wrapping around a closed surface kept whole: unsupported seam loop layout (8 runs) (×2) |
|
||||
| 40-box-sphere-torus | pass | Body1 | 36287.765 / 36287.433 | 10696.188 / 10696.165 | 10 / 10 | 6187 | |
|
||||
| 41-tube-radial-holes | pass | Body1 | 20243.087 / 20243.981 | 14873.680 / 14873.773 | 22 / 22 | 9763 | |
|
||||
| 42-overlapping-patterns | pass | Body1 | 38488.942 / 38487.064 | 21687.167 / 21686.603 | 43 / 43 | 5322 | a solid's shell does not close; solid 'Body1': imported as an open body (the shell is not closed) |
|
||||
| 43-cut-splits-body | pass | Body1, Body2 | 28000.000 / 28000.000 | 7200.000 / 7200.000 | 12 / 12 | 128 | |
|
||||
| 44-shell-l-bracket | pass | Body1 | 8384.000 / 8384.000 | 8784.000 / 8784.000 | 15 / 15 | 132 | |
|
||||
| 45-shell-boss-plate | pass | Body1 | 13676.813 / 13676.602 | 14156.507 / 14156.602 | 15 / 15 | 785 | |
|
||||
| 46-shell-two-open-faces | pass | Body1 | 16044.000 / 16044.000 | 11398.000 / 11398.000 | 10 / 10 | 141 | |
|
||||
| 47-draft-curved-pocket | pass | Body1 | 111388.066 / 111387.712 | 17700.333 / 17700.361 | 11 / 11 | 290 | |
|
||||
| 48-pulley | pass | Body1 | 168508.132 / 168510.841 | 23718.191 / 23718.644 | 12 / 12 | 4041 | |
|
||||
| 49-helical-sweep | pass | Body1 | 4740.421 / 4738.099 | 4768.066 / 4767.652 | 3 / 3 | 33507 | |
|
||||
| 50-sweep-tight-bend | FAIL | Body1 | 1621.826 / 3758.443 | – / – | 4 / 5 | 813 | the surfaces do not close into a solid: imported as an open body; solid 'Body1': face #231: face boundary is not a closed loop; solid 'Body1': imported as an open body (the shell is not closed) |
|
||||
| 51-loft-rect-circle-offset | pass | Body1 | 61129.309 / 61133.116 | 9412.484 / 9411.655 | 6 / 6 | 532 | |
|
||||
| 52-circular-pattern-fillet | pass | Body1 | 47206.121 / 47206.850 | 14141.995 / 14142.171 | 21 / 21 | 5990 | |
|
||||
| 53-path-pattern | pass | Body1 | 35010.576 / 35010.398 | 16463.935 / 16463.894 | 13 / 13 | 1431 | |
|
||||
| 54-mirror-body-fillets | pass | Body1 | 22780.189 / 22780.089 | 6972.821 / 6972.832 | 12 / 12 | 861 | |
|
||||
| 55-tapped-hole-cosmetic | pass | Body1 | 17542.374 / 17542.314 | 4761.564 / 4761.596 | 8 / 8 | 543 | |
|
||||
| 56-holes-on-curved-face | pass | Body1 | 77940.974 / 77942.388 | 10592.891 / 10593.139 | 7 / 7 | 3231 | solid 'Body1': face wrapping around a closed surface kept whole: a hole crosses the split line |
|
||||
| 57-hole-pattern-sketch-points | pass | Body1 | 36767.089 / 36766.712 | 11818.157 / 11818.153 | 24 / 24 | 2099 | |
|
||||
| 58-phone-stand | pass | Body1 | 74559.445 / 74559.177 | 29148.741 / 29148.785 | 19 / 19 | 181 | |
|
||||
| 59-enclosure-base | pass | Body1 | 34309.026 / 34309.486 | 33795.404 / 33795.610 | 39 / 39 | 2120 | solid 'Body1': 2 face(s) could not be meshed and are not shown; solid 'Body1': face wrapping around a closed surface kept whole: cannot cut a ring edge at the split line (×4) |
|
||||
| 60-pipe-flange | pass | Body1 | 323660.822 / 323660.979 | 56984.283 / 56984.564 | 16 / 16 | 4410 | |
|
||||
| 61-shaft-keyway | pass | Body1 | 47521.369 / 47525.946 | 9676.777 / 9677.096 | 14 / 14 | 3019 | solid 'Body1': face wrapping around a closed surface kept whole: a hole crosses the split line |
|
||||
| 62-heat-sink | pass | Body1 | 43125.000 / 43125.000 | 45725.000 / 45725.000 | 66 / 66 | 278 | |
|
||||
| 63-hinge-knuckle | pass | Body1 | 4783.060 / 4783.117 | 4110.247 / 4110.287 | 14 / 14 | 974 | solid 'Body1': face wrapping around a closed surface kept whole: a hole crosses the split line |
|
||||
| 64-sketch-tangent-arc-chain | pass | Body1 | 4800.000 / 4800.000 | 3843.701 / 3843.712 | 9 / 9 | 113 | |
|
||||
| 65-sketch-gear-tooth | pass | Body1 | 3987.829 / 3988.084 | 1945.416 / 1945.485 | 9 / 9 | 119 | |
|
||||
| 66-sketch-linkage | pass | Body1 | 8045.432 / 8045.386 | 4308.169 / 4308.175 | 10 / 10 | 1662 | |
|
||||
| 67-sm-base-flange | pass | Body1 | 15000.000 / 15000.000 | 12800.000 / 12800.000 | 6 / 6 | 31 | |
|
||||
| 68-sm-edge-flange | pass | Body1 | 18972.511 / 18972.622 | 16057.510 / 16057.550 | 14 / 14 | 182 | |
|
||||
| 69-sm-tray | pass | Body1 | 29665.924 / 29666.234 | 24979.972 / 24980.086 | 46 / 46 | 568 | |
|
||||
| 70-sm-flange-45 | pass | Body1 | 18450.720 / 18450.777 | 15629.618 / 15629.637 | 14 / 14 | 112 | |
|
||||
| 71-sm-custom-rule | pass | Body1 | 10053.662 / 10053.717 | 13896.235 / 13896.299 | 22 / 22 | 277 | |
|
||||
| 72-sm-contour-flange | pass | Body1 | 12097.511 / 12097.622 | 10411.960 / 10412.002 | 22 / 22 | 264 | |
|
||||
| 73-sm-hem | pass | Body1 | 21334.436 / 21334.723 | 17994.374 / 17994.473 | 22 / 22 | 317 | |
|
||||
| 74-sm-unfold-refold | pass | Body1 | 15800.138 / 15800.304 | 13610.263 / 13610.326 | 32 / 32 | 286 | |
|
||||
| 75-sm-bracket-holes | pass | Body1 | 13915.905 / 13916.012 | 12035.926 / 12035.962 | 25 / 25 | 558 | |
|
||||
| 76-sm-thickness-variant | pass | Body1 | 22520.416 / 22520.575 | 16064.080 / 16064.128 | 14 / 14 | 139 | |
|
||||
+95
-90
@@ -1,96 +1,101 @@
|
||||
# Fusion oracle
|
||||
# Reference tests
|
||||
|
||||
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.
|
||||
Generated by `cargo xtask oracle`. Each case in [`tests/oracle/`](../tests/oracle/) is a part described by our own `recipe.json`. SolidCraft replays the recipe through its command engine and compares the result with `expected.json`: body count, volume and area (relative tolerance 1e-3 unless the case states another), centre of mass and bounding box (within 1e-3 of the part's size), face count where the geometry fixes it, and, where the case has a `reference.stl`, the sampled symmetric (Hausdorff) distance between the two surfaces and the principal second moments of both triangulations. Every rebuilt part is also exported as STEP, validated and read back.
|
||||
|
||||
**76 / 76 cases pass** (0 need features not implemented yet).
|
||||
Where the expected values come from (the **Source** column):
|
||||
|
||||
| Case | Result | Notes |
|
||||
|---|---|---|
|
||||
| 01-box | pass | |
|
||||
| 02-cylinder | pass | |
|
||||
| 03-box-through-hole | pass | |
|
||||
| 04-revolve-vase | pass | |
|
||||
| 05-taper-extrude | pass | |
|
||||
| 06-symmetric-extrude | pass | |
|
||||
| 07-fillet-all-edges | pass | |
|
||||
| 08-chamfer | pass | |
|
||||
| 09-shell | pass | |
|
||||
| 10-rect-pattern-holes | pass | |
|
||||
| 11-circular-pattern-flange | pass | |
|
||||
| 12-mirror-feature | pass | |
|
||||
| 13-combine-join | pass | |
|
||||
| 14-combine-cut | pass | |
|
||||
| 15-combine-intersect | pass | |
|
||||
| 16-sweep | pass | |
|
||||
| 17-loft | pass | |
|
||||
| 18-hole-features | pass | |
|
||||
| 19-l-bracket-constrained | pass | |
|
||||
| 20-slot-constrained | pass | |
|
||||
| 21-draft | pass | |
|
||||
| 22-split-body | pass | |
|
||||
| 23-bracket-multifeature | pass | |
|
||||
| 24-user-parameters | pass | |
|
||||
| 25-sketch-triangle | pass | |
|
||||
| 26-sketch-belt | pass | |
|
||||
| 27-sketch-trapezoid | pass | |
|
||||
| 28-blind-pocket-fillets | pass | |
|
||||
| 29-two-sided-and-partial-revolve | pass | |
|
||||
| 30-fillet-after-shell | pass | |
|
||||
| 31-fillet-l-all-edges | pass | |
|
||||
| 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 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 | |
|
||||
| 39-tee-pipe | pass | |
|
||||
| 40-box-sphere-torus | pass | |
|
||||
| 41-tube-radial-holes | pass | |
|
||||
| 42-overlapping-patterns | pass | |
|
||||
| 43-cut-splits-body | pass | |
|
||||
| 44-shell-l-bracket | pass | |
|
||||
| 45-shell-boss-plate | pass | |
|
||||
| 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; 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 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 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; SolidCraft's through-all goes truly through) |
|
||||
| 64-sketch-tangent-arc-chain | pass | |
|
||||
| 65-sketch-gear-tooth | pass | |
|
||||
| 66-sketch-linkage | pass | |
|
||||
| 67-sm-base-flange | pass | |
|
||||
| 68-sm-edge-flange | pass | |
|
||||
| 69-sm-tray | pass | |
|
||||
| 70-sm-flange-45 | pass | |
|
||||
| 71-sm-custom-rule | pass | |
|
||||
| 72-sm-contour-flange | pass | |
|
||||
| 73-sm-hem | pass | |
|
||||
| 74-sm-unfold-refold | pass | |
|
||||
| 75-sm-bracket-holes | pass | |
|
||||
| 76-sm-thickness-variant | pass | |
|
||||
- **analytic**: closed-form geometry worked out by hand (prisms, cylinders, cones, spheres, tori and Pappus' theorems, constant-radius fillets on straight edges, sheet-metal bends as annular sectors). The scripts in [`tools/oracle-analytic/`](../tools/oracle-analytic/) compute them and give the formulas.
|
||||
- **freecad <version>**: the same recipe built with FreeCAD's public Python scripting API by [`tools/freecad-oracle/`](../tools/freecad-oracle/), an independent kernel run as an external program. FreeCAD is not a dependency of SolidCraft. Where the two kernels legitimately build a different shape (for example the corner patch where fillets meet), only volume and area are compared, within the tolerance the case states.
|
||||
|
||||
## No oracle
|
||||
No expected value comes from a commercial CAD product.
|
||||
|
||||
| Feature | Status | Tested by |
|
||||
|---|---|---|
|
||||
| Boss (draft, root fillet, hole, ribs) | no oracle (licence) | post, hole and rib volumes (`boss_with_hole_fillet_and_ribs`) |
|
||||
| Lip / Groove | no oracle (licence) | band offsets and groove clearance on a shelled box (`lip_and_groove_on_a_shelled_box`, `groove_takes_the_rule_clearance`) |
|
||||
| Snap Fit | no oracle (licence) | arm and catch volume, hook direction (`snap_fit_arm_and_catch`) |
|
||||
| Rest | no oracle (licence) | rectangle, round, hollow and drafted pads (`rest_pads`) |
|
||||
| Plastic Rules (manage, assign) | no oracle (licence) | library values, Thickness-based values, rule draft as the boss default (`plastic_rules_library_edit_and_assign`) |
|
||||
**75 / 76 cases pass**; sources: 55 analytic, 21 freecad. 1 known failure(s) tracked in issues; 0 case(s) without expected values yet.
|
||||
|
||||
Fusion builds these only with its Design Extension, which the oracle account lacks, so there is no Fusion part to compare with. They follow Autodesk's public descriptions of the features and are checked against hand-computed geometry; the plastic rule library values were read from Fusion's API.
|
||||
| Case | Source | Result | Surface distance mm (max / mean) | Method | Notes |
|
||||
|---|---|---|---|---|---|
|
||||
| 01-box | analytic | pass | | box 40 x 30 x 20 from the origin: V = abc, A = 2(ab + bc + ca) | |
|
||||
| 02-cylinder | analytic | pass | | cylinder r=15 h=50 on XY: V = pi r^2 h, A = 2 pi r^2 + 2 pi r h | |
|
||||
| 03-box-through-hole | analytic | pass | | box 60x40x20 minus centred through cylinder r=6: V = abc - pi r^2 c, A = 2(ab+bc+ca) - 2 pi r^2 + 2 pi r c; hole is centred so the centroid stays at the box centre | |
|
||||
| 04-revolve-vase | analytic | pass | | revolve of a line/3-point-arc profile (XZ, x = radius, z = -sketch y) 360 deg about Z. Pappus/Green: V = 2 pi * int x dA, z_c = int x z dA / int x dA, A = sum over profile edges of 2 pi int x ds (bottom disk r20, frustum r20->25, torus zone of the arc (centre (-16.000000, 33.000000), R=41.109610), frustum r15->18, top disk r18) | |
|
||||
| 05-taper-extrude | analytic | pass | | square frustum: base 40 at z=0, top a1 = 40 + 2*30*tan10 at z=30 (positive taper grows outward); V = h/3 (A0 + A1 + sqrt(A0 A1)), z_c = h (A0 + 2 sqrt(A0A1) + 3 A1) / (4 (A0 + sqrt(A0A1) + A1)), sides = 4 trapezoids (a0+a1)/2 * h/cos(10) | |
|
||||
| 06-symmetric-extrude | analytic | pass | | 50 (x) by 20 (z) rectangle on XZ extruded +-15 along Y: box 50x30x20 centred at origin | |
|
||||
| 07-fillet-all-edges | analytic | pass | | box 40x30x20 with every edge rounded r=3 = inner box (34x24x14) grown by a ball r (Steiner): V = abc + 2r(ab+bc+ca) + pi r^2 (a+b+c) + 4/3 pi r^3, A = 2(ab+bc+ca) + 2 pi r (a+b+c) + 4 pi r^2; faces 6 planes + 12 quarter cylinders + 8 sphere octants | |
|
||||
| 08-chamfer | analytic | pass | | box 40x30x20 with the 4 top edges chamfered 2: lower box 40x30x18 + top slab with section (36+2u)x(26+2u) at depth u (V, moment by exact polynomial integration); A = bottom + sides(h=18) + top 36x26 + 4 trapezoid bevels of slant 2 sqrt2 | |
|
||||
| 09-shell | analytic | pass | | 50x40x30 box shelled inward 2 with the top open: box minus cavity [2,48]x[2,38]x[2,30]; A = outer bottom + outer sides + rim + cavity floor + cavity walls | |
|
||||
| 10-rect-pattern-holes | analytic | pass | | plate 90x70x5 minus 12 through cylinders r=3 at (15+20i, 15+20j), i<4, j<3: V = abc - 12 pi r^2 t, A = 2(ab+bc+ca) + 12(2 pi r t - 2 pi r^2) | |
|
||||
| 11-circular-pattern-flange | analytic | pass | | annulus R50/r15 h10 minus 6 through cylinders r=5 on a 40 radius: V = pi(R^2-r^2)h - 6 pi rh^2 h, A = 2 pi(R^2-r^2) + 2 pi(R+r)h + 6(2 pi rh h - 2 pi rh^2); 6-fold symmetry keeps the centroid on the axis | |
|
||||
| 12-mirror-feature | analytic | pass | | block [-30,30]x[0,30]x[0,10] + two bosses r=6 h=15 at (+-15,15) on top (mirror across YZ): V = abc + 2 pi r^2 h, A = 2(ab+bc+ca) + 2 * 2 pi r h | |
|
||||
| 13-combine-join | analytic | pass | | box [0,40]^2x[0,20] union cylinder r=12 at (40,20) z 0..40: V = box + half-cylinder (x>40, z<20, centroid 40 + 4r/3pi) + full cylinder z 20..40; A = bottom (1600 + pi r^2/2) + box top (1600 - pi r^2/2) + cyl top + 3 full sides + x=40 side minus 24x20 + cyl side (pi r 40 outside half + pi r 20 upper inside half) | |
|
||||
| 14-combine-cut | analytic | pass | | box [-25,25]^2x[-20,0] minus sphere r=15 at the origin (lower hemisphere removed): V = abc - 2/3 pi r^3, hemisphere centroid z = -3r/8; A = 2(ab+bc+ca) - pi r^2 + 2 pi r^2 | |
|
||||
| 15-combine-intersect | analytic | pass | | cube [-20,20]^3 intersect sphere R=25: sphere minus 6 disjoint caps of height h=5 (caps cannot overlap: two would need x^2+y^2 > 800 > R^2); V = 4/3 pi R^3 - 6 pi h^2 (3R-h)/3, A = 4 pi R^2 - 6 * 2 pi R h + 6 pi (R^2-20^2) | |
|
||||
| 16-sweep | analytic | pass | | circle r=3 swept along line 30 (+Z) + tangent R20 quarter arc + line 30 (+X): two cylinders + quarter torus (Pappus: V = pi r^2 R theta, A = 2 pi r R theta; torus centroid at rho = (R^2 + r^2/4)/R * sin(theta/2)/(theta/2) from the bend axis along the bisector) + 2 end disks | |
|
||||
| 17-loft | freecad 1.1.4 | pass | 0.013 / 0.0019 (worst at -7.9, -10.8, 31.2) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part.makeLoft | |
|
||||
| 18-hole-features | analytic | pass | | plate 60x40x15 minus: counterbore (r7 x 5 deep + r4 through), countersink (90 deg cone frustum r6->r3 over 3 mm + r3 through), blind r3 hole 10 deep to the shoulder with a 118 deg cone point (height 3/tan59, centroid 1/4 up from the apex); areas: removed top/bottom disks, cylinder walls 2 pi r h, cb floor annulus, cone laterals pi r s | |
|
||||
| 19-l-bracket-constrained | analytic | pass | | solved L: (0,0)-(60,0)-(60,8)-(8,8)-(8,40)-(0,40); area 60x8 + 8x32, extruded 20: V = A2 h, A = 2 A2 + perimeter*h, centroid of the two rectangles | |
|
||||
| 20-slot-constrained | analytic | pass | | obround: arc centres (0,0) and (40,0), R6, tangent horizontal lines y=+-6: A2 = 2rL + pi r^2, V = A2 h, A = 2 A2 + perimeter (2L + 2 pi r) h | |
|
||||
| 21-draft | analytic | pass | | 40x40x30 box, 5 deg draft hinged on the bottom (z=0), pull -Z: square frustum 40 -> 40 + 2*30*tan5; V = h/3 (A0 + A1 + sqrt(A0A1)), sides 4 trapezoids of slant h/cos5 | |
|
||||
| 22-split-body | analytic | pass | | cylinder r20 z -30..30 split by the plane through the X axis tilted 30 deg (z = y tan30, stays within /z/<=11.6 inside the cylinder): 2 congruent halves (point symmetry), V total = pi r^2 h, A total = cylinder + 2 ellipses pi r (r/cos30) | |
|
||||
| 23-bracket-multifeature | freecad 1.1.4 | pass | 0.007 / 0.0010 (worst at 57.3, 49.3, 25.8) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part makeFillet, Part extrude join, Part extrude cut, Part makeChamfer | |
|
||||
| 24-user-parameters | analytic | pass | | width 60 x depth 40 x height 15 block from the origin with a centred d10 through hole: V = (wd - pi r^2) h, A = 2(wd - pi r^2) + 2(w+d)h + 2 pi r h | |
|
||||
| 25-sketch-triangle | analytic | pass | | solved triangle (0,0), (50,0), P = t(cos60, sin60) with t = 25 + sqrt(150) from /P-(50,0)/ = 45 (the root nearest the drawn apex); V = (1/2 * 50 * Py) * 5, A = 2 A2 + perimeter*5, centroid = vertex mean | |
|
||||
| 26-sketch-belt | analytic | pass | | belt: convex hull of circles R20 @ (0,0) and r10 @ (50,0) (external tangents, sin a = (R-r)/d) minus the d8 hole at (50,0), extruded 5 as one body; hull area = (R+r) sqrt(d^2-(R-r)^2) + R^2(pi+2a)/2 + r^2(pi-2a)/2; moments by Green's theorem over the line/arc boundary | |
|
||||
| 27-sketch-trapezoid | analytic | pass | | solved trapezoid bottom (-30,0)-(30,0), top (-15,25)-(15,25) minus two d6 holes at (+-18, 8) (symmetric about the axis, 36 apart, 8 above l1); trapezoid centroid y = H(a+2b)/(3(a+b)); V = A2 t, A = 2 A2 + perimeter t | |
|
||||
| 28-blind-pocket-fillets | freecad 1.1.4 | pass | 0.009 / 0.0015 (worst at 60.0, 15.1, 13.4) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, Part makeFillet | |
|
||||
| 29-two-sided-and-partial-revolve | analytic | pass | | block [-20,20]x[-25,10]x[0,20] minus the 270 deg revolve (from +X towards +Y) of the 8x8 square r 4..12, z 12..20; the removed section is the annular sector minus the circular segment y > 10 (R^2 acos(d/R) - d sqrt(R^2-d^2), y-moment 2/3 (R^2-d^2)^1.5) that lies outside the block; sector moments (R^3-r^3)/3 (sin, 1-cos); A = box - top opening + floor - front-face notch 2 sqrt(44) x 8 + inner wall r th h + outer wall (th - pi + 2 asin(5/6)) R h + 2 end caps | |
|
||||
| 30-fillet-after-shell | freecad 1.1.4 | pass | 0.008 / 0.0025 (worst at 56.3, 35.7, 3.5) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, makeThickness shell, Part makeFillet | |
|
||||
| 31-fillet-l-all-edges | freecad 1.1.4 | pass | 0.009 / 0.0018 (worst at 7.9, 38.6, 18.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet | |
|
||||
| 32-boss-plate-fillets | analytic | pass | | plate 60x60x8 + boss (r10, z 8..23) with R3 concave base and R2 convex top fillets taken as one solid of revolution: V, z-moment and lateral area from Pappus/Green on its (r,z) profile (quarter-circle fillet arcs); plate top loses the r13 disc | |
|
||||
| 33-fillet-tangent-chain | analytic | pass | | obround (R8, centres 40 apart) x 10 minus R2 fillet spandrel R^2(1-pi/4) and 1 mm chamfer triangle swept round the whole loop (Pappus: section x path of its centroid, inset R(10-3pi)/(3(4-pi)) resp. c/3); surfaces likewise (fillet arc centroid inset R-2R/pi) | |
|
||||
| 34-chamfer-then-fillet | freecad 1.1.4 | pass | 0.009 / 0.0022 (worst at 38.5, 5.6, 19.9) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeChamfer, Part makeFillet | |
|
||||
| 35-fillet-curved-curved | freecad 1.1.4 | pass | 0.011 / 0.0024 (worst at -9.5, 5.3, 14.6) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part makeFillet | |
|
||||
| 36-corner-blend-two-radii | freecad 1.1.4 | pass | 0.012 / 0.0026 (worst at 45.4, 39.2, 26.5) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet | |
|
||||
| 37-pyramid-apex-blend | freecad 1.1.4 | pass | 0.020 / 0.0029 (worst at -0.0, 0.0, 28.4) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part.makeLoft, Part makeFillet | |
|
||||
| 38-coincident-cylinders | analytic | pass | | union of r10 z0..30 and r10 z20..50 = cylinder r10 h50: V = pi r^2 h, A = 2 pi r h + 2 pi r^2 | |
|
||||
| 39-tee-pipe | freecad 1.1.4 | pass | 0.008 / 0.0014 (worst at -9.7, 2.4, 40.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part extrude cut | |
|
||||
| 40-box-sphere-torus | analytic | pass | | box 60x60x10 (z -10..0) + upper hemisphere r12 + spherical cap h2 below z=-10 (V = pi h^2(3R-h)/3, A = 2 pi R h) - lower half torus R22 r4 (Pappus: V = pi r^2/2 * 2 pi R, centroid -4r/3pi, A = pi r * 2 pi R); top loses disc r12 and annulus 18..26, bottom loses disc r sqrt(44) | faces: bottom, 4 sides, top split by the groove into an annulus (r 12..18) and the outer part, upper sphere, lower cap, torus |
|
||||
| 41-tube-radial-holes | freecad 1.1.4 | pass | 0.010 / 0.0012 (worst at 1.9, 19.9, 24.8) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, pattern (tools transformed and re-applied) | |
|
||||
| 42-overlapping-patterns | freecad 1.1.4 | pass | 0.030 / 0.0010 (worst at -20.0, -0.0, 5.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, pattern (tools transformed and re-applied), Part extrude cut, pattern (tools transformed and re-applied) | |
|
||||
| 43-cut-splits-body | analytic | pass | | two boxes 35x20x20 (x 0..35 and 45..80): V = 2abc, A = 2*2(ab+bc+ca) | |
|
||||
| 44-shell-l-bracket | analytic | pass | | L prism (area 736, perimeter 200) x 20 minus cavity = L shrunk by 2 (SolidCraft's intersection-join shell convention: inner faces offset by 2 and extended to meet, sharp inner corner at (6,6); area 352) from z=2 to 20. A rounded (arc) join would give V 8368.5487, A 8768.5487, 16 faces | |
|
||||
| 45-shell-boss-plate | analytic | pass | | plate 60x60x10 + boss r12 h20 minus cavity (box 56x56x8 + cylinder r10 z8..28) under SolidCraft's intersection-join shell convention (offset faces extended to meet: sharp circular inner edge at r10, z8). A rounded (arc) join would give V 13620.2567, A 14104.4455, 16 faces (torus round r2) | |
|
||||
| 46-shell-two-open-faces | analytic | pass | | box 50x40x30 minus cavity x3..47, y0..37, z3..30 (3 mm walls, top and front open) | |
|
||||
| 47-draft-curved-pocket | freecad 1.1.4 | pass | 0.008 / 0.0019 (worst at 22.1, 21.3, 15.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, PartDesign Draft | |
|
||||
| 48-pulley | analytic | pass | | revolve of the 12-segment (r,z) polygon about Z: V = 2 pi int r dA, Mz = 2 pi int r z dA, A = 2 pi int r ds (Pappus) | |
|
||||
| 49-helical-sweep | analytic | pass | | true helix coil (screw motion of the axial d4 section, R20, pitch 8, 3 turns): V = turns * 2 pi R * pi a^2; lateral A = turns * 2 pi * int sqrt(r^2 + (p/2pi)^2 (dr/ds)^2) ds (periodic trapezoid, exact to round-off) + two end discs; COM z = pitch*turns/2 | |
|
||||
| 50-sweep-tight-bend | analytic | pass | | tube r5 along line 20 + R5 quarter bend + line 20: V = pi r^2 L, A = 2 pi r L + 2 pi r^2 (Pappus, valid while bend radius >= r; here equal so the inner side pinches to a line); bend centroid at (R^2 + r^2/4)/R * sinc(pi/4) along the bisector | |
|
||||
| 51-loft-rect-circle-offset | freecad 1.1.4 | pass | 0.025 / 0.0030 (worst at -11.7, 15.0, 0.4) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part.makeLoft | |
|
||||
| 52-circular-pattern-fillet | analytic | pass | | disc r40 h8 + 6 x (boss r6 h10 with R2 concave base fillet as a solid of revolution, Pappus/Green on its (r,z) profile) at radius 28; disc top loses 6 discs r8 | |
|
||||
| 53-path-pattern | analytic | pass | | plate 120x60x5 minus 7 through holes d6 at arc lengths k*17.875031 along the arc through (100,15),(60,45),(20,15) (centre (60,10/3), R 125/3), translated copies | |
|
||||
| 54-mirror-body-fillets | analytic | pass | | prism h10 of the 60x40 rectangle with four R5 corner rounds and two d8 holes at (+-15,20): V = A h, area = 2A + perimeter*h | |
|
||||
| 55-tapped-hole-cosmetic | analytic | pass | | plate 40x30x15 minus d6.8 cylinder 12 deep (to the shoulder) and 118 deg drill point cone (h = r/tan59, V = pi r^2 h/3, lateral pi r * r/sin59); cosmetic thread adds no geometry | |
|
||||
| 56-holes-on-curved-face | freecad 1.1.4 | pass | 0.009 / 0.0018 (worst at -16.9, 18.5, 0.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, hole tool (cylinder/cone) cut | |
|
||||
| 57-hole-pattern-sketch-points | analytic | pass | | plate 80x50x10 minus 6 counterbored through holes (cb d10 x 5.5, hole d5.5) at x 15/40/65, y 15/35 | |
|
||||
| 58-phone-stand | freecad 1.1.4 | pass | 0.007 / 0.0010 (worst at 82.5, 11.7, 6.1) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet, Part extrude cut | |
|
||||
| 59-enclosure-base | freecad 1.1.4 | pass | 0.008 / 0.0017 (worst at 90.9, 51.0, 6.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet, makeThickness shell, Part extrude join, Part extrude cut | |
|
||||
| 60-pipe-flange | freecad 1.1.4 | pass | | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part revolve, Part extrude cut, pattern (tools transformed and re-applied), Part makeChamfer, Part makeFillet | No reference.stl: over 409600 bytes even at 0.02 mm deflection (11380 facets). |
|
||||
| 61-shaft-keyway | freecad 1.1.4 | pass | 0.008 / 0.0019 (worst at 11.9, 3.9, 45.8) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part revolve, Part makeChamfer, Part extrude cut | |
|
||||
| 62-heat-sink | analytic | pass | | base 60x50x5 + 15 fins 1.5x50x25 at x = 2 + 4k: sums of boxes; fin ends coplanar with the base ends | faces: bottom, x=0, x=60, y=0 and y=50 (each merged with the fin ends), 16 base-top strips, 15x3 fin faces |
|
||||
| 63-hinge-knuckle | freecad 1.1.4 | pass | 0.008 / 0.0016 (worst at 28.0, 36.9, 4.7) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part extrude cut | |
|
||||
| 64-sketch-tangent-arc-chain | analytic | pass | | solved sketch: 80x20 rectangle whose top is 4 tangent R12 arcs on 20 mm chords alternating up/down (net area 1600); prism h3: V = A h, area = 2A + perimeter*h, Green's theorem for centroid | |
|
||||
| 65-sketch-gear-tooth | freecad 1.1.4 | pass | 0.008 / 0.0017 (worst at 6.5, 39.5, 8.0) | FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Sketcher-solved sketch, Part extrude | |
|
||||
| 66-sketch-linkage | analytic | pass | | solved sketch: hull of circles R12 at (0,0) and R8 at (60,0) (outer tangent lines, cos a = 4/60) minus holes d12, d8 (concentric) and d4 at (30,+-4) (symmetry keeps radii equal); prism h6 | |
|
||||
| 67-sm-base-flange | analytic | pass | | plate 100 x 60 x t, t = 2.5 (Steel (mm) rule), thickness along +Z: V = 100*60*t, A = 2(ab+bc+ca) | |
|
||||
| 68-sm-edge-flange | analytic | pass | | extrusion along x (100) of the YZ section: base y 5..60 x t; 90 deg bend annulus R 2.5..5 about (y 5, z 5); flange y 0..2.5, z 5..20. Setback (R+t)tan45 = 5 (inside bend position, outer-face height datum: outer face of flange reaches z = 20); V = L*A_sec, A = 2 A_sec + L P_sec | trim/setback and height datum are SolidCraft's documented edge-flange conventions (sheet.rs add_flanges) |
|
||||
| 69-sm-tray | analytic | pass | | tray: base 90 x 50 x 2.5 (each edge trimmed by the setback 5); four 90 deg bends R 2.5..5 along the trimmed edges (90, 50, 90, 50 long); four walls 2.5 thick, z 5..25, each running on past its bend by ext = (R+t) - (t + gap/sqrt2) = 0.732233047 at both ends; open corners. A = sum of prisms - 2 x joints | corner treatment is SolidCraft's convention (sheet.rs close_corners: panels extended until their inner corners are the rule gap = t apart; no corner fill, open corner relief below the walls) |
|
||||
| 70-sm-flange-45 | analytic | pass | | as 68 with a 45 deg bend: setback (R+t)tan22.5 = 2.071068, base y 2.071068..60, bend annulus 2.5..5 over 45 deg about (y 2.071068, z 5), flat leg 15 - setback = 12.928932 turned 45 deg; extrusion along x 100 | SolidCraft convention: flange height (15) is measured along the flange's outer face from the virtual sharp at the original edge (y 0, z 0), so the outer top corner is at (y, z) = (-15 cos45, 15 sin45). |
|
||||
| 71-sm-custom-rule | analytic | pass | | U channel, rule t 1.5, R 3, K 0.33 (K unused folded): base y 4.5..45.5 x 1.5; two 90 deg bend annuli R 3..4.5; flanges y 0..1.5 and 48.5..50, z 4.5..20; extrusion along x 80 | the rule edit applies to the whole sheet body (base built before it is 1.5 thick too, as the recipe's edge picks at z = 1.5 state) |
|
||||
| 72-sm-contour-flange | analytic | pass | | contour flange, width 50 along +Y, XZ section: plate x 0..35 z 0..2.5; bend R 2.5..5 about (35, 5) (sketch corner = outer virtual sharp, setback R+t); wall x 37.5..40 z 5..27.5; bend R 2.5..5 about (42.5, 27.5) (sketch corner = inner virtual sharp, setback R); plate x 42.5..70 z 30..32.5 | SolidCraft convention: the sketch lines are the sheet face away from the material (material right of travel); thickness grows to the material side |
|
||||
| 73-sm-hem | analytic | pass | | 68's part (height 20) plus a flat hem on the flange top edge: 180 deg bend, inner radius = hem gap 0.01, outer 2.51, about (y 2.51, z 20), folded toward +y (inside); hem leg 8 - (0.01 + 2.5) = 5.49: y 2.52..5.02, z 14.51..20; extrusion along x 100 | SolidCraft conventions: hem gap 0.01 mm (rule hem_gap), hem length measured to the outer face of the hem bend (top at z 22.51), hem folds over the flange's inner face. |
|
||||
| 74-sm-unfold-refold | analytic | pass | | U channel 80x50 (t 2.5, R 2.5, base y 5..45, flanges z 5..20) with the flat-pattern slot x 35..45, y -12..10 refolded: BA = (pi/2)(R + K t) = 5.654866776, so the slot removes base y 5..10, the whole y=0 bend and the flange from z 5 to 5 + (12 - (BA - 5)) = 16.345133224; x-segments 0..35 / 35..45 / 45..80 | unfold/refold maps flat distance through the K-factor neutral layer (K 0.44) |
|
||||
| 75-sm-bracket-holes | analytic | known failure (#296) | | base x 0..75, y 5..40, z 0..2.5 (trimmed 5 by each flange's setback); bend 1 (R 2.5..5 about y 5, z 5) x 0..75; flange 1 y 0..2.5, z 5..30, x 0..80; bend 2 (R 2.5..5 about x 75, z 5) y 5..40; flange 2 x 77.5..80, z 5..15, y 5..40; minus two d6 through holes at (25,20), (55,20) in the base. A = sum of prism areas - 2 x 4 joint areas (75t, 75t, 35t, 35t) + hole walls - hole discs | step 14 (solid.extrude): invalid input: sew: nonmanifold edge contact; SolidCraft convention: the second flange trims the base under the first bend, so bend 1 stops at x 75 while flange 1's panel runs to x 80 (open corner relief, no fill). |
|
||||
| 76-sm-thickness-variant | analytic | pass | | final rule thickness 3 (bend radius = Thickness = 3): base y 6..60 x 3; bend annulus R 3..6 about (y 6, z 6); flange y 0..3, z 6..20; extrusion along x 100 | the rule's thickness edits regenerate the whole sheet body; only the final 3 mm state matters |
|
||||
|
||||
## Not covered by a reference case
|
||||
|
||||
| Feature | Tested by |
|
||||
|---|---|
|
||||
| Boss (draft, root fillet, hole, ribs) | post, hole and rib volumes (`boss_with_hole_fillet_and_ribs`) |
|
||||
| Lip / Groove | band offsets and groove clearance on a shelled box (`lip_and_groove_on_a_shelled_box`, `groove_takes_the_rule_clearance`) |
|
||||
| Snap Fit | arm and catch volume, hook direction (`snap_fit_arm_and_catch`) |
|
||||
| Rest | rectangle, round, hollow and drafted pads (`rest_pads`) |
|
||||
| Plastic Rules (manage, assign) | library values, Thickness-based values, rule draft as the boss default (`plastic_rules_library_edit_and_assign`) |
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
# Kernel robustness
|
||||
|
||||
Real users hit edge cases the Fusion oracle (docs/oracle.md) never sees: faces that coincide
|
||||
Real users hit edge cases the reference tests (docs/oracle.md) never see: faces that coincide
|
||||
by accident, tangencies, seams lying where another body cuts, walls thinner than a shell. The
|
||||
robustness runs build random but realistic designs and check every step.
|
||||
|
||||
|
||||
@@ -18,88 +18,3 @@ display on the resulting body.
|
||||
## IGES
|
||||
|
||||
IGES 5.3 files (`.igs`/`.iges`) open, insert and drop the same way: `crates/kernel/src/iges_in.rs` (written from the published specification) restates the file as STEP and the STEP reader reads it, so the feature holds STEP text. Read: manifold solid B-reps (186) with their shells, faces, loops, edge and vertex lists; trimmed (144) and bounded (143) surfaces, and bare B-spline patches, sewn into a solid by their shared edges (an open body when they do not close); lines, circular arcs, ellipses, composite curves, rational B-spline curves; planes (108, 190), cylinders, cones, spheres, tori, rational B-spline surfaces, surfaces of revolution and tabulated cylinders; transformation matrices; 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.
|
||||
|
||||
## 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). 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.**
|
||||
|
||||
| Case | Result | Bodies | Volume mm³ (got / Fusion) | Area mm² (got / Fusion) | Faces (got / Fusion) | ms | Notes |
|
||||
|---|---|---|---|---|---|---|---|
|
||||
| 01-box | pass | Box | 24000.000 / 24000.000 | 5200.000 / 5200.000 | 6 / 6 | 3 | |
|
||||
| 02-cylinder | pass | Cylinder | 35339.429 / 35342.917 | 6125.845 / 6126.106 | 3 / 3 | 87 | |
|
||||
| 03-box-through-hole | pass | Box | 45738.283 / 45738.053 | 9327.809 / 9327.788 | 7 / 7 | 62 | |
|
||||
| 04-revolve-vase | pass | Vase | 112488.847 / 112499.479 | 13156.093 / 13156.615 | 5 / 5 | 511 | |
|
||||
| 05-taper-extrude | pass | Frustum | 61814.826 / 61814.826 | 9676.915 / 9676.915 | 6 / 6 | 2 | |
|
||||
| 06-symmetric-extrude | pass | Block | 30000.000 / 30000.000 | 6200.000 / 6200.000 | 6 / 6 | 1 | |
|
||||
| 07-fillet-all-edges | pass | Box | 23340.621 / 23340.849 | 4726.220 / 4726.265 | 26 / 26 | 532 | |
|
||||
| 08-chamfer | pass | Box | 23730.667 / 23730.667 | 5029.352 / 5029.352 | 10 / 10 | 3 | |
|
||||
| 09-shell | pass | Box | 13632.000 / 13632.000 | 13992.000 / 13992.000 | 11 / 11 | 2 | |
|
||||
| 10-rect-pattern-holes | pass | Plate | 29803.713 / 29803.540 | 14652.481 / 14652.389 | 18 / 18 | 252 | |
|
||||
| 11-circular-pattern-flange | pass | Flange | 66758.144 / 66758.844 | 19320.617 / 19320.795 | 10 / 10 | 168 | |
|
||||
| 12-mirror-feature | pass | Base | 21392.575 / 21392.920 | 6530.944 / 6530.973 | 10 / 10 | 80 | |
|
||||
| 13-combine-join | pass | A | 45570.295 / 45571.680 | 8634.196 / 8634.336 | 9 / 9 | 43 | |
|
||||
| 14-combine-cut | pass | A | 42933.034 / 42931.417 | 9706.783 / 9706.858 | 7 / 7 | 402 | |
|
||||
| 15-combine-intersect | pass | A | 54452.172 / 54454.273 | 7382.532 / 7382.743 | 7 / 7 | 1022 | |
|
||||
| 16-sweep | pass | Tube | 2584.428 / 2584.724 | 1779.638 / 1779.698 | 5 / 5 | 612 | |
|
||||
| 17-loft | pass | Loft | 35121.595 / 35125.498 | 6495.373 / 6495.000 | 6 / 6 | 191 | |
|
||||
| 18-hole-features | pass | Plate | 33890.908 / 33890.710 | 8568.695 / 8568.696 | 13 / 13 | 184 | |
|
||||
| 19-l-bracket-constrained | pass | LBracket | 14720.000 / 14720.000 | 5472.000 / 5472.000 | 8 / 8 | 2 | |
|
||||
| 20-slot-constrained | pass | Slot | 2965.428 / 2965.487 | 1774.644 / 1774.690 | 6 / 6 | 15 | |
|
||||
| 21-draft | pass | Box | 54574.737 / 54574.737 | 8781.998 / 8781.998 | 6 / 6 | 1 | |
|
||||
| 22-split-body | pass | Cyl, Cyl (1) | 75390.813 / 75398.224 | 12954.440 / 12955.175 | 6 / 6 | 109 | |
|
||||
| 23-bracket-multifeature | pass | Bracket | 59331.477 / 59331.129 | 18290.767 / 18290.737 | 19 / 19 | 117 | |
|
||||
| 24-user-parameters | pass | Block | 34822.022 / 34821.903 | 8114.175 / 8114.159 | 7 / 7 | 58 | |
|
||||
| 25-sketch-triangle | pass | Triangle | 4032.155 / 4032.155 | 2274.099 / 2274.099 | 5 / 5 | 1 | |
|
||||
| 26-sketch-belt | pass | Belt | 11326.042 / 11326.170 | 5637.347 / 5637.404 | 7 / 7 | 42 | |
|
||||
| 27-sketch-trapezoid | pass | Trapezoid | 5342.286 / 5342.257 | 3066.961 / 3066.946 | 8 / 8 | 53 | |
|
||||
| 28-blind-pocket-fillets | pass | Block | 84299.345 / 84299.249 | 16279.560 / 16279.572 | 23 / 23 | 320 | |
|
||||
| 29-two-sided-and-partial-revolve | pass | Block | 25731.356 / 25731.307 | 6312.589 / 6312.602 | 12 / 12 | 26 | |
|
||||
| 30-fillet-after-shell | pass | Box | 21745.915 / 21745.876 | 14968.284 / 14968.332 | 27 / 27 | 464 | |
|
||||
| 31-fillet-l-all-edges | pass | L | 14321.483 / 14321.626 | 5024.004 / 5024.045 | 38 / 38 | 747 | |
|
||||
| 32-boss-plate-fillets | pass | Part | 33590.273 / 33590.350 | 9929.854 / 9929.864 | 10 / 10 | 1214 | |
|
||||
| 33-fillet-tangent-chain | pass | Slot | 8237.037 / 8237.122 | 2793.541 / 2793.569 | 14 / 14 | 555 | |
|
||||
| 34-chamfer-then-fillet | pass | Box | 22892.383 / 22892.421 | 4753.677 / 4753.688 | 18 / 18 | 54 | |
|
||||
| 35-fillet-curved-curved | pass | Tee | 63169.685 / 63171.443 | 10269.168 / 10269.348 | 6 / 6 | 904 | |
|
||||
| 36-corner-blend-two-radii | pass | Box | 57799.504 / 57800.240 | 8585.437 / 8585.524 | 18 / 18 | 519 | |
|
||||
| 37-pyramid-apex-blend | pass | Pyramid | 15465.376 / 15465.575 | 3914.529 / 3914.581 | 18 / 18 | 312 | |
|
||||
| 38-coincident-cylinders | pass | A | 15706.406 / 15707.963 | 3769.760 / 3769.911 | 3 / 3 | 67 | |
|
||||
| 39-tee-pipe | pass | Tee | 24095.763 / 24096.237 | 16709.772 / 16710.048 | 7 / 7 | 182 | |
|
||||
| 40-box-sphere-torus | pass | A | 36287.558 / 36287.433 | 10696.143 / 10696.165 | 10 / 10 | 2393 | |
|
||||
| 41-tube-radial-holes | pass | Tube | 20243.046 / 20243.981 | 14873.689 / 14873.773 | 22 / 22 | 1409 | |
|
||||
| 42-overlapping-patterns | pass | Plate | 38488.411 / 38487.064 | 21687.215 / 21686.603 | 43 / 43 | 533 | solid 'Plate': imported as an open body (the shell is not closed) |
|
||||
| 43-cut-splits-body | pass | Bar, Bar (1) | 28000.000 / 28000.000 | 7200.000 / 7200.000 | 12 / 12 | 5 | |
|
||||
| 44-shell-l-bracket | pass | L | 8384.000 / 8384.000 | 8784.000 / 8784.000 | 15 / 15 | 3 | |
|
||||
| 45-shell-boss-plate | pass | Part | 13676.354 / 13676.602 | 14156.531 / 14156.602 | 15 / 15 | 69 | |
|
||||
| 46-shell-two-open-faces | pass | Box | 16044.000 / 16044.000 | 11398.000 / 11398.000 | 10 / 10 | 3 | |
|
||||
| 47-draft-curved-pocket | pass | Block | 111387.998 / 111387.712 | 17700.336 / 17700.361 | 11 / 11 | 27 | |
|
||||
| 48-pulley | pass | Pulley | 168508.160 / 168510.841 | 23718.215 / 23718.644 | 12 / 12 | 321 | |
|
||||
| 49-helical-sweep | pass | Spring | 4733.931 / 4738.099 | 4765.903 / 4767.652 | 3 / 3 | 10552 | solid 'Spring': face wrapping around a closed surface kept whole: split line does not reach the rings |
|
||||
| 50-sweep-tight-bend | pass | Bend | 3758.025 / 3758.443 | – / – | 5 / 5 | 405 | |
|
||||
| 51-loft-rect-circle-offset | pass | Loft | 61129.957 / 61133.116 | 9412.473 / 9411.655 | 6 / 6 | 251 | |
|
||||
| 52-circular-pattern-fillet | pass | Hub | 47206.182 / 47206.850 | 14141.988 / 14142.171 | 21 / 21 | 820 | |
|
||||
| 53-path-pattern | pass | Plate | 35010.499 / 35010.398 | 16463.947 / 16463.894 | 13 / 13 | 130 | |
|
||||
| 54-mirror-body-fillets | pass | Bracket | 22780.113 / 22780.089 | 6972.831 / 6972.832 | 12 / 12 | 98 | |
|
||||
| 55-tapped-hole-cosmetic | pass | Plate | 17542.360 / 17542.314 | 4761.590 / 4761.596 | 8 / 8 | 84 | |
|
||||
| 56-holes-on-curved-face | pass | Cyl | 77941.171 / 77942.388 | 10592.951 / 10593.139 | 7 / 7 | 378 | |
|
||||
| 57-hole-pattern-sketch-points | pass | Plate | 36767.029 / 36766.712 | 11818.165 / 11818.153 | 24 / 24 | 197 | |
|
||||
| 58-phone-stand | pass | Stand | 74559.442 / 74559.177 | 29148.741 / 29148.785 | 19 / 19 | 24 | |
|
||||
| 59-enclosure-base | pass | Enclosure | 34309.038 / 34309.486 | 33795.405 / 33795.610 | 39 / 39 | 286 | |
|
||||
| 60-pipe-flange | pass | Flange | 323661.027 / 323660.979 | 56984.270 / 56984.564 | 16 / 16 | 993 | |
|
||||
| 61-shaft-keyway | pass | Shaft | 47521.504 / 47525.946 | 9676.781 / 9677.096 | 14 / 14 | 182 | |
|
||||
| 62-heat-sink | pass | HeatSink | 43125.000 / 43125.000 | 45725.000 / 45725.000 | 66 / 66 | 8 | |
|
||||
| 63-hinge-knuckle | pass | Hinge | 4783.029 / 4783.117 | 4110.255 / 4110.287 | 14 / 14 | 118 | |
|
||||
| 64-sketch-tangent-arc-chain | pass | Wave | 4800.000 / 4800.000 | 3843.701 / 3843.712 | 9 / 9 | 10 | |
|
||||
| 65-sketch-gear-tooth | pass | Tooth | 3987.831 / 3988.084 | 1945.416 / 1945.485 | 9 / 9 | 8 | |
|
||||
| 66-sketch-linkage | pass | Link | 8045.410 / 8045.386 | 4308.172 / 4308.175 | 10 / 10 | 129 | |
|
||||
| 67-sm-base-flange | pass | Body1 | 15000.000 / 15000.000 | 12800.000 / 12800.000 | 6 / 6 | 2 | |
|
||||
| 68-sm-edge-flange | pass | Body1 | 18972.476 / 18972.622 | 16057.518 / 16057.550 | 14 / 14 | 26 | |
|
||||
| 69-sm-tray | pass | Body1 | 29665.828 / 29666.234 | 24979.993 / 24980.086 | 46 / 46 | 66 | |
|
||||
| 70-sm-flange-45 | pass | Body1 | 18450.704 / 18450.777 | 15629.621 / 15629.637 | 14 / 14 | 14 | |
|
||||
| 71-sm-custom-rule | pass | Body1 | 10053.579 / 10053.717 | 13896.249 / 13896.299 | 22 / 22 | 38 | |
|
||||
| 72-sm-contour-flange | pass | Body1 | 12097.478 / 12097.622 | 10411.968 / 10412.002 | 22 / 22 | 36 | |
|
||||
| 73-sm-hem | pass | Body1 | 21334.478 / 21334.723 | 17994.397 / 17994.473 | 22 / 22 | 60 | |
|
||||
| 74-sm-unfold-refold | pass | Body1 | 15800.086 / 15800.304 | 13610.275 / 13610.326 | 32 / 32 | 84 | |
|
||||
| 75-sm-bracket-holes | pass | Body1 | 13915.866 / 13916.012 | 12035.934 / 12035.962 | 25 / 25 | 100 | |
|
||||
| 76-sm-thickness-variant | pass | Body1 | 22520.368 / 22520.575 | 16064.089 / 16064.128 | 14 / 14 | 31 | |
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "box 40 x 30 x 20 from the origin: V = abc, A = 2(ab + bc + ca)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 24000.0,
|
||||
"area_mm2": 5200.0,
|
||||
"center_of_mass": [
|
||||
20.0,
|
||||
15.0,
|
||||
10.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"faces": 6
|
||||
}
|
||||
@@ -0,0 +1,153 @@
|
||||
{
|
||||
"id": "01-box",
|
||||
"name": "Box extrude",
|
||||
"description": "40 x 30 rectangle on XY extruded 20 mm.",
|
||||
"exercises": [
|
||||
"sketch",
|
||||
"extrude"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch1",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
40.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
40.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch1",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "cylinder r=15 h=50 on XY: V = pi r^2 h, A = 2 pi r^2 + 2 pi r h",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 35342.917352885175,
|
||||
"area_mm2": 6126.105674500097,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
25.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-15.0,
|
||||
-15.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
15.0,
|
||||
15.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"faces": 3
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
{
|
||||
"id": "02-cylinder",
|
||||
"name": "Cylinder",
|
||||
"description": "Circle r=15 at origin on XY extruded 50 mm.",
|
||||
"exercises": [
|
||||
"sketch",
|
||||
"circle",
|
||||
"extrude"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch1",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"radius": 15,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch1",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 50,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Cylinder"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "box 60x40x20 minus centred through cylinder r=6: V = abc - pi r^2 c, A = 2(ab+bc+ca) - 2 pi r^2 + 2 pi r c; hole is centred so the centroid stays at the box centre",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 45738.05328941535,
|
||||
"area_mm2": 9327.787565803084,
|
||||
"center_of_mass": [
|
||||
30.0,
|
||||
20.0,
|
||||
10.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
60.0,
|
||||
40.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"faces": 7
|
||||
}
|
||||
@@ -0,0 +1,240 @@
|
||||
{
|
||||
"id": "03-box-through-hole",
|
||||
"name": "Box with through hole",
|
||||
"description": "60x40x20 box; d=12 circle sketched on the top face at the face centre, cut through all.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"sketch_on_face",
|
||||
"extrude_cut",
|
||||
"through_all"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
60.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
60.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch2",
|
||||
"plane": {
|
||||
"ref": "face: top planar face of Box (z=20, normal +Z)",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
30.0,
|
||||
20.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"radius": 6,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Cut1",
|
||||
"operation": "cut",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch2",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "through_all",
|
||||
"direction": "negative"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "revolve of a line/3-point-arc profile (XZ, x = radius, z = -sketch y) 360 deg about Z. Pappus/Green: V = 2 pi * int x dA, z_c = int x z dA / int x dA, A = sum over profile edges of 2 pi int x ds (bottom disk r20, frustum r20->25, torus zone of the arc (centre (-16.000000, 33.000000), R=41.109610), frustum r15->18, top disk r18)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 112499.47865571639,
|
||||
"area_mm2": 13156.614546780698,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
35.737480169007014
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-25.10960958218893,
|
||||
-25.10960958218893,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
25.10960958218893,
|
||||
25.10960958218893,
|
||||
80.0
|
||||
]
|
||||
},
|
||||
"faces": 5
|
||||
}
|
||||
@@ -0,0 +1,201 @@
|
||||
{
|
||||
"id": "04-revolve-vase",
|
||||
"name": "Revolved vase profile",
|
||||
"description": "Closed profile (lines + 3-point arc) on XZ revolved 360 deg about the world Z axis.",
|
||||
"exercises": [
|
||||
"sketch_xz",
|
||||
"arc",
|
||||
"revolve"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Profile",
|
||||
"plane": {
|
||||
"ref": "XZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
20.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
25.0,
|
||||
-30.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "arc_three_point",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
25.0,
|
||||
-30.0
|
||||
]
|
||||
},
|
||||
"mid": [
|
||||
23.0,
|
||||
-46.0
|
||||
],
|
||||
"end": {
|
||||
"at": [
|
||||
15.0,
|
||||
-60.0
|
||||
]
|
||||
},
|
||||
"id": "a1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a1.end",
|
||||
"at": [
|
||||
15.0,
|
||||
-60.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
18.0,
|
||||
-80.0
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
18.0,
|
||||
-80.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0.0,
|
||||
-80.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l4.end",
|
||||
"at": [
|
||||
0.0,
|
||||
-80.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l5"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "revolve",
|
||||
"name": "Revolve1",
|
||||
"operation": "new",
|
||||
"profile": {
|
||||
"sketch": "Profile",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"a1",
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4",
|
||||
"l5"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"axis": {
|
||||
"ref": "world Z axis",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"dir": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
},
|
||||
"angle": 360,
|
||||
"result_bodies": [
|
||||
"Vase"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "square frustum: base 40 at z=0, top a1 = 40 + 2*30*tan10 at z=30 (positive taper grows outward); V = h/3 (A0 + A1 + sqrt(A0 A1)), z_c = h (A0 + 2 sqrt(A0A1) + 3 A1) / (4 (A0 + sqrt(A0A1) + A1)), sides = 4 trapezoids (a0+a1)/2 * h/cos(10)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 61814.82595953696,
|
||||
"area_mm2": 9676.91517028238,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
16.162703882981894
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-25.28980942125395,
|
||||
-25.28980942125395,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
25.28980942125395,
|
||||
25.28980942125395,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"faces": 6
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
{
|
||||
"id": "05-taper-extrude",
|
||||
"name": "Tapered extrude",
|
||||
"description": "40x40 square centred on origin, extruded 30 mm with a 10 deg taper.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"taper"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch1",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch1",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 30,
|
||||
"direction": "positive",
|
||||
"taper_deg": 10
|
||||
},
|
||||
"result_bodies": [
|
||||
"Frustum"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "50 (x) by 20 (z) rectangle on XZ extruded +-15 along Y: box 50x30x20 centred at origin",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 30000.0,
|
||||
"area_mm2": 6200.0,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-25.0,
|
||||
-15.0,
|
||||
-10.0
|
||||
],
|
||||
"max": [
|
||||
25.0,
|
||||
15.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
"faces": 6
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
{
|
||||
"id": "06-symmetric-extrude",
|
||||
"name": "Symmetric extrude",
|
||||
"description": "50x20 rectangle centred on origin on the XZ plane, extruded symmetrically 15 mm each side (30 total).",
|
||||
"exercises": [
|
||||
"sketch_xz",
|
||||
"extrude",
|
||||
"symmetric"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch1",
|
||||
"plane": {
|
||||
"ref": "XZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-25,
|
||||
-10
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
25,
|
||||
-10
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
25.0,
|
||||
-10.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
25,
|
||||
10
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
25.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-25,
|
||||
10
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-25.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-25.0,
|
||||
-10.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch1",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "symmetric",
|
||||
"distance": 15,
|
||||
"is_full_length": false,
|
||||
"meaning": "distance on each side"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Block"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "box 40x30x20 with every edge rounded r=3 = inner box (34x24x14) grown by a ball r (Steiner): V = abc + 2r(ab+bc+ca) + pi r^2 (a+b+c) + 4/3 pi r^3, A = 2(ab+bc+ca) + 2 pi r (a+b+c) + 4 pi r^2; faces 6 planes + 12 quarter cylinders + 8 sphere octants",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 23340.84937505542,
|
||||
"area_mm2": 4726.265361880023,
|
||||
"center_of_mass": [
|
||||
20.0,
|
||||
15.0,
|
||||
10.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"faces": 26
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
{
|
||||
"id": "07-fillet-all-edges",
|
||||
"name": "Box with all edges filleted",
|
||||
"description": "40x30x20 box, constant-radius 3 mm fillet on all 12 edges.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"fillet"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
40.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
40.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "Fillet1",
|
||||
"type": "constant_radius",
|
||||
"radius": 3,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "all 12 edges of Box",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
30.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
30.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
30.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
15.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
15.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
0.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
0.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
15.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
15.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "box 40x30x20 with the 4 top edges chamfered 2: lower box 40x30x18 + top slab with section (36+2u)x(26+2u) at depth u (V, moment by exact polynomial integration); A = bottom + sides(h=18) + top 36x26 + 4 trapezoid bevels of slant 2 sqrt2",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 23730.666666666668,
|
||||
"area_mm2": 5029.352380466497,
|
||||
"center_of_mass": [
|
||||
19.999999999999996,
|
||||
15.0,
|
||||
9.894145409596582
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"faces": 10
|
||||
}
|
||||
@@ -0,0 +1,235 @@
|
||||
{
|
||||
"id": "08-chamfer",
|
||||
"name": "Box with top edges chamfered",
|
||||
"description": "40x30x20 box, equal-distance 2 mm chamfer on the 4 edges of the top face.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"chamfer"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
40.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
40.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "chamfer",
|
||||
"name": "Chamfer1",
|
||||
"type": "equal_distance",
|
||||
"distance": 2,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the 4 edges bounding the top face (z=20)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
15.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
0.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
15.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "50x40x30 box shelled inward 2 with the top open: box minus cavity [2,48]x[2,38]x[2,30]; A = outer bottom + outer sides + rim + cavity floor + cavity walls",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 13632.0,
|
||||
"area_mm2": 13992.0,
|
||||
"center_of_mass": [
|
||||
25.0,
|
||||
20.0,
|
||||
11.598591549295774
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
50.0,
|
||||
40.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"faces": 11
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
{
|
||||
"id": "09-shell",
|
||||
"name": "Shelled open box",
|
||||
"description": "50x40x30 box shelled inward 2 mm with the top face removed.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"shell"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
50,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
50.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
50,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
50.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 30,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "shell",
|
||||
"name": "Shell1",
|
||||
"faces_removed": [
|
||||
{
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
25.0,
|
||||
20.0,
|
||||
30.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"face_selection": "top face (z=30)",
|
||||
"inside_thickness": 2,
|
||||
"outside_thickness": 0,
|
||||
"tangent_chain": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "plate 90x70x5 minus 12 through cylinders r=3 at (15+20i, 15+20j), i<4, j<3: V = abc - 12 pi r^2 t, A = 2(ab+bc+ca) + 12(2 pi r t - 2 pi r^2)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 29803.53996706153,
|
||||
"area_mm2": 14652.38934211693,
|
||||
"center_of_mass": [
|
||||
44.99999999999998,
|
||||
34.99999999999999,
|
||||
2.4999999999999964
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
90.0,
|
||||
70.0,
|
||||
5.0
|
||||
]
|
||||
},
|
||||
"faces": 18
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
{
|
||||
"id": "10-rect-pattern-holes",
|
||||
"name": "Rectangular pattern of holes",
|
||||
"description": "90x70x5 plate; d=6 hole at (15,15) cut through; feature pattern 4 along X @20 mm x 3 along Y @20 mm.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"extrude_cut",
|
||||
"rectangular_pattern"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Plate_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
90,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
90.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
90,
|
||||
70
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
90.0,
|
||||
70.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
70
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
70.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Plate",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Plate_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 5,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Plate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "HoleSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
15,
|
||||
15
|
||||
]
|
||||
},
|
||||
"radius": 3,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Hole1",
|
||||
"operation": "cut",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "HoleSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "through_all",
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Plate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "rectangular_pattern",
|
||||
"name": "RectPattern1",
|
||||
"pattern_of": "features",
|
||||
"features": [
|
||||
"Hole1"
|
||||
],
|
||||
"direction1": {
|
||||
"axis": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"count": 4,
|
||||
"spacing": 20
|
||||
},
|
||||
"direction2": {
|
||||
"axis": [
|
||||
0,
|
||||
1,
|
||||
0
|
||||
],
|
||||
"count": 3,
|
||||
"spacing": 20
|
||||
},
|
||||
"distance_type": "spacing (distance between instances)"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "annulus R50/r15 h10 minus 6 through cylinders r=5 on a 40 radius: V = pi(R^2-r^2)h - 6 pi rh^2 h, A = 2 pi(R^2-r^2) + 2 pi(R+r)h + 6(2 pi rh h - 2 pi rh^2); 6-fold symmetry keeps the centroid on the axis",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 66758.84388878311,
|
||||
"area_mm2": 19320.79481957723,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
5.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-50.0,
|
||||
-50.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
50.0,
|
||||
50.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
"faces": 10
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
{
|
||||
"id": "11-circular-pattern-flange",
|
||||
"name": "Flange with circular hole pattern",
|
||||
"description": "Annulus OD100 / ID30 extruded 10; d=10 hole at (40,0) cut through; circular pattern x6 over 360 about Z.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"annulus_profile",
|
||||
"extrude_cut",
|
||||
"circular_pattern"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "FlangeSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"radius": 50,
|
||||
"id": "c_out"
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"ref": "c_out.center",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"radius": 15,
|
||||
"id": "c_in"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Flange",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "FlangeSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c_out"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outer": false,
|
||||
"curves": [
|
||||
"c_in"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 10,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Flange"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "HoleSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
40,
|
||||
0
|
||||
]
|
||||
},
|
||||
"radius": 5,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Hole1",
|
||||
"operation": "cut",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "HoleSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "through_all",
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Flange"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "circular_pattern",
|
||||
"name": "CircPattern1",
|
||||
"pattern_of": "features",
|
||||
"features": [
|
||||
"Hole1"
|
||||
],
|
||||
"axis": {
|
||||
"ref": "world Z axis",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"dir": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
},
|
||||
"count": 6,
|
||||
"total_angle": 360,
|
||||
"symmetric": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "block [-30,30]x[0,30]x[0,10] + two bosses r=6 h=15 at (+-15,15) on top (mirror across YZ): V = abc + 2 pi r^2 h, A = 2(ab+bc+ca) + 2 * 2 pi r h",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 21392.920065876977,
|
||||
"area_mm2": 6530.973355292325,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
14.999999999999998,
|
||||
6.98250171986157
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-30.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
30.0,
|
||||
30.0,
|
||||
25.0
|
||||
]
|
||||
},
|
||||
"faces": 10
|
||||
}
|
||||
@@ -0,0 +1,262 @@
|
||||
{
|
||||
"id": "12-mirror-feature",
|
||||
"name": "Mirror feature",
|
||||
"description": "60x30x10 block centred on X; d=12 boss 15 tall joined at (15,15) on top; boss feature mirrored across YZ.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"extrude_join",
|
||||
"mirror"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Base_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-30,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
30,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
30.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
30,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
30.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-30,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-30.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-30.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Base",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Base_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 10,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Base"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "BossSketch",
|
||||
"plane": {
|
||||
"ref": "face: top face of Base (z=10)",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
10.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
15.0,
|
||||
10.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
15.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"radius": 6,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Boss",
|
||||
"operation": "join",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "BossSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 15,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Base"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "mirror",
|
||||
"name": "Mirror1",
|
||||
"mirror_of": "features",
|
||||
"features": [
|
||||
"Boss"
|
||||
],
|
||||
"plane": {
|
||||
"ref": "YZ",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"normal": [
|
||||
1,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"operation": "join"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "box [0,40]^2x[0,20] union cylinder r=12 at (40,20) z 0..40: V = box + half-cylinder (x>40, z<20, centroid 40 + 4r/3pi) + full cylinder z 20..40; A = bottom (1600 + pi r^2/2) + box top (1600 - pi r^2/2) + cyl top + 3 full sides + x=40 side minus 24x20 + cyl side (pi r 40 outside half + pi r 20 upper inside half)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 45571.68026350791,
|
||||
"area_mm2": 8634.336052701581,
|
||||
"center_of_mass": [
|
||||
26.46176756194705,
|
||||
19.999999999999996,
|
||||
13.970793611305016
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
52.0,
|
||||
40.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"faces": 9
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
{
|
||||
"id": "13-combine-join",
|
||||
"name": "Boolean union",
|
||||
"description": "Box 40x40x20 at origin + cylinder r=12 h=40 at (40,20) as separate bodies, then combine join.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"combine_join"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "BoxA_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
40.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
40.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "BoxA",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "BoxA_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "CylSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
40,
|
||||
20
|
||||
]
|
||||
},
|
||||
"radius": 12,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "CylB",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "CylSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 40,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "combine",
|
||||
"name": "Combine1",
|
||||
"operation": "join",
|
||||
"target_body": "A",
|
||||
"tool_bodies": [
|
||||
"B"
|
||||
],
|
||||
"keep_tools": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "box [-25,25]^2x[-20,0] minus sphere r=15 at the origin (lower hemisphere removed): V = abc - 2/3 pi r^3, hemisphere centroid z = -3r/8; A = 2(ab+bc+ca) - pi r^2 + 2 pi r^2",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 42931.41652942297,
|
||||
"area_mm2": 9706.858347057703,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
-10.720336182305564
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-25.0,
|
||||
-25.0,
|
||||
-20.0
|
||||
],
|
||||
"max": [
|
||||
25.0,
|
||||
25.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"faces": 7
|
||||
}
|
||||
@@ -0,0 +1,267 @@
|
||||
{
|
||||
"id": "14-combine-cut",
|
||||
"name": "Boolean subtract",
|
||||
"description": "Box (-25,-25,-20)..(25,25,0) minus a sphere r=15 at origin (hemispherical dimple).",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"revolve",
|
||||
"combine_cut"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "BoxA_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-25,
|
||||
-25
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
25,
|
||||
-25
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
25.0,
|
||||
-25.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
25,
|
||||
25
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
25.0,
|
||||
25.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-25,
|
||||
25
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-25.0,
|
||||
25.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-25.0,
|
||||
-25.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "BoxA",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "BoxA_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "negative"
|
||||
},
|
||||
"result_bodies": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "SphereB_sketch",
|
||||
"plane": {
|
||||
"ref": "XZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "arc_three_point",
|
||||
"start": {
|
||||
"at": [
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"mid": [
|
||||
15.0,
|
||||
0.0
|
||||
],
|
||||
"end": {
|
||||
"at": [
|
||||
0.0,
|
||||
-15.0
|
||||
]
|
||||
},
|
||||
"id": "a1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a1.end",
|
||||
"at": [
|
||||
0.0,
|
||||
-15.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "a1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "revolve",
|
||||
"name": "SphereB",
|
||||
"operation": "new",
|
||||
"profile": {
|
||||
"sketch": "SphereB_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"a1",
|
||||
"l1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"axis": {
|
||||
"ref": "world Z axis",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"dir": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
},
|
||||
"angle": 360,
|
||||
"result_bodies": [
|
||||
"B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "combine",
|
||||
"name": "Combine1",
|
||||
"operation": "cut",
|
||||
"target_body": "A",
|
||||
"tool_bodies": [
|
||||
"B"
|
||||
],
|
||||
"keep_tools": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "cube [-20,20]^3 intersect sphere R=25: sphere minus 6 disjoint caps of height h=5 (caps cannot overlap: two would need x^2+y^2 > 800 > R^2); V = 4/3 pi R^3 - 6 pi h^2 (3R-h)/3, A = 4 pi R^2 - 6 * 2 pi R h + 6 pi (R^2-20^2)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 54454.27266222308,
|
||||
"area_mm2": 7382.7427359360145,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-20.0,
|
||||
-20.0,
|
||||
-20.0
|
||||
],
|
||||
"max": [
|
||||
20.0,
|
||||
20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"faces": 7
|
||||
}
|
||||
@@ -0,0 +1,269 @@
|
||||
{
|
||||
"id": "15-combine-intersect",
|
||||
"name": "Boolean intersect",
|
||||
"description": "Cube 40 centred at origin intersected with sphere r=25 at origin.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"symmetric",
|
||||
"revolve",
|
||||
"combine_intersect"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "CubeSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "CubeA",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "CubeSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "symmetric",
|
||||
"distance": 20,
|
||||
"is_full_length": false,
|
||||
"meaning": "distance on each side"
|
||||
},
|
||||
"result_bodies": [
|
||||
"A"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "SphereB_sketch",
|
||||
"plane": {
|
||||
"ref": "XZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "arc_three_point",
|
||||
"start": {
|
||||
"at": [
|
||||
0.0,
|
||||
25.0
|
||||
]
|
||||
},
|
||||
"mid": [
|
||||
25.0,
|
||||
0.0
|
||||
],
|
||||
"end": {
|
||||
"at": [
|
||||
0.0,
|
||||
-25.0
|
||||
]
|
||||
},
|
||||
"id": "a1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a1.end",
|
||||
"at": [
|
||||
0.0,
|
||||
-25.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "a1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
25.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "revolve",
|
||||
"name": "SphereB",
|
||||
"operation": "new",
|
||||
"profile": {
|
||||
"sketch": "SphereB_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"a1",
|
||||
"l1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"axis": {
|
||||
"ref": "world Z axis",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"dir": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
},
|
||||
"angle": 360,
|
||||
"result_bodies": [
|
||||
"B"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "combine",
|
||||
"name": "Combine1",
|
||||
"operation": "intersect",
|
||||
"target_body": "A",
|
||||
"tool_bodies": [
|
||||
"B"
|
||||
],
|
||||
"keep_tools": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "circle r=3 swept along line 30 (+Z) + tangent R20 quarter arc + line 30 (+X): two cylinders + quarter torus (Pappus: V = pi r^2 R theta, A = 2 pi r R theta; torus centroid at rho = (R^2 + r^2/4)/R * sin(theta/2)/(theta/2) from the bend axis along the bisector) + 2 end disks",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 2584.7244290365306,
|
||||
"area_mm2": 1779.6982871223033,
|
||||
"center_of_mass": [
|
||||
13.958930123810118,
|
||||
0.0,
|
||||
36.04106987618987
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-3.0,
|
||||
-3.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
50.0,
|
||||
3.0,
|
||||
53.0
|
||||
]
|
||||
},
|
||||
"faces": 5
|
||||
}
|
||||
@@ -0,0 +1,182 @@
|
||||
{
|
||||
"id": "16-sweep",
|
||||
"name": "Sweep along path",
|
||||
"description": "d=6 circle on XY at origin swept along a path on XZ: line up 30, tangent R20 arc, line 30 along +X.",
|
||||
"exercises": [
|
||||
"sketch_xz",
|
||||
"sweep",
|
||||
"path_chain"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Path",
|
||||
"plane": {
|
||||
"ref": "XZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0.0,
|
||||
-30.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "arc_three_point",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
0.0,
|
||||
-30.0
|
||||
]
|
||||
},
|
||||
"mid": [
|
||||
5.857864376,
|
||||
-44.142135624
|
||||
],
|
||||
"end": {
|
||||
"at": [
|
||||
20.0,
|
||||
-50.0
|
||||
]
|
||||
},
|
||||
"id": "a1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a1.end",
|
||||
"at": [
|
||||
20.0,
|
||||
-50.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
50.0,
|
||||
-50.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "ProfileSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"radius": 3,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "sweep",
|
||||
"name": "Sweep1",
|
||||
"operation": "new",
|
||||
"profile": {
|
||||
"sketch": "ProfileSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"path": {
|
||||
"sketch": "Path",
|
||||
"curves": [
|
||||
"l1",
|
||||
"a1",
|
||||
"l2"
|
||||
],
|
||||
"chained": true
|
||||
},
|
||||
"orientation": "perpendicular",
|
||||
"result_bodies": [
|
||||
"Tube"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part.makeLoft",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 35125.49757633739,
|
||||
"area_mm2": 6495.0000512370025,
|
||||
"center_of_mass": [
|
||||
4.902280481153527e-11,
|
||||
9.800242527565077e-16,
|
||||
15.119060422795876
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-20.0000001,
|
||||
-20.0000001,
|
||||
-1e-07
|
||||
],
|
||||
"max": [
|
||||
20.0000001,
|
||||
20.0000001,
|
||||
40.000000100000015
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
{
|
||||
"id": "17-loft",
|
||||
"name": "Loft square to circle",
|
||||
"description": "Loft from a 40x40 square on XY (z=0) to a d=20 circle on a plane offset 40 mm.",
|
||||
"exercises": [
|
||||
"loft",
|
||||
"offset_plane"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Bottom",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "construction_plane",
|
||||
"name": "Plane40",
|
||||
"method": "offset",
|
||||
"base": "XY",
|
||||
"offset": 40
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Top",
|
||||
"plane": {
|
||||
"ref": "construction plane Plane40 (XY offset 40)",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
40.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"radius": 10,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "loft",
|
||||
"name": "Loft1",
|
||||
"operation": "new",
|
||||
"sections": [
|
||||
{
|
||||
"sketch": "Bottom",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sketch": "Top",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"end_conditions": "free (none)",
|
||||
"rails": [],
|
||||
"solid": true,
|
||||
"result_bodies": [
|
||||
"Loft"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "plate 60x40x15 minus: counterbore (r7 x 5 deep + r4 through), countersink (90 deg cone frustum r6->r3 over 3 mm + r3 through), blind r3 hole 10 deep to the shoulder with a 118 deg cone point (height 3/tan59, centroid 1/4 up from the apex); areas: removed top/bottom disks, cylinder walls 2 pi r h, cb floor annulus, cone laterals pi r s",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 33890.710358950084,
|
||||
"area_mm2": 8568.695781624992,
|
||||
"center_of_mass": [
|
||||
30.307000593669976,
|
||||
20.000000000000004,
|
||||
7.38224663372136
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
60.0,
|
||||
40.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"faces": 13
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"id": "18-hole-features",
|
||||
"name": "Hole features (counterbore, countersink, blind drilled)",
|
||||
"description": "60x40x15 plate; on the top face: counterbore hole at (15,20) through all, countersink at (35,20) through all, simple blind hole depth 10 at (50,20) with 118 deg drill tip.",
|
||||
"exercises": [
|
||||
"hole_counterbore",
|
||||
"hole_countersink",
|
||||
"hole_blind"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Plate_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
60.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
60.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Plate",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Plate_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 15,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Plate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "hole",
|
||||
"name": "CBore",
|
||||
"hole_type": "counterbore",
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
30.0,
|
||||
20.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"position": [
|
||||
15,
|
||||
20,
|
||||
15
|
||||
],
|
||||
"direction": "into the material (-Z)",
|
||||
"diameter": 8,
|
||||
"counterbore_diameter": 14,
|
||||
"counterbore_depth": 5,
|
||||
"extent": "through_all"
|
||||
},
|
||||
{
|
||||
"op": "hole",
|
||||
"name": "CSink",
|
||||
"hole_type": "countersink",
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
30.0,
|
||||
20.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"position": [
|
||||
35,
|
||||
20,
|
||||
15
|
||||
],
|
||||
"direction": "into the material (-Z)",
|
||||
"diameter": 6,
|
||||
"countersink_diameter": 12,
|
||||
"countersink_angle": 90,
|
||||
"extent": "through_all"
|
||||
},
|
||||
{
|
||||
"op": "hole",
|
||||
"name": "Blind",
|
||||
"hole_type": "simple",
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
25.5,
|
||||
20.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"position": [
|
||||
50,
|
||||
20,
|
||||
15
|
||||
],
|
||||
"direction": "into the material (-Z)",
|
||||
"diameter": 6,
|
||||
"depth": 10,
|
||||
"tip_angle": 118,
|
||||
"extent": "distance"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "solved L: (0,0)-(60,0)-(60,8)-(8,8)-(8,40)-(0,40); area 60x8 + 8x32, extruded 20: V = A2 h, A = 2 A2 + perimeter*h, centroid of the two rectangles",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 14720.0,
|
||||
"area_mm2": 5472.0,
|
||||
"center_of_mass": [
|
||||
20.956521739130434,
|
||||
10.956521739130435,
|
||||
10.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
60.0,
|
||||
40.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"faces": 8
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
{
|
||||
"id": "19-l-bracket-constrained",
|
||||
"name": "Constrained L-bracket sketch extruded",
|
||||
"description": "L outline drawn roughly, fully constrained with H/V constraints + 4 dimensions + corner on origin; solved profile extruded 20 mm.",
|
||||
"exercises": [
|
||||
"sketch_constraints",
|
||||
"sketch_dimensions",
|
||||
"extrude"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "LSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
55,
|
||||
-2
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
55.0,
|
||||
-2.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
57,
|
||||
9
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
57.0,
|
||||
9.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
10,
|
||||
7
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
10.0,
|
||||
7.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
9,
|
||||
45
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l4.end",
|
||||
"at": [
|
||||
9.0,
|
||||
45.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-1,
|
||||
38
|
||||
]
|
||||
},
|
||||
"id": "l5"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l5.end",
|
||||
"at": [
|
||||
-1.0,
|
||||
38.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
1.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"id": "l6"
|
||||
}
|
||||
],
|
||||
"constraints": [
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l5"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "vertical",
|
||||
"refs": [
|
||||
"l2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "vertical",
|
||||
"refs": [
|
||||
"l4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "vertical",
|
||||
"refs": [
|
||||
"l6"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"origin"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"l1.end"
|
||||
],
|
||||
"value": 60,
|
||||
"unit": "mm",
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l2.start",
|
||||
"l2.end"
|
||||
],
|
||||
"value": 8,
|
||||
"unit": "mm",
|
||||
"orientation": "vertical"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l6.start",
|
||||
"l6.end"
|
||||
],
|
||||
"value": 40,
|
||||
"unit": "mm",
|
||||
"orientation": "vertical"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l5.start",
|
||||
"l5.end"
|
||||
],
|
||||
"value": 8,
|
||||
"unit": "mm",
|
||||
"orientation": "horizontal"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "LSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4",
|
||||
"l5",
|
||||
"l6"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"LBracket"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "obround: arc centres (0,0) and (40,0), R6, tangent horizontal lines y=+-6: A2 = 2rL + pi r^2, V = A2 h, A = 2 A2 + perimeter (2L + 2 pi r) h",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 2965.4866776461627,
|
||||
"area_mm2": 1774.6902302738526,
|
||||
"center_of_mass": [
|
||||
20.0,
|
||||
0.0,
|
||||
2.5
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-6.0,
|
||||
-6.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
46.0,
|
||||
6.0,
|
||||
5.0
|
||||
]
|
||||
},
|
||||
"faces": 6
|
||||
}
|
||||
@@ -0,0 +1,225 @@
|
||||
{
|
||||
"id": "20-slot-constrained",
|
||||
"name": "Constrained slot extruded",
|
||||
"description": "Obround: two arcs + two lines drawn inexactly, solved with tangent/equal/horizontal constraints, R6 and 40 mm centre distance; extruded 5.",
|
||||
"exercises": [
|
||||
"sketch_constraints",
|
||||
"tangent",
|
||||
"equal",
|
||||
"extrude"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "SlotSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "arc_center_start_sweep",
|
||||
"center": {
|
||||
"at": [
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"at": [
|
||||
1,
|
||||
7
|
||||
]
|
||||
},
|
||||
"sweep_deg": 180,
|
||||
"id": "a1"
|
||||
},
|
||||
{
|
||||
"type": "arc_center_start_sweep",
|
||||
"center": {
|
||||
"at": [
|
||||
37,
|
||||
2
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"at": [
|
||||
37,
|
||||
-3
|
||||
]
|
||||
},
|
||||
"sweep_deg": 180,
|
||||
"id": "a2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a1.end",
|
||||
"at": [
|
||||
1.0,
|
||||
-5.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "a2.start",
|
||||
"at": [
|
||||
37.0,
|
||||
-3.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a2.end",
|
||||
"at": [
|
||||
37.0,
|
||||
7.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "a1.start",
|
||||
"at": [
|
||||
1.0,
|
||||
7.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
}
|
||||
],
|
||||
"constraints": [
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"a1.center",
|
||||
"origin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"a1",
|
||||
"l1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"l1",
|
||||
"a2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"a2",
|
||||
"l2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"l2",
|
||||
"a1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "equal",
|
||||
"refs": [
|
||||
"a1",
|
||||
"a2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
{
|
||||
"type": "radius",
|
||||
"refs": [
|
||||
"a1"
|
||||
],
|
||||
"value": 6,
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"a1.center",
|
||||
"a2.center"
|
||||
],
|
||||
"value": 40,
|
||||
"unit": "mm",
|
||||
"orientation": "horizontal"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "SlotSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"a1",
|
||||
"a2",
|
||||
"l1",
|
||||
"l2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 5,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Slot"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "40x40x30 box, 5 deg draft hinged on the bottom (z=0), pull -Z: square frustum 40 -> 40 + 2*30*tan5; V = h/3 (A0 + A1 + sqrt(A0A1)), sides 4 trapezoids of slant h/cos5",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 54574.73735870641,
|
||||
"area_mm2": 8781.998445243411,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
15.614983642248854
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-22.62465990577772,
|
||||
-22.62465990577772,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
22.62465990577772,
|
||||
22.62465990577772,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"faces": 6
|
||||
}
|
||||
@@ -0,0 +1,253 @@
|
||||
{
|
||||
"id": "21-draft",
|
||||
"name": "Draft side faces",
|
||||
"description": "40x40x30 box (centred on origin in XY); 5 deg draft on the 4 side faces, neutral plane = bottom face.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"draft"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
-20
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-20.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 30,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "draft",
|
||||
"name": "Draft1",
|
||||
"faces": [
|
||||
{
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
20.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
-20.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
-1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
-20.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"face_selection": "the 4 vertical side faces",
|
||||
"neutral_plane": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"neutral_selection": "bottom face (z=0)",
|
||||
"angle": 5,
|
||||
"flipped": false
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "cylinder r20 z -30..30 split by the plane through the X axis tilted 30 deg (z = y tan30, stays within |z|<=11.6 inside the cylinder): 2 congruent halves (point symmetry), V total = pi r^2 h, A total = cylinder + 2 ellipses pi r (r/cos30)",
|
||||
"body_count": 2,
|
||||
"volume_mm3": 75398.22368615503,
|
||||
"area_mm2": 12955.175474262087,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-20.0,
|
||||
-20.0,
|
||||
-30.0
|
||||
],
|
||||
"max": [
|
||||
20.0,
|
||||
20.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"faces": 6
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
{
|
||||
"id": "22-split-body",
|
||||
"name": "Split body by angled plane",
|
||||
"description": "Cylinder r=20, z from -30 to 30 (symmetric extrude) split by a plane containing the X axis tilted 30 deg from XY.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"symmetric",
|
||||
"construction_plane_angle",
|
||||
"split_body"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch1",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"radius": 20,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch1",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "symmetric",
|
||||
"distance": 30,
|
||||
"is_full_length": false,
|
||||
"meaning": "distance on each side"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Cyl"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "construction_plane",
|
||||
"name": "Tilt30",
|
||||
"method": "at_angle",
|
||||
"axis": "world X axis",
|
||||
"base_plane": "XY",
|
||||
"angle": 30,
|
||||
"result_origin": [
|
||||
12.125,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"result_normal": [
|
||||
0.0,
|
||||
-0.5,
|
||||
0.866025404
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "split_body",
|
||||
"name": "Split1",
|
||||
"body": "Cyl",
|
||||
"tool": "Tilt30",
|
||||
"extend_tool": true,
|
||||
"result_bodies": [
|
||||
"Cyl",
|
||||
"Cyl (1)"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part makeFillet, Part extrude join, Part extrude cut, Part makeChamfer",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 59331.128616959795,
|
||||
"area_mm2": 18290.736978057597,
|
||||
"center_of_mass": [
|
||||
40.00000000000001,
|
||||
34.567350242237325,
|
||||
15.289013135744947
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
80.0,
|
||||
50.0,
|
||||
50.0
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,693 @@
|
||||
{
|
||||
"id": "23-bracket-multifeature",
|
||||
"name": "Multi-feature bracket",
|
||||
"description": "Base plate 80x50x8, back wall, inner fillet R5, gusset, 2 base holes, wall hole, front corner chamfers.",
|
||||
"exercises": [
|
||||
"extrude",
|
||||
"extrude_join",
|
||||
"fillet",
|
||||
"symmetric",
|
||||
"offset_plane",
|
||||
"extrude_cut",
|
||||
"chamfer"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Base_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
80,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
80.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
80,
|
||||
50
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
80.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
50
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Base",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Base_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 8,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Bracket"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "WallSketch",
|
||||
"plane": {
|
||||
"ref": "face: top of Base (z=8)",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
8.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
25.0,
|
||||
8.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
80.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
80.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
80.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
80.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
50.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Wall",
|
||||
"operation": "join",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "WallSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 42,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Bracket"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "InnerFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 5,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the X-parallel concave edge where base top (z=8) meets wall front (y=42)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
80.0,
|
||||
42.0,
|
||||
8.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
42.0,
|
||||
8.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
42.0,
|
||||
8.0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "construction_plane",
|
||||
"name": "MidPlane",
|
||||
"method": "offset",
|
||||
"base": "YZ",
|
||||
"offset": 40
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "GussetSketch",
|
||||
"plane": {
|
||||
"ref": "construction plane MidPlane (YZ offset 40)",
|
||||
"origin": [
|
||||
40.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-8.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-35.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
-35.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-8.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
-8.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-8.0,
|
||||
42.0
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Gusset",
|
||||
"operation": "join",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "GussetSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "symmetric",
|
||||
"distance": 3,
|
||||
"is_full_length": false,
|
||||
"meaning": "distance on each side"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Bracket"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "BaseHoles",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
20,
|
||||
20
|
||||
]
|
||||
},
|
||||
"radius": 4,
|
||||
"id": "c1"
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
60,
|
||||
20
|
||||
]
|
||||
},
|
||||
"radius": 4,
|
||||
"id": "c2"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "BaseHoles",
|
||||
"operation": "cut",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "BaseHoles",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sketch": "BaseHoles",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "through_all",
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Bracket"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "WallHole",
|
||||
"plane": {
|
||||
"ref": "face: wall front face (y=42, normal -Y)",
|
||||
"origin": [
|
||||
0.0,
|
||||
42.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0
|
||||
],
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
-1.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
61.5,
|
||||
42.0,
|
||||
31.5
|
||||
]
|
||||
}
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
20.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"radius": 5,
|
||||
"id": "c1"
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
60.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"radius": 5,
|
||||
"id": "c2"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "WallHoles",
|
||||
"operation": "cut",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "WallHole",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sketch": "WallHole",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 8,
|
||||
"direction": "negative"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Bracket"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "chamfer",
|
||||
"name": "FrontChamfer",
|
||||
"type": "equal_distance",
|
||||
"distance": 3,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the 2 vertical edges at the front corners of the base (y=0, z 0..8)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
80.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
80.0,
|
||||
0.0,
|
||||
8.0
|
||||
],
|
||||
"point": [
|
||||
80.0,
|
||||
0.0,
|
||||
4.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
8.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
4.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "width 60 x depth 40 x height 15 block from the origin with a centred d10 through hole: V = (wd - pi r^2) h, A = 2(wd - pi r^2) + 2(w+d)h + 2 pi r h",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 34821.90275490383,
|
||||
"area_mm2": 8114.15926535898,
|
||||
"center_of_mass": [
|
||||
30.0,
|
||||
20.0,
|
||||
7.5
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
60.0,
|
||||
40.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"faces": 7
|
||||
}
|
||||
@@ -0,0 +1,275 @@
|
||||
{
|
||||
"id": "24-user-parameters",
|
||||
"name": "Parametric block driven by user parameters",
|
||||
"description": "Rectangle sized by params width x depth (corner at origin), centred hole d=hole_d, extruded height.",
|
||||
"exercises": [
|
||||
"user_parameters",
|
||||
"sketch_constraints",
|
||||
"dimension_expressions",
|
||||
"extrude",
|
||||
"recompute"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [
|
||||
{
|
||||
"name": "width",
|
||||
"expression": "60 mm",
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"name": "depth",
|
||||
"expression": "40 mm",
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"name": "height",
|
||||
"expression": "15 mm",
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"name": "hole_d",
|
||||
"expression": "10 mm",
|
||||
"unit": "mm"
|
||||
}
|
||||
],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch1",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
50,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
50.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
50,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
50.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
25,
|
||||
15
|
||||
]
|
||||
},
|
||||
"radius": 4,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"origin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "vertical",
|
||||
"refs": [
|
||||
"l2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "vertical",
|
||||
"refs": [
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"l1.end"
|
||||
],
|
||||
"value": "width",
|
||||
"unit": "mm",
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l2.start",
|
||||
"l2.end"
|
||||
],
|
||||
"value": "depth",
|
||||
"unit": "mm",
|
||||
"orientation": "vertical"
|
||||
},
|
||||
{
|
||||
"type": "diameter",
|
||||
"refs": [
|
||||
"c1"
|
||||
],
|
||||
"value": "hole_d",
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"origin",
|
||||
"c1.center"
|
||||
],
|
||||
"value": "width / 2",
|
||||
"unit": "mm",
|
||||
"orientation": "horizontal"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"origin",
|
||||
"c1.center"
|
||||
],
|
||||
"value": "depth / 2",
|
||||
"unit": "mm",
|
||||
"orientation": "vertical"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch1",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outer": false,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": "height",
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Block"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "solved triangle (0,0), (50,0), P = t(cos60, sin60) with t = 25 + sqrt(150) from |P-(50,0)| = 45 (the root nearest the drawn apex); V = (1/2 * 50 * Py) * 5, A = 2 A2 + perimeter*5, centroid = vertex mean",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 4032.1546015511467,
|
||||
"area_mm2": 2274.0990841900384,
|
||||
"center_of_mass": [
|
||||
22.87457478565265,
|
||||
10.752412270803058,
|
||||
2.5
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
50.0,
|
||||
32.257236812409175,
|
||||
5.0
|
||||
]
|
||||
},
|
||||
"faces": 5
|
||||
}
|
||||
@@ -0,0 +1,184 @@
|
||||
{
|
||||
"id": "25-sketch-triangle",
|
||||
"name": "Sketch solver: triangle with lengths and angle",
|
||||
"description": "Triangle drawn roughly; l1 horizontal from origin, |l1|=50, |l2|=45, angle(l1,l3)=60 deg. Solved sketch extruded 5 mm.",
|
||||
"exercises": [
|
||||
"sketch_only",
|
||||
"sketch_constraints",
|
||||
"angle_dim"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Triangle",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
2,
|
||||
1
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
45,
|
||||
3
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
45.0,
|
||||
3.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
22,
|
||||
35
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
22.0,
|
||||
35.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
2.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
}
|
||||
],
|
||||
"constraints": [
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"origin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l1"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"l1.end"
|
||||
],
|
||||
"value": 50,
|
||||
"unit": "mm",
|
||||
"orientation": "aligned"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l2.start",
|
||||
"l2.end"
|
||||
],
|
||||
"value": 45,
|
||||
"unit": "mm",
|
||||
"orientation": "aligned"
|
||||
},
|
||||
{
|
||||
"type": "angle",
|
||||
"refs": [
|
||||
"l1",
|
||||
"l3"
|
||||
],
|
||||
"value": 60,
|
||||
"unit": "deg",
|
||||
"text_at": [
|
||||
8,
|
||||
4
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Triangle",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 5,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Triangle"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "belt: convex hull of circles R20 @ (0,0) and r10 @ (50,0) (external tangents, sin a = (R-r)/d) minus the d8 hole at (50,0), extruded 5 as one body; hull area = (R+r) sqrt(d^2-(R-r)^2) + R^2(pi+2a)/2 + r^2(pi-2a)/2; moments by Green's theorem over the line/arc boundary",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 11326.169514235082,
|
||||
"area_mm2": 5637.404150511762,
|
||||
"center_of_mass": [
|
||||
15.321857347287981,
|
||||
0.0,
|
||||
2.5
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-20.0,
|
||||
-20.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
60.0,
|
||||
20.0,
|
||||
5.0
|
||||
]
|
||||
},
|
||||
"faces": 7
|
||||
}
|
||||
@@ -0,0 +1,296 @@
|
||||
{
|
||||
"id": "26-sketch-belt",
|
||||
"name": "Sketch solver: tangent lines between circles",
|
||||
"description": "Circles d40 at origin and d20 at 50 mm (horizontal), concentric d8 hole, two lines tangent to both. All profiles extruded 5 mm.",
|
||||
"exercises": [
|
||||
"sketch_only",
|
||||
"sketch_constraints",
|
||||
"tangent",
|
||||
"concentric",
|
||||
"point_on_curve"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Belt",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
2,
|
||||
-1
|
||||
]
|
||||
},
|
||||
"radius": 18,
|
||||
"id": "c1"
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
45,
|
||||
3
|
||||
]
|
||||
},
|
||||
"radius": 12,
|
||||
"id": "c2"
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
46,
|
||||
2
|
||||
]
|
||||
},
|
||||
"radius": 5,
|
||||
"id": "c3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
3,
|
||||
19
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
47,
|
||||
13
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
1,
|
||||
-22
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
44,
|
||||
-8
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
}
|
||||
],
|
||||
"constraints": [
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"c1.center",
|
||||
"origin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "horizontal_points",
|
||||
"refs": [
|
||||
"c1.center",
|
||||
"c2.center"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "concentric",
|
||||
"refs": [
|
||||
"c3",
|
||||
"c2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"c1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l1.end",
|
||||
"c2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"l1",
|
||||
"c1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"l1",
|
||||
"c2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l2.start",
|
||||
"c1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l2.end",
|
||||
"c2"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"l2",
|
||||
"c1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "tangent",
|
||||
"refs": [
|
||||
"l2",
|
||||
"c2"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
{
|
||||
"type": "diameter",
|
||||
"refs": [
|
||||
"c1"
|
||||
],
|
||||
"value": 40,
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"type": "diameter",
|
||||
"refs": [
|
||||
"c2"
|
||||
],
|
||||
"value": 20,
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"type": "diameter",
|
||||
"refs": [
|
||||
"c3"
|
||||
],
|
||||
"value": 8,
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"c1.center",
|
||||
"c2.center"
|
||||
],
|
||||
"value": 50,
|
||||
"unit": "mm",
|
||||
"orientation": "horizontal"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Belt",
|
||||
"loops": [
|
||||
{
|
||||
"outer": false,
|
||||
"curves": [
|
||||
"c3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c2",
|
||||
"c2"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sketch": "Belt",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1",
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"sketch": "Belt",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1",
|
||||
"c2",
|
||||
"l1",
|
||||
"l2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 5,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Belt"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "solved trapezoid bottom (-30,0)-(30,0), top (-15,25)-(15,25) minus two d6 holes at (+-18, 8) (symmetric about the axis, 36 apart, 8 above l1); trapezoid centroid y = H(a+2b)/(3(a+b)); V = A2 t, A = 2 A2 + perimeter t",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 5342.256661176918,
|
||||
"area_mm2": 3066.94581842842,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
11.275769231975591,
|
||||
2.5
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-30.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
30.0,
|
||||
25.0,
|
||||
5.0
|
||||
]
|
||||
},
|
||||
"faces": 8
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
{
|
||||
"id": "27-sketch-trapezoid",
|
||||
"name": "Sketch solver: parallel/perpendicular/midpoint/symmetry",
|
||||
"description": "Trapezoid: bottom horizontal 60 centred on origin (midpoint), construction axis perpendicular, top parallel 30 with its midpoint on the axis end (height 25); two d6 holes symmetric about the axis.",
|
||||
"exercises": [
|
||||
"sketch_only",
|
||||
"sketch_constraints",
|
||||
"parallel",
|
||||
"perpendicular",
|
||||
"midpoint",
|
||||
"symmetric",
|
||||
"offset_dim",
|
||||
"construction_geometry"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Trapezoid",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-27,
|
||||
2
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
31,
|
||||
-1
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
31.0,
|
||||
-1.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
18,
|
||||
23
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
18.0,
|
||||
23.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-13,
|
||||
27
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-13.0,
|
||||
27.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-27.0,
|
||||
2.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
1,
|
||||
1
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
2,
|
||||
22
|
||||
]
|
||||
},
|
||||
"id": "l0",
|
||||
"construction": true
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
-14,
|
||||
9
|
||||
]
|
||||
},
|
||||
"radius": 2.5,
|
||||
"id": "h1"
|
||||
},
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
16,
|
||||
7
|
||||
]
|
||||
},
|
||||
"radius": 3.5,
|
||||
"id": "h2"
|
||||
}
|
||||
],
|
||||
"constraints": [
|
||||
{
|
||||
"type": "horizontal",
|
||||
"refs": [
|
||||
"l1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "midpoint",
|
||||
"refs": [
|
||||
"origin",
|
||||
"l1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "perpendicular",
|
||||
"refs": [
|
||||
"l0",
|
||||
"l1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "coincident",
|
||||
"refs": [
|
||||
"l0.start",
|
||||
"origin"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "parallel",
|
||||
"refs": [
|
||||
"l3",
|
||||
"l1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "midpoint",
|
||||
"refs": [
|
||||
"l0.end",
|
||||
"l3"
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "symmetric",
|
||||
"refs": [
|
||||
"h1",
|
||||
"h2",
|
||||
"l0"
|
||||
]
|
||||
}
|
||||
],
|
||||
"dimensions": [
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l1.start",
|
||||
"l1.end"
|
||||
],
|
||||
"value": 60,
|
||||
"unit": "mm",
|
||||
"orientation": "aligned"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l0.start",
|
||||
"l0.end"
|
||||
],
|
||||
"value": 25,
|
||||
"unit": "mm",
|
||||
"orientation": "aligned"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"l3.start",
|
||||
"l3.end"
|
||||
],
|
||||
"value": 30,
|
||||
"unit": "mm",
|
||||
"orientation": "aligned"
|
||||
},
|
||||
{
|
||||
"type": "diameter",
|
||||
"refs": [
|
||||
"h1"
|
||||
],
|
||||
"value": 6,
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"type": "offset",
|
||||
"refs": [
|
||||
"l1",
|
||||
"h1.center"
|
||||
],
|
||||
"value": 8,
|
||||
"unit": "mm"
|
||||
},
|
||||
{
|
||||
"type": "distance",
|
||||
"refs": [
|
||||
"h1.center",
|
||||
"h2.center"
|
||||
],
|
||||
"value": 36,
|
||||
"unit": "mm",
|
||||
"orientation": "horizontal"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Trapezoid",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outer": false,
|
||||
"curves": [
|
||||
"h1"
|
||||
]
|
||||
},
|
||||
{
|
||||
"outer": false,
|
||||
"curves": [
|
||||
"h2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 5,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Trapezoid"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude cut, Part makeFillet",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 84299.24852854047,
|
||||
"area_mm2": 16279.571562823978,
|
||||
"center_of_mass": [
|
||||
39.99999999999999,
|
||||
29.999999999999996,
|
||||
9.161827142086619
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
80.0,
|
||||
60.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,577 @@
|
||||
{
|
||||
"id": "28-blind-pocket-fillets",
|
||||
"name": "Blind pocket with filleted corners",
|
||||
"description": "80x60x20 block; 50x30 rectangle on top face cut 8 mm deep (blind); 4 vertical pocket corner edges filleted R5, then pocket floor edges R2.",
|
||||
"exercises": [
|
||||
"extrude_cut",
|
||||
"blind",
|
||||
"fillet",
|
||||
"concave_fillet"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Block_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
80,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
80.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
80,
|
||||
60
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
80.0,
|
||||
60.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
60
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
60.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Block",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Block_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Block"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "PocketSketch",
|
||||
"plane": {
|
||||
"ref": "face: top of Block (z=20)",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
15.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
65.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
65.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
65.0,
|
||||
45.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
65.0,
|
||||
45.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
15.0,
|
||||
45.0
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
15.0,
|
||||
45.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
15.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Pocket",
|
||||
"operation": "cut",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "PocketSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 8,
|
||||
"direction": "negative"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Block"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "CornerFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 5,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the 4 vertical pocket corner edges (z 12..20)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
65.0,
|
||||
15.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
65.0,
|
||||
15.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
65.0,
|
||||
15.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
15.0,
|
||||
15.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
15.0,
|
||||
15.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
15.0,
|
||||
15.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
65.0,
|
||||
45.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
65.0,
|
||||
45.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
65.0,
|
||||
45.0,
|
||||
16.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
15.0,
|
||||
45.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
15.0,
|
||||
45.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
15.0,
|
||||
45.0,
|
||||
16.0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "FloorFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 2,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "all edges of the pocket floor (z=12) - 4 lines + 4 arcs",
|
||||
"edges": [
|
||||
{
|
||||
"type": "arc",
|
||||
"start": [
|
||||
20.0,
|
||||
45.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
15.0,
|
||||
40.0,
|
||||
12.0
|
||||
],
|
||||
"center": [
|
||||
20.0,
|
||||
40.0,
|
||||
12.0
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
-0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
16.464466094,
|
||||
43.535533906,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
15.0,
|
||||
20.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
15.0,
|
||||
40.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
15.0,
|
||||
30.0,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc",
|
||||
"start": [
|
||||
15.0,
|
||||
20.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
20.0,
|
||||
15.0,
|
||||
12.0
|
||||
],
|
||||
"center": [
|
||||
20.0,
|
||||
20.0,
|
||||
12.0
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
-0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
16.464466094,
|
||||
16.464466094,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
15.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
20.0,
|
||||
15.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
15.0,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc",
|
||||
"start": [
|
||||
60.0,
|
||||
15.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
65.0,
|
||||
20.0,
|
||||
12.0
|
||||
],
|
||||
"center": [
|
||||
60.0,
|
||||
20.0,
|
||||
12.0
|
||||
],
|
||||
"axis": [
|
||||
-0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
63.535533906,
|
||||
16.464466094,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
65.0,
|
||||
40.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
65.0,
|
||||
20.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
65.0,
|
||||
30.0,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "arc",
|
||||
"start": [
|
||||
65.0,
|
||||
40.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
45.0,
|
||||
12.0
|
||||
],
|
||||
"center": [
|
||||
60.0,
|
||||
40.0,
|
||||
12.0
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
-0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
63.535533906,
|
||||
43.535533906,
|
||||
12.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
20.0,
|
||||
45.0,
|
||||
12.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
45.0,
|
||||
12.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
45.0,
|
||||
12.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "block [-20,20]x[-25,10]x[0,20] minus the 270 deg revolve (from +X towards +Y) of the 8x8 square r 4..12, z 12..20; the removed section is the annular sector minus the circular segment y > 10 (R^2 acos(d/R) - d sqrt(R^2-d^2), y-moment 2/3 (R^2-d^2)^1.5) that lies outside the block; sector moments (R^3-r^3)/3 (sin, 1-cos); A = box - top opening + floor - front-face notch 2 sqrt(44) x 8 + inner wall r th h + outer wall (th - pi + 2 asin(5/6)) R h + 2 end caps",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 25731.30662164881,
|
||||
"area_mm2": 6312.602171854095,
|
||||
"center_of_mass": [
|
||||
0.17244881492337513,
|
||||
-8.273218841749548,
|
||||
9.4709883773001
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-20.0,
|
||||
-25.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
20.0,
|
||||
10.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
"faces": 12
|
||||
}
|
||||
@@ -0,0 +1,289 @@
|
||||
{
|
||||
"id": "29-two-sided-and-partial-revolve",
|
||||
"name": "Two-sided extrude + partial revolve cut",
|
||||
"description": "Rectangle 40x20 on XZ extruded two sides (+10 along normal, 25 opposite); then a 270 deg revolve cut of an 8x8 square about the world Z axis.",
|
||||
"exercises": [
|
||||
"extrude_two_sides",
|
||||
"revolve_partial",
|
||||
"revolve_cut"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Sketch1",
|
||||
"plane": {
|
||||
"ref": "XZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-20.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
20.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-20.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-20.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Sketch1",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "two_sides",
|
||||
"side1": 10,
|
||||
"side2_opposite": 25
|
||||
},
|
||||
"result_bodies": [
|
||||
"Block"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "RevolveSketch",
|
||||
"plane": {
|
||||
"ref": "XZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
4.0,
|
||||
-12.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
12.0,
|
||||
-12.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
12.0,
|
||||
-12.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
12.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
12.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
4.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
4.0,
|
||||
-20.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
4.0,
|
||||
-12.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "revolve",
|
||||
"name": "RevolveCut",
|
||||
"operation": "cut",
|
||||
"profile": {
|
||||
"sketch": "RevolveSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"axis": {
|
||||
"ref": "world Z axis",
|
||||
"origin": [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
],
|
||||
"dir": [
|
||||
0,
|
||||
0,
|
||||
1
|
||||
]
|
||||
},
|
||||
"angle": 270,
|
||||
"result_bodies": [
|
||||
"Block"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, makeThickness shell, Part makeFillet",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 21745.87649398271,
|
||||
"area_mm2": 14968.331952825032,
|
||||
"center_of_mass": [
|
||||
30.000000000000004,
|
||||
20.000000000000007,
|
||||
11.575289961678543
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-8.881784197001252e-16,
|
||||
-8.881784197001252e-16,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
60.0,
|
||||
40.0,
|
||||
30.0
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,385 @@
|
||||
{
|
||||
"id": "30-fillet-after-shell",
|
||||
"name": "Fillets after shell",
|
||||
"description": "60x40x30 box shelled 3 mm (top open); outer vertical edges R6, inner vertical edges R3, then the inner floor loop R1 (tangent chain).",
|
||||
"exercises": [
|
||||
"shell",
|
||||
"fillet",
|
||||
"fillet_after_shell",
|
||||
"tangent_chain"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
60.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
60.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 30,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "shell",
|
||||
"name": "Shell1",
|
||||
"faces_removed": [
|
||||
{
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
30.0,
|
||||
20.0,
|
||||
30.0
|
||||
]
|
||||
}
|
||||
],
|
||||
"face_selection": "top face (z=30)",
|
||||
"inside_thickness": 3,
|
||||
"outside_thickness": 0,
|
||||
"tangent_chain": false
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "OuterFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 6,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the 4 outer vertical edges (x in {0,60}, y in {0,40})",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
40.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
40.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
40.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
60.0,
|
||||
40.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
0.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
60.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "InnerFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 3,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the 4 inner vertical edges (x in {3,57}, y in {3,37}, z 3..30)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
57.0,
|
||||
37.0,
|
||||
3.0
|
||||
],
|
||||
"end": [
|
||||
57.0,
|
||||
37.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
57.0,
|
||||
37.0,
|
||||
16.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
3.0,
|
||||
37.0,
|
||||
3.0
|
||||
],
|
||||
"end": [
|
||||
3.0,
|
||||
37.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
3.0,
|
||||
37.0,
|
||||
16.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
57.0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"end": [
|
||||
57.0,
|
||||
3.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
57.0,
|
||||
3.0,
|
||||
16.5
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
3.0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"end": [
|
||||
3.0,
|
||||
3.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
3.0,
|
||||
3.0,
|
||||
16.5
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "FloorFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 1,
|
||||
"tangent_chain": true,
|
||||
"edge_selection": "one edge of the inner floor (z=3) + tangent chain = whole floor boundary loop",
|
||||
"edges": [
|
||||
{
|
||||
"type": "arc",
|
||||
"start": [
|
||||
3.0,
|
||||
6.0,
|
||||
3.0
|
||||
],
|
||||
"end": [
|
||||
6.0,
|
||||
3.0,
|
||||
3.0
|
||||
],
|
||||
"center": [
|
||||
6.0,
|
||||
6.0,
|
||||
3.0
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
3.878679656,
|
||||
3.878679656,
|
||||
3.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 14321.625800678561,
|
||||
"area_mm2": 5024.044667641301,
|
||||
"center_of_mass": [
|
||||
20.90086014062925,
|
||||
10.902918183990028,
|
||||
10.000000000000005
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-4.440892098500626e-16,
|
||||
-4.440892098500626e-16,
|
||||
-4.440892098500626e-16
|
||||
],
|
||||
"max": [
|
||||
60.0,
|
||||
40.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,524 @@
|
||||
{
|
||||
"id": "31-fillet-l-all-edges",
|
||||
"name": "All-edge fillet on an L block",
|
||||
"description": "L profile (60x40, 8 thick legs) extruded 20; every edge filleted R2 - mixed convex/concave corners.",
|
||||
"exercises": [
|
||||
"fillet",
|
||||
"concave_edge",
|
||||
"mixed_corner"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "LSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
60.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
60,
|
||||
8
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
60.0,
|
||||
8.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
8,
|
||||
8
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
8.0,
|
||||
8.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
8,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l4.end",
|
||||
"at": [
|
||||
8.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l5"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l5.end",
|
||||
"at": [
|
||||
0.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l6"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "LSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4",
|
||||
"l5",
|
||||
"l6"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"L"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "Fillet1",
|
||||
"type": "constant_radius",
|
||||
"radius": 2,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "all 18 edges of L",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
8.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
4.0,
|
||||
40.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
40.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
40.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
8.0,
|
||||
40.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
40.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
4.0,
|
||||
40.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
8.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
8.0,
|
||||
40.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
8.0,
|
||||
40.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
8.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
8.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
8.0,
|
||||
24.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
8.0,
|
||||
8.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
8.0,
|
||||
40.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
8.0,
|
||||
24.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
8.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
8.0,
|
||||
8.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
8.0,
|
||||
8.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
8.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
34.0,
|
||||
8.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
8.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
8.0,
|
||||
8.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
34.0,
|
||||
8.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
8.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
60.0,
|
||||
8.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
8.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
60.0,
|
||||
4.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
8.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
60.0,
|
||||
4.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
60.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
60.0,
|
||||
0.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
30.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
60.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
30.0,
|
||||
0.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
20.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
40.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
20.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "plate 60x60x8 + boss (r10, z 8..23) with R3 concave base and R2 convex top fillets taken as one solid of revolution: V, z-moment and lateral area from Pappus/Green on its (r,z) profile (quarter-circle fillet arcs); plate top loses the r13 disc",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 33590.34963015678,
|
||||
"area_mm2": 9929.863617615047,
|
||||
"center_of_mass": [
|
||||
0.0,
|
||||
0.0,
|
||||
5.602823546473236
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-30.0,
|
||||
-30.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
30.0,
|
||||
30.0,
|
||||
23.0
|
||||
]
|
||||
},
|
||||
"faces": 10
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
{
|
||||
"id": "32-boss-plate-fillets",
|
||||
"name": "Boss on plate: base and top fillets",
|
||||
"description": "60x60x8 plate centred on origin; d20 boss 15 tall joined on top; base circle edge R3 (concave), top circle edge R2 (convex).",
|
||||
"exercises": [
|
||||
"extrude_join",
|
||||
"fillet",
|
||||
"cylinder_plane_edge"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Plate_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
-30,
|
||||
-30
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
30,
|
||||
-30
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
30.0,
|
||||
-30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
30,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
30.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
-30,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
-30.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
-30.0,
|
||||
-30.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Plate",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Plate_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 8,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Part"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "BossSketch",
|
||||
"plane": {
|
||||
"ref": "face: top of plate (z=8)",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
8.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"face": {
|
||||
"type": "plane",
|
||||
"normal_at_point_on_face": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"plane_normal_outward": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
8.0
|
||||
]
|
||||
}
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"radius": 10,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Boss",
|
||||
"operation": "join",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "BossSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 15,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Part"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "BaseFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 3,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "circular edge r=10 at z=8 (boss meets plate)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "circle",
|
||||
"start": [
|
||||
-10.0,
|
||||
-0.0,
|
||||
8.0
|
||||
],
|
||||
"end": [
|
||||
-10.0,
|
||||
-0.0,
|
||||
8.0
|
||||
],
|
||||
"center": [
|
||||
0.0,
|
||||
0.0,
|
||||
8.0
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"point": [
|
||||
10.0,
|
||||
0.0,
|
||||
8.0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "TopFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 2,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "circular edge r=10 at z=23 (boss top)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "circle",
|
||||
"start": [
|
||||
-10.0,
|
||||
-0.0,
|
||||
23.0
|
||||
],
|
||||
"end": [
|
||||
-10.0,
|
||||
-0.0,
|
||||
23.0
|
||||
],
|
||||
"center": [
|
||||
0.0,
|
||||
0.0,
|
||||
23.0
|
||||
],
|
||||
"axis": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
],
|
||||
"point": [
|
||||
10.0,
|
||||
0.0,
|
||||
23.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"source": "analytic",
|
||||
"method": "obround (R8, centres 40 apart) x 10 minus R2 fillet spandrel R^2(1-pi/4) and 1 mm chamfer triangle swept round the whole loop (Pappus: section x path of its centroid, inset R(10-3pi)/(3(4-pi)) resp. c/3); surfaces likewise (fillet arc centroid inset R-2R/pi)",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 8237.12239193832,
|
||||
"area_mm2": 2793.569488148386,
|
||||
"center_of_mass": [
|
||||
20.0,
|
||||
0.0,
|
||||
4.975827363940953
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-8.0,
|
||||
-8.0,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
48.0,
|
||||
8.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
"faces": 14
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
{
|
||||
"id": "33-fillet-tangent-chain",
|
||||
"name": "Fillet chain across tangent faces",
|
||||
"description": "Obround (centres 0 and 40, R8) extruded 10; one top edge filleted R2 with tangent chain (whole top loop), then one bottom edge chamfered 1 with tangent chain.",
|
||||
"exercises": [
|
||||
"fillet",
|
||||
"tangent_chain",
|
||||
"chamfer"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "SlotSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "arc_center_start_sweep",
|
||||
"center": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
8
|
||||
]
|
||||
},
|
||||
"sweep_deg": 180,
|
||||
"id": "a1"
|
||||
},
|
||||
{
|
||||
"type": "arc_center_start_sweep",
|
||||
"center": {
|
||||
"at": [
|
||||
40,
|
||||
0
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"at": [
|
||||
40,
|
||||
-8
|
||||
]
|
||||
},
|
||||
"sweep_deg": 180,
|
||||
"id": "a2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a1.end",
|
||||
"at": [
|
||||
-0.0,
|
||||
-8.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "a2.start",
|
||||
"at": [
|
||||
40.0,
|
||||
-8.0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "a2.end",
|
||||
"at": [
|
||||
40.0,
|
||||
8.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "a1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
8.0
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "SlotSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"a1",
|
||||
"a2",
|
||||
"l1",
|
||||
"l2"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 10,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Slot"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "TopFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 2,
|
||||
"tangent_chain": true,
|
||||
"edge_selection": "top straight edge at y=-8, z=10, plus tangent chain",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
-8.0,
|
||||
10.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
-8.0,
|
||||
10.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
-8.0,
|
||||
10.0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "chamfer",
|
||||
"name": "BottomChamfer",
|
||||
"type": "equal_distance",
|
||||
"distance": 1,
|
||||
"tangent_chain": true,
|
||||
"edge_selection": "bottom straight edge at y=-8, z=0, plus tangent chain",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
-0.0,
|
||||
-8.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
-8.0,
|
||||
0.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
-8.0,
|
||||
0.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeChamfer, Part makeFillet",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 22892.42132988775,
|
||||
"area_mm2": 4753.688476928938,
|
||||
"center_of_mass": [
|
||||
20.000000000000004,
|
||||
15.000000000000012,
|
||||
9.95512677479753
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-1.3322676295501878e-15,
|
||||
-8.881784197001252e-16,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
{
|
||||
"id": "34-chamfer-then-fillet",
|
||||
"name": "Chamfer then fillet",
|
||||
"description": "40x30x20 box; 4 vertical edges chamfered 5; then all 8 edges of the (now octagonal) top face filleted R2.",
|
||||
"exercises": [
|
||||
"chamfer",
|
||||
"fillet",
|
||||
"chamfer_fillet_interaction"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
40.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
40,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
40.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
30
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
30.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 20,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "chamfer",
|
||||
"name": "Chamfer1",
|
||||
"type": "equal_distance",
|
||||
"distance": 5,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the 4 vertical edges",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
30.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
30.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
30.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
0.0,
|
||||
10.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
10.0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "Fillet1",
|
||||
"type": "constant_radius",
|
||||
"radius": 2,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "all 8 edges of the top face (z=20)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
5.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
5.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
2.5,
|
||||
2.5,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
5.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
35.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
0.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
5.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
35.0,
|
||||
0.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
37.5,
|
||||
2.5,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
40.0,
|
||||
5.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
25.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
40.0,
|
||||
15.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
35.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
40.0,
|
||||
25.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
37.5,
|
||||
27.5,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
35.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
5.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
20.0,
|
||||
30.0,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
25.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
5.0,
|
||||
30.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
2.5,
|
||||
27.5,
|
||||
20.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
25.0,
|
||||
20.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
5.0,
|
||||
20.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
15.0,
|
||||
20.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part extrude join, Part makeFillet",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 63171.61517720092,
|
||||
"area_mm2": 10269.29821399885,
|
||||
"center_of_mass": [
|
||||
1.2666242694772013e-06,
|
||||
-8.296669862737878e-08,
|
||||
2.5627304852052224
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-40.0000001,
|
||||
-15.0000001,
|
||||
-15.0000001
|
||||
],
|
||||
"max": [
|
||||
40.0000001,
|
||||
15.0000001,
|
||||
35.0000001
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,193 @@
|
||||
{
|
||||
"id": "35-fillet-curved-curved",
|
||||
"name": "Fillet between two cylinders",
|
||||
"description": "Cylinder r15 along X (x -40..40) joined with a cylinder r10 along +Z (z 0..35); the saddle intersection edge filleted R3.",
|
||||
"exercises": [
|
||||
"sketch_yz",
|
||||
"symmetric",
|
||||
"extrude_join",
|
||||
"fillet",
|
||||
"curved_curved_edge"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "MainSketch",
|
||||
"plane": {
|
||||
"ref": "YZ",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
0.0,
|
||||
0.0,
|
||||
-1.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"radius": 15,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Main",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "MainSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "symmetric",
|
||||
"distance": 40,
|
||||
"is_full_length": false,
|
||||
"meaning": "distance on each side"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Tee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "BranchSketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "circle",
|
||||
"center": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"radius": 10,
|
||||
"id": "c1"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Branch",
|
||||
"operation": "join",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "BranchSketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"c1"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 35,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Tee"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "SaddleFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 3,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "edge(s) whose both adjacent faces are cylinders (the branch/main intersection curve)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "nurbs",
|
||||
"start": [
|
||||
0.0,
|
||||
-10.0,
|
||||
11.180339887
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
-10.0,
|
||||
11.180339887
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
10.0,
|
||||
11.180339887
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Binary file not shown.
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"source": "freecad 1.1.4",
|
||||
"method": "FreeCAD (OpenCASCADE) build of recipe.json by tools/freecad-oracle/build.py: Part extrude, Part makeFillet",
|
||||
"body_count": 1,
|
||||
"volume_mm3": 57800.23972405685,
|
||||
"area_mm2": 8585.524087145672,
|
||||
"center_of_mass": [
|
||||
24.999999999999996,
|
||||
19.999999999999996,
|
||||
14.865497483183885
|
||||
],
|
||||
"bbox": {
|
||||
"min": [
|
||||
-1.7763568394002505e-15,
|
||||
-1.0000000177635683e-07,
|
||||
0.0
|
||||
],
|
||||
"max": [
|
||||
50.0,
|
||||
40.0,
|
||||
30.0
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,274 @@
|
||||
{
|
||||
"id": "36-corner-blend-two-radii",
|
||||
"name": "Corner blends with two radii",
|
||||
"description": "50x40x30 box; 4 vertical edges R8, then the top-face loop R4 (tangent chain) - 3-edge corner blends with unequal radii.",
|
||||
"exercises": [
|
||||
"fillet",
|
||||
"corner_blend",
|
||||
"tangent_chain"
|
||||
],
|
||||
"units": {
|
||||
"length": "mm",
|
||||
"angle": "deg",
|
||||
"area": "mm2",
|
||||
"volume": "mm3"
|
||||
},
|
||||
"conventions": {
|
||||
"world": "right-handed; Z up. XY/XZ/YZ are the origin planes.",
|
||||
"sketch_frame": "sketch-local 2D coords; world = plane.origin + x*plane.x_dir + y*plane.y_dir (origin in mm). Profile 'loops' list sketch entity ids.",
|
||||
"point_refs": "'<id>.start' / '<id>.end' / '<id>.center' reuse (share) that existing sketch point; 'at' is its position when referenced.",
|
||||
"extrude_direction": "positive = along sketch plane normal unless stated",
|
||||
"selections": "selected faces/edges carry 'point': a world-space point on the face / the edge's parametric midpoint; pick the nearest face/edge to it."
|
||||
},
|
||||
"parameters": [],
|
||||
"features": [
|
||||
{
|
||||
"op": "sketch",
|
||||
"name": "Extrude1_sketch",
|
||||
"plane": {
|
||||
"ref": "XY",
|
||||
"origin": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"x_dir": [
|
||||
1.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"y_dir": [
|
||||
0.0,
|
||||
1.0,
|
||||
0.0
|
||||
],
|
||||
"normal": [
|
||||
0.0,
|
||||
0.0,
|
||||
1.0
|
||||
]
|
||||
},
|
||||
"entities": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"at": [
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
50,
|
||||
0
|
||||
]
|
||||
},
|
||||
"id": "l1"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l1.end",
|
||||
"at": [
|
||||
50.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
50,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l2"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l2.end",
|
||||
"at": [
|
||||
50.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"at": [
|
||||
0,
|
||||
40
|
||||
]
|
||||
},
|
||||
"id": "l3"
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": {
|
||||
"ref": "l3.end",
|
||||
"at": [
|
||||
0.0,
|
||||
40.0
|
||||
]
|
||||
},
|
||||
"end": {
|
||||
"ref": "l1.start",
|
||||
"at": [
|
||||
0.0,
|
||||
0.0
|
||||
]
|
||||
},
|
||||
"id": "l4"
|
||||
}
|
||||
],
|
||||
"constraints": [],
|
||||
"dimensions": []
|
||||
},
|
||||
{
|
||||
"op": "extrude",
|
||||
"name": "Extrude1",
|
||||
"operation": "new",
|
||||
"profiles": [
|
||||
{
|
||||
"sketch": "Extrude1_sketch",
|
||||
"loops": [
|
||||
{
|
||||
"outer": true,
|
||||
"curves": [
|
||||
"l1",
|
||||
"l2",
|
||||
"l3",
|
||||
"l4"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"extent": {
|
||||
"type": "distance",
|
||||
"distance": 30,
|
||||
"direction": "positive"
|
||||
},
|
||||
"result_bodies": [
|
||||
"Box"
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "VerticalFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 8,
|
||||
"tangent_chain": false,
|
||||
"edge_selection": "the 4 vertical edges",
|
||||
"edges": [
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
40.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
40.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
50.0,
|
||||
40.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
50.0,
|
||||
40.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
50.0,
|
||||
40.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
50.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
50.0,
|
||||
0.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
50.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "line",
|
||||
"start": [
|
||||
0.0,
|
||||
0.0,
|
||||
0.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
0.0,
|
||||
30.0
|
||||
],
|
||||
"point": [
|
||||
0.0,
|
||||
0.0,
|
||||
15.0
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"op": "fillet",
|
||||
"name": "TopFillet",
|
||||
"type": "constant_radius",
|
||||
"radius": 4,
|
||||
"tangent_chain": true,
|
||||
"edge_selection": "one edge of the top face + tangent chain (whole top loop)",
|
||||
"edges": [
|
||||
{
|
||||
"type": "arc",
|
||||
"start": [
|
||||
8.0,
|
||||
0.0,
|
||||
30.0
|
||||
],
|
||||
"end": [
|
||||
0.0,
|
||||
8.0,
|
||||
30.0
|
||||
],
|
||||
"center": [
|
||||
8.0,
|
||||
8.0,
|
||||
30.0
|
||||
],
|
||||
"axis": [
|
||||
-0.0,
|
||||
-0.0,
|
||||
-1.0
|
||||
],
|
||||
"point": [
|
||||
2.343145751,
|
||||
2.343145751,
|
||||
30.0
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Loaded 100 of 204 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user