From afb63fc3753b12941c294c80b4932967a37d6955 Mon Sep 17 00:00:00 2001 From: Ni Jingzhe <1448976235@qq.com> Date: Thu, 12 Nov 2026 14:47:57 +0800 Subject: [PATCH] release: 2.3.0 (includes the never-published 2.2.9) DECISION: release the one-solid Part, the Windows build fix and the three issue fixes (#87, #88, #89) as 2.3.0. Not 2.2.10: the owner chose 2.3.0. 2.2.9 is skipped; it was prepared on dev but never tagged or published (no v2.2.9 tag, nothing on PyPI, the dry run of its workflow published nothing), so there is nothing to withdraw. WHY A MINOR VERSION - It removes interface of the withdrawn 2.2.8 (`bodies=`, `Part.multi_body`, profile 3), adds keywords (`clean=` on three booleans), and widens the exporters' input. - It changes a default: `cut_rsolid`, `cut_rsolid_list` and `intersect_rsolid` unify their result, so the topology and the fingerprint of a body that used to keep a split face change. - It breaks a recorded format on purpose: `clean` is a required parameter of the three recorded operations, a graph recorded before 2.3.0 is rejected on replay and has to be recorded again (the owner's rule: no branches for old formats). WHAT CHANGES - pyproject.toml, uv.lock (the simplecadapi package entry), docs/skill/SKILL.md and docs/skill/references/SDK_OVERVIEW.md: 2.2.9 -> 2.3.0. - CHANGELOG.md: the 2.2.9 entry becomes 2.3.0 (2026-11-12) and gains the four changes; "Fixed" gains the blend validity check. - docs/updates/2.3.0.md and 2.3.0.zh-CN.md replace the 2.2.9 notes (deleted): everything the 2.2.9 notes said, then the booleans, the exporters and read-only runs, the fingerprints, and the Windows build. The release workflow turns them into the release page. Checked: test_skill_lint and test_auto_docs_gen pass; the release workflow checks that the tag equals the pyproject version (v2.3.0). test_skill_build::test_files_without_directives_copied_verbatim fails on the base commit as well (a Windows encoding problem of this machine). The code of dev was green in CI at 8c482366 before this commit; this commit changes no code. Co-Authored-By: Claude Sonnet 5.5 --- CHANGELOG.md | 22 ++++++- docs/skill/SKILL.md | 4 +- docs/skill/references/SDK_OVERVIEW.md | 4 +- docs/updates/2.2.9.md | 50 -------------- docs/updates/2.2.9.zh-CN.md | 44 ------------- docs/updates/2.3.0.md | 93 +++++++++++++++++++++++++++ docs/updates/2.3.0.zh-CN.md | 79 +++++++++++++++++++++++ pyproject.toml | 2 +- uv.lock | 2 +- 9 files changed, 198 insertions(+), 102 deletions(-) delete mode 100644 docs/updates/2.2.9.md delete mode 100644 docs/updates/2.2.9.zh-CN.md create mode 100644 docs/updates/2.3.0.md create mode 100644 docs/updates/2.3.0.zh-CN.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 99366250..c99aad74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 ``) and MinGW (`_USE_MATH_DEFINES`; `build_occt.py` diff --git a/docs/skill/SKILL.md b/docs/skill/SKILL.md index 2d26981c..08b8c925 100644 --- a/docs/skill/SKILL.md +++ b/docs/skill/SKILL.md @@ -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 diff --git a/docs/skill/references/SDK_OVERVIEW.md b/docs/skill/references/SDK_OVERVIEW.md index bc93c570..50d42e5c 100644 --- a/docs/skill/references/SDK_OVERVIEW.md +++ b/docs/skill/references/SDK_OVERVIEW.md @@ -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 diff --git a/docs/updates/2.2.9.md b/docs/updates/2.2.9.md deleted file mode 100644 index ba8cf453..00000000 --- a/docs/updates/2.2.9.md +++ /dev/null @@ -1,50 +0,0 @@ -# 2.2.9 Update Notes - -2.2.9 replaces 2.2.8, which has been withdrawn (yanked on PyPI, its GitHub release deleted). 2.2.8 let a `Part` hold several -bodies, and a Part like that could not take part in an assembly. In 2.2.9 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. -Everything else in 2.2.8 is in 2.2.9; the 2.2.8 notes describe it. - -## Why a Part holds one solid - -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`; every commit of the change states its own -reason. - -## What changes for you - -* **`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 part definition is profile 2 with one solid, as before 2.2.8. - 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** (`kind = "part"`, no cell making the Part) 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. One-body Parts, `Pieces` (the result of a splitting cut), connectors, mates and `explode_rassembly` - behave as they did. - -## 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 ``), 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` (GitHub no - longer serves `macos-14`; the wheels still target macOS 14.0). - -## For contributors - -* `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. diff --git a/docs/updates/2.2.9.zh-CN.md b/docs/updates/2.2.9.zh-CN.md deleted file mode 100644 index a0617d58..00000000 --- a/docs/updates/2.2.9.zh-CN.md +++ /dev/null @@ -1,44 +0,0 @@ -# 2.2.9 更新说明 - -2.2.9 取代 2.2.8。2.2.8 已被撤回(PyPI 上已 yank,GitHub 上的发布也已删除)。2.2.8 让一个 `Part` 可以持有多个 body, -而这样的 Part 没法参与装配。2.2.9 让 Part 重新只持有一个实体,同时保留编辑器需要的能力:建模过程中,笔记本里可以并排放着 -几个实体。2.2.8 的其余内容都在 2.2.9 里,请看 2.2.8 的更新说明。 - -## 为什么 Part 只持有一个实体 - -编辑器必须允许一个笔记本里同时存在几个实体,而且它们之间不需要装配约束:比如先阵列刀具再切割,总有一刻刀具有好几把, -本体也在。2.2.8 把这件事放进了 Part 里解决。但是 Part 是装配里被摆放、被配合、被挂接口的单位,而持有多个 body 的 Part 这些都做不到: - -* 它不能带连接器,所以任何配合或公开连接器都够不到它; -* 装配里只要有一个这样的 Part,`explode_rassembly` 就报 `TypeError`; -* 旧的场景编译器有四处直接读 Part 的唯一 body。 - -并排的几个实体只是笔记本里的中间值,不是产品里的零件。所以现在它们是笔记本里的普通值,Part 就是一个实体。完整的说明在 -`design-docs/part-holds-one-solid.md`,这次改动的每个提交都写明了各自的原因。 - -## 对你有什么影响 - -* **`make_part_rpart(part_id, body, name=None)`** 接受一个实体、一个 `Body`(带显示名、颜色和不透明度的实体),或者切割分裂后 - 恰好只有一块的列表。给多个实体会报 `ValueError`,并说明该怎么做:每个实体做成一个 Part 放进装配(只是放在一起时用单位位姿), - 或者先把它们合并、切割成一个实体。 -* **已移除**(它们只在被撤回的 2.2.8 里存在过):`bodies=` 参数、`Part.multi_body`、`scad.part_shape`、 - `inspect_body_overlaps_rlist`、`BodyOverlap`、`BodyOverlapWarning`,以及零件定义的 `simplecad-part-definition-3` 版式和 - `solid_cache.bodies`。零件定义回到 2.2.8 之前的第 2 版式,只含一个实体。记录了“一个 Part 里有多个实体”的模型 JSON, - 重放时会被拒绝,并给出同样的提示。 -* **零件笔记本里只有一个开放实体**(`kind = "part"`,没有任何单元格做出 Part)时,仍然自动得到隐式 Part。**有多个开放实体** - 时没有产品:运行会报 `SeveralOpenBodiesError`,并列出这些实体;请用真正作为零件的那个来做 Part - (`scad.make_part_rpart(part_id=scad.notebook_id(), body=...)`),或者给每个实体各做一个 Part,再放进装配。 -* **编辑器**会把这些开放实体并排画出来,标题栏显示“N 个开放实体”;`use_as_product` 会用你选的那个实体做出 Part。 - 单个实体的 Part、`Pieces`(切割分裂的结果)、连接器、配合和 `explode_rassembly` 的行为都和以前一样。 - -## 修复 - -* 在 Windows 上,`sca cell` 不会再杀掉它正在对话的编辑器:现在探测编辑器进程时不再发信号(Windows 上 - `os.kill(pid, 0)` 会直接终止进程)。 -* 从源码构建:GCC 14(`operators_promote.cpp` 补上 ``)、MinGW(定义 `_USE_MATH_DEFINES`,以及编译器是 MinGW 的 - `g++` 时 `build_occt.py` 选用 Ninja);macOS 的安装包改在 `macos-15` 上构建(GitHub 不再提供 `macos-14`,安装包仍然面向 - macOS 14.0)。 - -## 给贡献者 - -* `AGENT.md` 记录了在 Windows 上如何重新编译 C++ 模块,以及提交该怎么组织:一个决策一个提交,说明里写清楚促成这个决策的讨论和原因。 diff --git a/docs/updates/2.3.0.md b/docs/updates/2.3.0.md new file mode 100644 index 00000000..9cdbd2bb --- /dev/null +++ b/docs/updates/2.3.0.md @@ -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 ``), 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. diff --git a/docs/updates/2.3.0.zh-CN.md b/docs/updates/2.3.0.zh-CN.md new file mode 100644 index 00000000..dfb8a179 --- /dev/null +++ b/docs/updates/2.3.0.zh-CN.md @@ -0,0 +1,79 @@ +# 2.3.0 更新说明 + +2.3.0 接在 2.2.7 之后。2.2.8 已被撤回(PyPI 上已 yank,GitHub 上的发布也已删除),取代它的 2.2.9 已经准备好但从未发布, +所以两者的全部内容都在 2.3.0 里。这是一个次版本:它移除了被撤回的 2.2.8 里的接口,增加了关键字,并改变了 +`cut_rsolid` 和 `intersect_rsolid` 默认返回的结果。 + +## Part 只持有一个实体 + +2.2.8 让一个 `Part` 可以持有多个 body,而这样的 Part 没法参与装配。2.3.0 让 Part 重新只持有一个实体,同时保留编辑器需要的能力: +建模过程中,笔记本里可以并排放着几个实体。 + +编辑器必须允许一个笔记本里同时存在几个实体,而且它们之间不需要装配约束:比如先阵列刀具再切割,总有一刻刀具有好几把, +本体也在。2.2.8 把这件事放进了 Part 里解决。但是 Part 是装配里被摆放、被配合、被挂接口的单位,而持有多个 body 的 Part 这些都做不到: + +* 它不能带连接器,所以任何配合或公开连接器都够不到它; +* 装配里只要有一个这样的 Part,`explode_rassembly` 就报 `TypeError`; +* 旧的场景编译器有四处直接读 Part 的唯一 body。 + +并排的几个实体只是笔记本里的中间值,不是产品里的零件。所以现在它们是笔记本里的普通值,Part 就是一个实体。完整的说明在 +`design-docs/part-holds-one-solid.md`。 + +* **`make_part_rpart(part_id, body, name=None)`** 接受一个实体、一个 `Body`(带显示名、颜色和不透明度的实体),或者切割分裂后 + 恰好只有一块的列表。给多个实体会报 `ValueError`,并说明该怎么做:每个实体做成一个 Part 放进装配(只是放在一起时用单位位姿), + 或者先把它们合并、切割成一个实体。 +* **已移除**(它们只在被撤回的 2.2.8 里存在过):`bodies=` 参数、`Part.multi_body`、`scad.part_shape`、 + `inspect_body_overlaps_rlist`、`BodyOverlap`、`BodyOverlapWarning`,以及零件定义的 `simplecad-part-definition-3` 版式和 + `solid_cache.bodies`。记录了“一个 Part 里有多个实体”的模型 JSON,重放时会被拒绝,并给出同样的提示。 +* **零件笔记本里只有一个开放实体**时,仍然自动得到隐式 Part。**有多个开放实体**时没有产品:运行会报 `SeveralOpenBodiesError`, + 并列出这些实体;请用真正作为零件的那个来做 Part(`scad.make_part_rpart(part_id=scad.notebook_id(), body=...)`),或者给每个 + 实体各做一个 Part,再放进装配。 +* **编辑器**会把这些开放实体并排画出来,标题栏显示“N 个开放实体”;`use_as_product` 会用你选的那个实体做出 Part。 + +## 布尔运算:`cut_rsolid` 和 `intersect_rsolid` 会清理结果 + +`union_rsolid` 一直会把落在同一个曲面上的面和边合并;`cut_rsolid`、`cut_rsolid_list` 和 `intersect_rsolid` 以前不会。 +用圆柱从侧面切穿一个盒子,圆柱壁会沿着它的参数接缝被劈成两个面,而 OCCT 对沿着这种被劈开的面的边做圆角是错的: +它会返回体积不对的无效实体而且不报错(issue #88),或者拒绝,并把原因怪到半径上。 + +* **`cut_rsolid`、`cut_rsolid_list`、`intersect_rsolid` 增加 `clean=True`**(默认),把留下的被劈开的面和边合并。标签和血缘跟着 + 清理走。如果合并后的结果无效,就保留原始结果。`clean=False` 和以前一样返回原始结果。 +* **结果的面和边变少**:以前会留下被劈开的面的切割,几何(体积、面积、包围盒)不变,但拓扑变了,因此这种实体的指纹也变了。 + 包含这种切割的、已保存的产品需要重新生成。 +* **`fillet_rsolid` 和 `chamfer_rsolid` 会检查结果**:输入有效而 OCCT 判定结果无效时,会报出指明操作的错误,而不是把它当返回值交出去。 +* **圆角因面被劈开而失败时会直说**(并说明半径不是限制),不再建议更小的半径。 +* **记录的操作图**:`clean` 是 `make_cut_rsolid`、`make_cut_rsolid_list` 和 `make_intersect_rsolid` 的记录参数。2.3.0 之前记录的 + 图没有这个参数,重放时会和缺少任何参数的图一样被拒绝:请重新记录。 + +## 不经过 `.scadpkg` 文件直接导出,以及在源目录只读时运行 + +* **导出器接受构建的结果。** `export_product_package_to_step / stl / obj / mjcf` 和 FreeCAD 转换器除了包、字节和路径,也接受 + `@scad.part` / `@scad.assemble` 的结果(或它的定义),在内存里打包,且不生成场景(没有导出器会读那份网格投影)。 + 从刚建好的零件导出 STEP:`scad.exporter.export_product_package_to_step(build_part(), "part.step")`;issue #89 里那块 + 120 x 120 x 10 的板,先 capture 再导出要 1.85 秒,现在 0.26 秒。 +* **`run_notebook(path, cache=False)` 不碰源目录。** 它不再写 `__marimo__/.../deps.json`,也不在笔记本旁边创建状态目录,所以能在 + 源目录只读的环境里运行(比如源码以 `:ro` 挂载的容器)。带缓存的运行仍然把状态写在那里,写不进就报错。 +* **单脚本任务是受支持的入口。** 技能里“模型是 marimo 笔记本”(R3)仍然适用于人会打开或复用的一切,现在也允许任务明确要求单个脚本时 + 使用普通脚本,模板在 `domains/export-and-translation.md`。 + +## 指纹不会把落在量化边界上的值劈开 + +实体的几何指纹把每个测量值量化到 1e-7 mm。像 -3.34690815 mm 这样的设计值恰好落在这个取整的边界上,计算噪声(比如 BREP 往返) +差一个末位,就会被取整到相邻的整数,于是指纹变了,恢复拓扑快照时报 `geometry_mismatch`(issue #87)。现在量化器在取整前, +先把值吸附到一个比噪声粗得多、比量化单位细得多的网格上。带有边界上的值的实体,其指纹会变化一次;在此之前记录的快照无法恢复, +需要重新捕获。 + +## 修复 + +* 在 Windows 上,`sca cell` 不会再杀掉它正在对话的编辑器:现在探测编辑器进程时不再发信号(Windows 上 + `os.kill(pid, 0)` 会直接终止进程)。 +* 从源码构建:GCC 14(`operators_promote.cpp` 补上 ``)、MinGW(定义 `_USE_MATH_DEFINES`,以及编译器是 MinGW 的 + `g++` 时 `build_occt.py` 选用 Ninja);macOS 的安装包改在 `macos-15` 上构建(安装包仍然面向 macOS 14.0)。 + +## 给贡献者 + +* **在 Windows 上,直接 `uv sync` 就能构建。** 没有 Visual Studio 时,构建以前会落到 NMake 生成器而失败,每个贡献者都得手动设 + `CMAKE_GENERATOR`、`CC`、`CXX` 和 `CMAKE_ARGS`。现在构建后端(`tools/build_backend/scad_build.py`)在没有 Visual Studio 时选用 + Ninja 和 PATH 上的 gcc,自己找 OpenCASCADE(`SCAD_OCCT_PREFIX`、`C:/occt`,或者它一次性构建的用户缓存目录),并按内存限制并行数。 + 你自己设的值仍然优先。`uv` 只在 `src/cpp` 变化时才重新构建(`[tool.uv] cache-keys`),改版本号不会触发。 +* `AGENT.md` 记录了在 Windows 上如何重新编译 C++ 模块,以及提交该怎么组织:一个决策一个提交,说明里写清楚促成这个决策的讨论和原因。 diff --git a/pyproject.toml b/pyproject.toml index f8bdcd35..925732b2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "simplecadapi" -version = "2.2.9" +version = "2.3.0" description = "A simplified OpenCASCADE-native CAD modeling Python API" readme = "README.md" license = { file = "LICENSE" } diff --git a/uv.lock b/uv.lock index a597e696..ba2aaeec 100644 --- a/uv.lock +++ b/uv.lock @@ -2996,7 +2996,7 @@ wheels = [ [[package]] name = "simplecadapi" -version = "2.2.9" +version = "2.3.0" source = { editable = "." } dependencies = [ { name = "cryptography" },