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
44 changes: 44 additions & 0 deletions .agents/docs/2026-11-14-add-manifold-mwrap-plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Add mcpplibs.mwrap.manifold (manifold-m 3.5.5.1)

## Form

- Third-party upstream [elalish/manifold](https://github.com/elalish/manifold), release v3.5.5, Apache-2.0 (its
`linalg.h` derives from Sterling Orsten's public-domain library).
- Form A external mwrap module repository [mcpplibs/manifold-m](https://github.com/mcpplibs/manifold-m), package
`mcpplibs.mwrap.manifold`, version 3.5.5.1 (wrapper revision 1 of upstream 3.5.5; the first indexed release of the
identity), tag `mwrap-3.5.5.1`; release asset `manifold-m-3.5.5.1.tar.gz` (`d80aa82f…7d3a`), CN mirror
`gitcode.com/mcpp-res/mwrap-manifold` with identical bytes. The repository's `mwrap-3.5.5` release is not indexed:
it was built without `-ffp-contract=off`, which upstream's own build sets (a fused multiply-add changes results).
- Implementation ownership: the library has no compat package in this index, so the wrapper vendors `src/*.cpp`,
`src/*.h` and `include/manifold/` from the release archive (checksums in `upstream/documents.json`) and builds them
as upstream's default build does (floating-point contraction off), save two choices: one thread (`MANIFOLD_PAR=-1`, no oneTBB) and no `CrossSection`
(`MANIFOLD_CROSS_SECTION` off: upstream's class needs Clipper2, and the backend without it,
`cross_section_boolean2.cpp`, is a stub in 3.5.5 — every operation gives nothing — so it is not vendored and
`<manifold/cross_section.h>` is not exported). It deploys upstream's README, LICENSE and AUTHORS with its own
LICENSE/NOTICE (`runtime.deploy`).

## Module

- `mcpplibs.mwrap.manifold`: every public name of `<manifold/manifold.h>` and `<manifold/polygon.h>` by `using` in the
namespace upstream declares it in — `manifold` (36 types, 10 functions, the alias `la`), `manifold::math` (16
functions), `linalg` (10 types, 116 functions and operators).
- Internal-linkage adaptation: seven `constexpr` objects at namespace scope are not `inline` (`kPi`, `kTwoPi`,
`kHalfPi`, `DEFAULT_SEGMENTS`, `DEFAULT_ANGLE`, `DEFAULT_LENGTH`, `linalg::identity`); a module cannot export
them, so they are declared again under their names, with their types and values, in `mcpplibs::mwrap::manifold`.
- No `.macros` module: the public headers define no object-like constant; `DEBUG_ASSERT` and `ASSERT` are upstream's
own control-flow macros, empty in this configuration. `<manifold/mesh.h>` names `std::istream` and `std::ostream`
without their header; the module's global fragment includes `<istream>` and `<ostream>` first.
- The export list is derived by `tools/surface.py` from the headers as clang reads them (its JSON AST), recorded in
`tools/surface.json`, and audited in CI: the module is the record on every row, and on the Linux LLVM row the
record is derived again and compared.

## Verification

- Wrapper CI (Linux x86_64 GCC 16.1 and LLVM 23.1.3, macOS LLVM, Windows LLVM): the module tests (solids, booleans
and their volumes, the same input giving the same mesh, the same triangles on every row — every corner bit for bit,
by a pinned hash; the order they come in follows the standard library's sort and differs between libstdc++ and
libc++ — meshes in and out, hull, decomposition and splitting,
linalg, outlines extruded, revolved, sliced and projected, header mixing), the export audit, and the default
template from an installed copy with a strict build.
- This index: consumer `tests/mwrap/manifold-boolean`; the installed-template check with `--deploy`
(`STATIC_NOTICES`).
5 changes: 5 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1728,6 +1728,11 @@ jobs:
shell: bash
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.cgltf@1.15 --deploy

- name: Installed manifold wrapper archive, actual default boolean template and its relocated notices
if: contains(format(' {0} ', env.MEMBERS), ' manifold-boolean ') && matrix.toolchain == 'default'
shell: bash
run: python3 tests/mwrap/check_installed_template.py --mcpp "$MCPP" --package mcpplibs.mwrap.manifold@3.5.5.1 --deploy

- name: Installed libarchive wrapper archive, actual default archive template and its relocated notices
if: contains(format(' {0} ', env.MEMBERS), ' libarchive-bundle ') && matrix.toolchain == 'default'
shell: bash
Expand Down
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@

### Added

- **mcpplibs.mwrap.manifold 3.5.5.1(新包)。** manifold-m:Manifold(始终保持流形的实体几何库)的 C++23 模块,以 `export using`
在上游的命名空间里导出 `<manifold/manifold.h>` 与 `<manifold/polygon.h>` 的全部公开名字(`manifold` 36 个类型、10 个函数,
`manifold::math` 16 个函数,`linalg` 10 个类型、116 个函数与运算符);7 个内部链接的 `constexpr` 常量(`kPi`、
`DEFAULT_SEGMENTS`、`linalg::identity` 等)以原名位于 `mcpplibs::mwrap::manifold`。导出清单由 clang 读头文件推出并在 CI 中审计。
封装仓库从上游 3.5.5 发布包取源码并自行构建:与上游一样关闭浮点收缩(封装修订 3.5.5.1 补上,3.5.5 未入索引),
单线程(`MANIFOLD_PAR=-1`,不依赖 oneTBB),同样的输入在各平台给出逐位相同的三角形(次序随标准库的排序而不同),不含 `CrossSection`(上游实现依赖
Clipper2,3.5.5 中不依赖它的后端只是占位)。消费者测试 `tests/mwrap/manifold-boolean`,安装后模板检查带 `--deploy`。
GLOBAL 为 GitHub 发布资产,CN 为 GitCode `mcpp-res/mwrap-manifold` 镜像,sha256 相同。
- **mcpplibs.mwrap.meshoptimizer 1.3、mcpplibs.mwrap.cgltf 1.15(新包)。** 两个 mwrap 包:meshoptimizer-m 以 `export using`
导出 `<meshoptimizer.h>` 的全部公开名字(9 个类型、20 个枚举值、89 个函数),`MESHOPTIMIZER_VERSION` 在 `.macros` 中为同名常量;
cgltf-m 导出 `<cgltf.h>` 的全部公开名字(72 个类型、102 个枚举值、37 个函数),并再导出 `.write`(`<cgltf_write.h>`)。
Expand Down
1 change: 1 addition & 0 deletions mcpp.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ members = [
"tests/mwrap/xxhash-digest",
"tests/mwrap/meshoptimizer-simplify",
"tests/mwrap/cgltf-inspect",
"tests/mwrap/manifold-boolean",
"tests/mwrap/libarchive-bundle",
"tests/mwrap/libc-stdio",
"tests/mwrap/miniaudio-engine",
Expand Down
50 changes: 50 additions & 0 deletions pkgs/mwrap/manifold.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
-- Immutable upstream-aligned mwrap source release (manifold-m mwrap-3.5.5.1, wrapper revision 1 of upstream 3.5.5);
-- GLOBAL and CN bytes are identical.
--
-- The wrapper is Apache-2.0; the library it vendors and links statically,
-- Manifold 3.5.5, is Apache-2.0: the package deploys its README, LICENSE and
-- AUTHORS beside programs (runtime.deploy).
package = {
spec = "1",
namespace = "mcpplibs.mwrap",
name = "manifold",
description = "manifold-m: Manifold 3.5.5 (booleans, hulls, level sets, slices and extrusions of solids that stay solid) as a C++23 module",
licenses = { "Apache-2.0" },
repo = "https://github.com/mcpplibs/manifold-m",
type = "package",
extensions = {
autoupdate = {
tag = "mwrap-${version}",
url = "https://github.com/mcpplibs/manifold-m/releases/download/${tag}/manifold-m-${version}.tar.gz",
},
},
xpm = {
linux = {
["3.5.5.1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/manifold-m/releases/download/mwrap-3.5.5.1/manifold-m-3.5.5.1.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-manifold/releases/download/3.5.5.1/manifold-m-3.5.5.1.tar.gz",
},
sha256 = "d80aa82f45a6aa10cf4d8611ad5c2140fb837015630f7710ee6ee4e078fd7d3a",
},
},
macosx = {
["3.5.5.1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/manifold-m/releases/download/mwrap-3.5.5.1/manifold-m-3.5.5.1.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-manifold/releases/download/3.5.5.1/manifold-m-3.5.5.1.tar.gz",
},
sha256 = "d80aa82f45a6aa10cf4d8611ad5c2140fb837015630f7710ee6ee4e078fd7d3a",
},
},
windows = {
["3.5.5.1"] = {
url = {
GLOBAL = "https://github.com/mcpplibs/manifold-m/releases/download/mwrap-3.5.5.1/manifold-m-3.5.5.1.tar.gz",
CN = "https://gitcode.com/mcpp-res/mwrap-manifold/releases/download/3.5.5.1/manifold-m-3.5.5.1.tar.gz",
},
sha256 = "d80aa82f45a6aa10cf4d8611ad5c2140fb837015630f7710ee6ee4e078fd7d3a",
},
},
},
}
1 change: 1 addition & 0 deletions tests/mwrap/check_installed_template.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"mcpplibs.mwrap.libarchive": ("libarchive.cppm", "upstream/libarchive/COPYING", b"Tim Kientzle", b"Tim Kientzle"),
"mcpplibs.mwrap.meshoptimizer": ("meshoptimizer.cppm", "upstream/meshoptimizer/LICENSE.md", b"MIT License", b"Arseny Kapoulkine"),
"mcpplibs.mwrap.cgltf": ("cgltf.cppm", "upstream/cgltf/LICENSE", b"Permission is hereby granted", b"Kuhlmann"),
"mcpplibs.mwrap.manifold": ("manifold.cppm", "upstream/manifold/LICENSE", b"Apache License", b"The Manifold Authors"),
"mcpplibs.mwrap.libc": ("libc.cppm", None, b"", b"ISO/IEC 9899:2018"),
}

