Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,27 @@ All notable changes to SimpleCADAPI are documented here. Per-version
user-facing notes live in [`docs/updates/`](docs/updates/) (English and
中文).

## 2.2.9 — 2026-11-10
## 2.3.0 — 2026-11-12

Replaces 2.2.8, which was withdrawn (yanked on PyPI). User-facing notes: [`docs/updates/2.2.9.md`](docs/updates/2.2.9.md).
Follows 2.2.7. 2.2.8 was withdrawn (yanked on PyPI) and the 2.2.9 prepared to replace it was never published: both are in
2.3.0. User-facing notes: [`docs/updates/2.3.0.md`](docs/updates/2.3.0.md).

### Changed
- `cut_rsolid`, `cut_rsolid_list` and `intersect_rsolid` unify the same-domain faces and edges of their result by default
(`clean=True`, as `union_rsolid` always did; history and tags follow; the raw result is kept when the unified one would be
invalid; `clean=False` returns the raw result). A cut through the side of a box with a cylinder left the cylinder wall split
along its seam, and OCCT fillets an edge along such a face wrongly (#88). `clean` is a recorded parameter of the three
operations: a graph recorded before 2.3.0 has none and is rejected on replay (record it again). Bodies built with such a cut
have fewer faces and edges, and different fingerprints.
- The exporters (`export_product_package_to_step / stl / obj / mjcf`, the FreeCAD translator) also take the result of a build or
its definition, packaged in memory without the scene projection (#89): 1.85 s -> 0.26 s for a 36-hole plate.
- `run_notebook(path, cache=False)` writes nothing beside the notebook: no `deps.json`, no state directory, so it runs where the
source directory is read-only (#89). The skill allows a plain script for a task that asks for a single script (R3 exception).
- Geometry fingerprints quantize through a noise-tolerant `_q` (#87); a fingerprint of a body with a value on a quantization
boundary changes once, and a topology snapshot recorded before does not restore.
- The build backend is `tools/build_backend/scad_build.py` (scikit-build-core plus toolchain and OpenCASCADE detection on
Windows): a plain `uv sync` builds without Visual Studio or hand-set `CMAKE_*` variables; `uv` rebuilds only when `src/cpp`
changes.
- A `Part` holds exactly one solid again. The multi-body Part of the withdrawn 2.2.8 release (`make_part_rpart(bodies=...)`,
`Part.multi_body`, `scad.part_shape`, `inspect_body_overlaps_rlist`, `BodyOverlapWarning`, part definition profile
`simplecad-part-definition-3` with `solid_cache.bodies`) is removed: a Part is the unit an assembly places, mates and gives
Expand All @@ -19,6 +35,8 @@ Replaces 2.2.8, which was withdrawn (yanked on PyPI). User-facing notes: [`docs/
makes the Part from the one you choose. Several solids as a deliverable are one Part each in an Assembly.

### Fixed
- `fillet_rsolid` / `chamfer_rsolid` raise when OCCT returns a solid it calls invalid while the input was valid (they used to
return it, #88); a fillet that fails on split faces says so instead of blaming the radius.
- On Windows `sca cell` no longer kills the editor it talks to: the editor pid is probed without signalling it
(`os.kill(pid, 0)` terminates the process on Windows).
- Building from source with GCC 14 (`operators_promote.cpp` includes `<algorithm>`) and MinGW (`_USE_MATH_DEFINES`; `build_occt.py`
Expand Down
4 changes: 2 additions & 2 deletions docs/skill/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ description: Build, assemble, inspect, reconstruct, and export parametric CAD mo
license: Apache-2.0
metadata:
project: simplecadapi
version: 2.2.9
version: 2.3.0
package-name: simplecadapi
package-version: 2.2.9
package-version: 2.3.0
---

# SimpleCAD SDK Skill
Expand Down
4 changes: 2 additions & 2 deletions docs/skill/references/SDK_OVERVIEW.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# SDK Overview

- Project: `simplecadapi`
- Version: `2.2.9`
- Package distribution: `simplecadapi==2.2.9`
- Version: `2.3.0`
- Package distribution: `simplecadapi==2.3.0`

## What this skill bundles

Expand Down
50 changes: 0 additions & 50 deletions docs/updates/2.2.9.md

This file was deleted.

44 changes: 0 additions & 44 deletions docs/updates/2.2.9.zh-CN.md

This file was deleted.

93 changes: 93 additions & 0 deletions docs/updates/2.3.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 2.3.0 Update Notes

2.3.0 follows 2.2.7. 2.2.8 was withdrawn (yanked on PyPI, its GitHub release deleted) and a 2.2.9 that replaced it was prepared
but never published, so everything of both is in 2.3.0. It is a minor version because it removes interface of the withdrawn
2.2.8, adds keywords, and changes what `cut_rsolid` and `intersect_rsolid` return by default.

## A Part holds exactly one solid

2.2.8 let a `Part` hold several bodies, and a Part like that could not take part in an assembly. In 2.3.0 a Part holds exactly
one solid again, and the editor keeps what it needed from the change: several solids can sit side by side in a notebook while a
model is built.

The editor has to let several solids exist in one notebook with no assembly constraint between them: to pattern a tool and then
cut, there is a moment where more than one tool and the body exist together. In 2.2.8 that was solved inside the Part. But a
Part is the unit an assembly places, mates and gives connectors to, and a Part of several bodies could do none of that:

* connectors on it were refused, so no mate or public connector could reach it;
* `explode_rassembly` raised `TypeError` on any assembly that held one;
* the older scene compiler read the single body of a Part in four places.

The solids side by side are intermediate values of a notebook, not product items. So they are notebook values now, and the
Part is one solid. The full account is in `design-docs/part-holds-one-solid.md`.

* **`make_part_rpart(part_id, body, name=None)`** takes one Solid, a `Body` (a solid with a display name, colour and opacity), or
a list of exactly one piece of a split cut. Several solids raise `ValueError`, which says what to do: one Part per solid in
an Assembly (identity placements when they only sit together), or union / cut them into one solid first.
* **Removed** (they only existed in the withdrawn 2.2.8): the `bodies=` argument, `Part.multi_body`, `scad.part_shape`,
`inspect_body_overlaps_rlist`, `BodyOverlap`, `BodyOverlapWarning`, and the part definition profile
`simplecad-part-definition-3` with `solid_cache.bodies`. A model JSON recorded with several solids in one Part is refused on
replay, with the same guidance.
* **A part notebook with one open solid** still gets the implicit Part. **With several open solids** there is no product:
running it raises `SeveralOpenBodiesError`, which names the solids; make the Part from the one that is the part
(`scad.make_part_rpart(part_id=scad.notebook_id(), body=...)`), or make a Part of each and an Assembly of them.
* **The editor** draws such open solids side by side and its header says "N open solids"; `use_as_product` makes the Part
from the solid you choose.

## Booleans: `cut_rsolid` and `intersect_rsolid` clean their result

`union_rsolid` always unified the faces and edges that lie on one surface; `cut_rsolid`, `cut_rsolid_list` and
`intersect_rsolid` did not. A cut through the side of a box with a cylinder leaves the cylinder wall split along its seam, and
OCCT fillets an edge along such a split face wrongly: it returned an invalid solid with a wrong volume and no error (issue #88),
or refused with a message that blamed the radius.

* **`cut_rsolid`, `cut_rsolid_list`, `intersect_rsolid` take `clean=True`** (the default) and unify what they leave split. Tags
and lineage follow the clean-up. If the unified result would be invalid, the raw result is kept. `clean=False` returns the raw
result, as before.
* **Fewer faces and edges** in the result of a cut that used to leave a split face: the geometry (volume, area, bounding box) is
the same, the topology and so the fingerprints of such a body change. Rebuild stored products that contain one.
* **`fillet_rsolid` and `chamfer_rsolid` check their result**: a solid OCCT calls invalid (while the input was valid) is an
error naming the operation, not a returned value.
* **A fillet that fails on split faces says so** (and that the radius is not the limit) instead of advising a smaller radius.
* **Recorded graphs**: `clean` is a recorded parameter of `make_cut_rsolid`, `make_cut_rsolid_list` and `make_intersect_rsolid`.
A graph recorded before 2.3.0 has none and is rejected on replay like any graph with a missing parameter: record it again.

## Exporting without a `.scadpkg` file, and running where the source is read-only

* **The exporters take the result of a build.** `export_product_package_to_step / stl / obj / mjcf` and the FreeCAD translator
accept the result of `@scad.part` / `@scad.assemble` (or its definition) as well as a package, bytes or a path, and package
it in memory without the scene (the tessellated projection no exporter reads). A STEP file from a freshly built part:
`scad.exporter.export_product_package_to_step(build_part(), "part.step")`; the 120 x 120 x 10 plate of issue #89 takes 0.26 s
where capture followed by export took 1.85 s.
* **`run_notebook(path, cache=False)` leaves the source directory alone.** It no longer writes `__marimo__/.../deps.json` or
creates the state directory beside the notebook, so it runs where the source is read-only (a container with the source
mounted `:ro`). A run with the cache still writes its state there, and fails if it cannot.
* **A single-script task is a supported entry.** The skill keeps "a model is a marimo notebook" (R3) for everything a person
opens or reuses, and now allows a plain script for a task that asks for exactly that, with a template in
`domains/export-and-translation.md`.

## Fingerprints do not split a value on a quantization boundary

A body's geometry fingerprint quantizes every measured value to 1e-7 mm. A design value such as -3.34690815 mm lies exactly on a
boundary of that rounding, and one unit in the last place of computation noise (a BREP round trip) sent it to the neighbouring
integer, so the fingerprint changed and restoring a topology snapshot raised `geometry_mismatch` (issue #87). The quantizer now
snaps the value to a grid far coarser than the noise and far finer than a quantum before rounding. Fingerprints change once for a
body with a value on a boundary; a snapshot recorded before that does not restore and has to be captured again.

## Fixed

* On Windows, `sca cell` no longer kills the editor it talks to: the editor's process id is probed without signalling it
(`os.kill(pid, 0)` on Windows terminates the process).
* Building from source: GCC 14 (`operators_promote.cpp` includes `<algorithm>`), MinGW (`_USE_MATH_DEFINES`, and
`build_occt.py` picks Ninja when a MinGW `g++` is the compiler), and the macOS wheels are built on `macos-15` (the wheels still
target macOS 14.0).

## For contributors

* **A plain `uv sync` builds the package on Windows.** Without Visual Studio the build used to fall to the NMake generator and
fail, and every contributor set `CMAKE_GENERATOR`, `CC`, `CXX` and `CMAKE_ARGS` by hand. The build backend
(`tools/build_backend/scad_build.py`) now picks Ninja and the gcc on the path when Visual Studio is absent, finds OpenCASCADE
(`SCAD_OCCT_PREFIX`, `C:/occt`, or a per-user cache it builds once) and limits the jobs by memory. Anything you set yourself
still wins. `uv` rebuilds only when `src/cpp` changes (`[tool.uv] cache-keys`), not on a version bump.
* `AGENT.md` records how to rebuild the C++ module on Windows and how commits are organised: one decision per commit, with the
reasons from the discussion that led to it.
Loading
Loading