Expand Down
11 changes: 11 additions & 0 deletions tests/mwrap/manifold-boolean/mcpp.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "manifold-boolean"
version = "3.5.5.1"
description = "Consume the immutable Manifold wrapper: booleans of solids and their volumes, a mesh in and out, the same mesh twice, an outline extruded and sliced"

[indices]
# Use the complete candidate built-in index prepared by tests/run_members.sh.
# An explicit empty table overrides the legacy workspace redirect.

[dependencies]
mcpplibs.mwrap.manifold = "3.5.5.1"
57 changes: 57 additions & 0 deletions tests/mwrap/manifold-boolean/tests/behavior.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// The installed Manifold wrapper: booleans of solids and their volumes, a
// mesh in and out, the same input giving the same mesh, an outline extruded
// and sliced, linalg's operators, the adapted constants.
import std;
import mcpplibs.mwrap.manifold;

using manifold::Manifold;
using manifold::vec3;

namespace {

bool near(double one, double other, double within = 1e-9) { return std::abs(one - other) <= within; }

} // namespace

int main() {
const Manifold one = Manifold::Cube(vec3(2.0));
const Manifold other = one.Translate(vec3(1.0, 0.0, 0.0));
if (one.NumVert() != 8 || one.NumTri() != 12 || !near(one.Volume(), 8.0)) return 1;
if (!near((one + other).Volume(), 12.0) || !near((one - other).Volume(), 4.0) || !near((one ^ other).Volume(), 4.0)) return 2;
if (!near(one.Boolean(other, manifold::OpType::Intersect).Volume(), 4.0)) return 3;

// A cube with a round hole through it has a handle.
const Manifold drilled = Manifold::Cube(vec3(2.0), true) - Manifold::Cylinder(4.0, 0.5, 0.5, 32, true);
if (drilled.Status() != Manifold::Error::NoError || drilled.Genus() != 1 || !(drilled.Volume() < 8.0)) return 4;

// The same input, the same mesh.
const auto made = [] { return (Manifold::Sphere(1.0, 24) - Manifold::Cube(vec3(1.0))).GetMeshGL(); };
const manifold::MeshGL first = made(), second = made();
if (first.vertProperties.empty() || first.vertProperties != second.vertProperties || first.triVerts != second.triVerts) return 5;

// A tetrahedron in, a solid out; one triangle short is refused with its reason.
manifold::MeshGL mesh;
mesh.numProp = 3;
mesh.vertProperties = {0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1};
mesh.triVerts = {0, 2, 1, 0, 1, 3, 1, 2, 3, 0, 3, 2};
if (!near(Manifold(mesh).Volume(), 1.0 / 6.0, 1e-12)) return 6;
mesh.triVerts.resize(9);
if (Manifold(mesh).Status() != Manifold::Error::NotManifold) return 7;

// An outline raised and cut again.
const manifold::Polygons square{{{-1, -1}, {1, -1}, {1, 1}, {-1, 1}}};
const Manifold raised = Manifold::Extrude(square, 3.0);
double area = 0;
for (const manifold::SimplePolygon& loop : raised.Slice(1.5))
for (std::size_t at = 0; at < loop.size(); ++at) area += linalg::cross(loop[at], loop[(at + 1) % loop.size()]) / 2;
if (!near(raised.Volume(), 12.0) || !near(area, 4.0)) return 8;

// linalg's operators and the constants of internal linkage, adapted.
const vec3 along(1.0, 2.0, 2.0);
if (linalg::length(along) != 3.0 || along * 2.0 - along != along) return 9;
if (manifold::degrees(mcpplibs::mwrap::manifold::kPi) != 180.0 || mcpplibs::mwrap::manifold::DEFAULT_SEGMENTS != 0) return 10;

std::println("Manifold 3.5.5 through import mcpplibs.mwrap.manifold: a drilled cube of volume {:.4f} and genus {}, {} triangles the same twice", drilled.Volume(), drilled.Genus(),
first.NumTri());
return 0;
}
Loading