This document is the practical setup guide for building, testing, and working on Geometer from a fresh checkout.
Geometer is a focused C++17 geometry library and CLI built on OpenCASCADE Technology (OCCT). Its job is to provide generic CAD/kernel geometry operations for browser, native CLI, and Python tooling.
Current and planned library surfaces include:
- STEP to GLB conversion.
- STEP hidden-line projection geometry.
- Planar contour extraction for simplified projected outlines.
- Planar batch boolean/offset solving for filled 2D geometry.
- Future STEP mesh/tessellation APIs for browser rendering.
The core library must stay generic. Do not put board placement rules, Altium
specific names, visualizer policy, or downstream application semantics into
geometer.
For the current callable C++, C ABI, Python, WASM, CLI, JSON, and binary formats, start at ../design/README.md.
src/cpp/lib/- reusable C++ library code.src/cpp/cli/- thin CLI wrapper around the library.python/geometer/- executable-backed Python package.examples/- Python, C++, and WASM-facing examples.tests/- stratified tests and C++ test sources.docs/adr/- architecture decisions.docs/requirements/- numbered requirements.docs/design/- maintained interface and format documentation.docs/developer/- developer setup, validation, and release commands.scripts/- dependency/build helper scripts.dist/- distributable binaries and WASM outputs..deps/- local generated dependencies and toolchains.
.deps/ is local generated state and must not be committed. It contains cloned
and built dependencies such as OCCT, emsdk, and OCCT WASM artifacts. It is
intentionally ignored by Git.
third_party/rapidjson/ is different. RapidJSON is header-only, small enough to
vendor, and required by OCCT's GLB export path. The vendored copy is checked in
so a fresh clone does not need a separate RapidJSON git checkout.
third_party/clipper2/ is also different. Clipper2 is a compact BSL-1.0 C++
library used by Geometer's generic planar batch solve API. The checked-in copy
contains only the C++ library sources, headers, upstream license, and a local
vendoring note.
dist/ is different. This repository currently treats dist/ as the location
for distributable binaries. CMake and WASM builds copy final outputs there.
Those outputs are committed when publishing changes so another project can clone
and use Geometer without a local native/WASM rebuild. Canonical native artifacts
live under dist/native/<platform>/ and canonical WASM artifacts live under
dist/wasm/<target>/. Root-level dist/geometer* artifacts are intentionally
not produced.
OCCT is not vendored into the repository and is not added with CMake
FetchContent, because OCCT uses CMAKE_SOURCE_DIR internally and does not work
correctly as a subdirectory dependency. Instead, Geometer builds OCCT as a
standalone project and finds it with find_package(OpenCASCADE).
Pinned dependency versions live in scripts:
- OCCT:
scripts/build_occt.py - RapidJSON:
third_party/rapidjson/ - Clipper2:
third_party/clipper2/ - emsdk:
scripts/build_wasm.py
For agent workspaces or downstream monorepo workspaces, prefer copying an already-prepared Geometer checkout when one is available locally. A prepared checkout includes:
.deps/with native OCCT state and WASM emsdk/OCCT state.build/for the native CMake build.build-wasm/for the Emscripten build.dist/with the committed/runtime artifacts.
On this development machine, the prepared checkout may live at
C:\ELI\geometer. Copying that directory into a sibling workspace preserves the
expensive dependency builds and allows fast API iteration. A fresh clone is still
valid, but the first native/WASM dependency build can take tens of minutes.
After copying into a workspace:
git status --short --branch
.\dist\native\windows-x64\geometer.exe --version
node .\dist\wasm\node-test\geometer-node-test.js --versionDo not run python scripts\build_occt.py --clean,
python scripts\build_wasm.py --clean, or delete .deps/ unless intentionally
refreshing dependencies. Those operations remove the cached dependency builds.
Native builds require:
- Git.
- Python 3.
- CMake 3.24 or newer.
- Ninja.
- A C++17 compiler toolchain.
On Windows, use a Visual Studio developer environment or another shell where the selected C++ compiler is available to CMake. The default CMake preset uses Ninja. The OCCT dependency is built with the active native compiler for that platform, so use the same shell consistently for configure/build/validation.
On WSL2/Linux, install the usual build toolchain first. For Debian/Ubuntu distros, the minimum package set is:
sudo apt update
sudo apt install -y build-essential git cmake ninja-build python3 python3-venvOn macOS, install equivalent tools with Homebrew:
brew install cmake ninja pythonWASM builds additionally require enough disk space for emsdk and a WASM OCCT
build. The script manages emsdk locally under .deps/.
From the repository root:
cmake --preset default
cmake --build build --config ReleaseFrom WSL2/Linux/macOS, the same preset is intended to work:
cmake --preset default
cmake --build build --config ReleaseThe native validation script runs the native build, verifies the
platform-specific dist/native/<platform>/geometer executable, projects the
SOT-23 STEP fixture to JSON/SVG/GLB, exercises the source-checkout Python wrapper
through GEOMETER_EXE, checks Linux dynamic dependencies where applicable, and
runs CTest:
python scripts/validate_native.pyPass --skip-ctest to run only the build, CLI, source-checkout Python-wrapper,
and dependency checks.
On first configure, CMake looks for OCCT at:
.deps/native/<platform>/occt-install/lib/cmake/opencascade
If OCCT is missing, top-level CMake automatically invokes:
python scripts\build_occt.pyThat script uses vendored RapidJSON, clones OCCT, builds OCCT as static
libraries, and installs it into .deps/native/<platform>/occt-install/. The
first run is slow. Later configures reuse that platform-specific .deps/ state
and should be fast.
The vendored RapidJSON v1.1.0 copy includes Geometer's small modern Clang compatibility patch so OCCT's GLTF toolkit compiles in native and WASM builds.
Build outputs are copied into dist/native/<platform>/ after a successful
native build.
Current platform directory names use windows-x64, linux-x64, macos-x64,
and macos-arm64.
Common native CLI commands:
.\dist\native\windows-x64\geometer.exe --version
.\dist\native\windows-x64\geometer.exe step-to-glb input.step output.glb
.\dist\native\windows-x64\geometer.exe step-project-hlr input.step output.json
.\dist\native\windows-x64\geometer.exe step-project-svg input.step output.svg --mode simple --view top
.\dist\native\windows-x64\geometer.exe init-request request.json --step input.step --operation step_hlr_projection_json --output output.json
.\dist\native\windows-x64\geometer.exe run request.json response.json
.\dist\native\windows-x64\geometer.exe planar-batch-solve request.bin response.bin --warmup 1 --repeat 5 --metrics metrics.jsonThe Python package uses the native CLI by default. From a source checkout,
GEOMETER_EXE is optional if dist/native/<platform>/geometer(.exe) exists.
The old Python wheel direction based on geometer.dll plus OCCT TK*.dll
runtime files is retired; dist/ should not persist those files.
The PyPI distribution name is wn-geometer; the import package remains
geometer.
To build a local Python wheel, first build the native CLI so
dist/native/<platform>/geometer(.exe) exists. Then run:
python -m build --wheel --outdir out\wheelhouse
python -m twine check out\wheelhouse\*.whlThe package validation script builds the local wheel, installs it into a clean
temporary environment, verifies that Python resolves the bundled executable from
inside the installed package, verifies the generated geometer console script,
and runs the headless package example:
python scripts\validate_python_package.pyThe wheel build copies the platform executable into
geometer/native/<platform>/ inside the wheel, exposes the geometer console
script, and marks the wheel platform-specific. The Windows executable wheel
should use a py3-none-win_amd64 tag because it contains no CPython extension
module.
Geometer currently publishes Apple Silicon macOS wheels and does not publish an Intel macOS wheel. The standard macOS release target is:
py3-none-macosx_11_0_arm64
The release scripts default Darwin native and wheel builds to
MACOSX_DEPLOYMENT_TARGET=11.0. When changing the target, rebuild OCCT and the
native CLI from clean generated state because static OCCT objects carry their
own Mach-O minimum OS metadata:
python scripts/build_occt.py --clean
rm -rf build-native-macos-arm64
python scripts/validate_native.py
python scripts/validate_python_package.py --skip-native-validationBefore uploading, verify both the filename and the bundled executable:
otool -l dist/native/macos-arm64/geometer | rg -A5 'LC_BUILD_VERSION|LC_VERSION_MIN_MACOSX'
python -m twine check out/wheelhouse/macos-arm64/wn_geometer-2026.5.25-py3-none-macosx_11_0_arm64.whlThe Mach-O minos value must not be newer than the wheel platform tag. Do not
retag an existing macOS wheel to a lower version without rebuilding native
artifacts.
Treat GitHub Actions macos-latest as a moving CI label, not as a release
target. GitHub announces macos-latest migrations and rolls them out gradually,
so normal maintenance is to keep the Geometer wheel target pinned to the
supported floor (macosx_11_0_arm64) and test that wheel on the current
macos-latest runner. A macos-latest migration only requires repackaging if
the wheel install or native smoke test fails on the new hosted runner, or if a
new SDK/toolchain raises the Mach-O minos above the published wheel tag.
For WSL/Linux release wheels, repair the built linux_x86_64 wheel before PyPI
upload:
uvx --from auditwheel --with patchelf auditwheel show out/wheelhouse/linux-x64/wn_geometer-*.whl
uvx --from auditwheel --with patchelf auditwheel repair --plat manylinux_2_39_x86_64 --wheel-dir out/wheelhouse/linux-x64/repaired out/wheelhouse/linux-x64/wn_geometer-2026.5.25-py3-none-linux_x86_64.whlThe exact manylinux tag is determined by auditwheel show; rebuild in an older
manylinux image if a wider glibc compatibility tag is required.
PyPI upload commands:
# Preflight metadata.
python -m twine check out\wheelhouse\windows-x64\wn_geometer-2026.5.25-py3-none-win_amd64.whl out\wheelhouse\linux-x64\repaired\wn_geometer-2026.5.25-py3-none-manylinux_2_39_x86_64.whl out\wheelhouse\macos-arm64\wn_geometer-2026.5.25-py3-none-macosx_11_0_arm64.whl
# Optional dry-run project on TestPyPI.
python -m twine upload --repository testpypi out\wheelhouse\windows-x64\wn_geometer-2026.5.25-py3-none-win_amd64.whl out\wheelhouse\linux-x64\repaired\wn_geometer-2026.5.25-py3-none-manylinux_2_39_x86_64.whl out\wheelhouse\macos-arm64\wn_geometer-2026.5.25-py3-none-macosx_11_0_arm64.whl
# Public PyPI release.
python -m twine upload --repository pypi out\wheelhouse\windows-x64\wn_geometer-2026.5.25-py3-none-win_amd64.whl out\wheelhouse\linux-x64\repaired\wn_geometer-2026.5.25-py3-none-manylinux_2_39_x86_64.whl out\wheelhouse\macos-arm64\wn_geometer-2026.5.25-py3-none-macosx_11_0_arm64.whlFor token-based upload, set TWINE_USERNAME=__token__ and put the PyPI or
TestPyPI API token in TWINE_PASSWORD, or use an equivalent .pypirc/keyring
setup. Do not write upload tokens into the repository.
The current release target is wn-geometer==2026.5.25; callers install
wn-geometer==2026.5.25 and import geometer.
For local token setup, copy .env.example to .env, fill the token values,
and keep .env out of version control.
Use this when changing the pinned OCCT version or when the local OCCT build is suspect:
python scripts\build_occt.py --clean
python scripts\build_occt.py
cmake --preset default
cmake --build build --config Release--clean removes OCCT build/install state for the current native platform under
.deps/native/<platform>/; it does not remove vendored RapidJSON, the shared
OCCT source checkout, or the Geometer build/ directory. Add --clean-source
only when intentionally refreshing the shared OCCT source checkout too.
The public Python package uses the executable backend only. Keep ctypes/native loading experiments out of the normal wheel and application path unless a future ADR explicitly reopens that backend.
From the repository root:
python scripts\build_wasm.pyThis script:
- Clones and activates pinned emsdk under
.deps/emsdk/. - Uses vendored RapidJSON and reuses or clones OCCT source under
.deps/. - Cross-compiles OCCT to
.deps/occt-wasm-install/. - Builds Geometer in
build-wasm/. - Copies the full browser/Web Worker C ABI outputs
geometer.js/geometer.wasmintodist/wasm/browser/. This is the official application integration WASM and includes OCCT-backed STEP/HLR/GLB plus planar byte APIs. - Copies the Node CLI parity/test outputs
geometer-node-test.js/geometer-node-test.wasmintodist/wasm/node-test/. - Copies the planar-only browser C ABI outputs
geometer-planar-browser.js/geometer-planar-browser.wasmintodist/wasm/planar-browser/. This smaller build intentionally excludes OCCT/STEP and is retained for planar-only browser workers. - Writes
dist/README.md.
To remove WASM-specific generated state:
python scripts\build_wasm.py --cleanThe Node CLI target uses filesystem access for command-line parity. The full
browser target is modularized and exports the flat C ABI entry points
geometer_step_hlr_projection_json_bytes and geometer_step_to_glb_bytes for
direct byte-buffer calls from JavaScript or a Web Worker.
The full browser target also exports geometer_version_string and
geometer_abi_version. Downstream browser consumers should check those before
depending on a specific ABI. Earlier pre-date ABI integers tracked planar batch,
diagnostic, and triangulation additions; current releases use the ADR 006
date-based ABI generation, for example 20260525.
Geometer follows ADR 006.
The current release identity is v2026-05-25; the CMake/PyPI package version
is 2026.5.25; the C ABI generation is 20260525.
The root CMakeLists.txt declares GEOMETER_RELEASE_DATE,
GEOMETER_RELEASE_VERSION, and GEOMETER_ABI_VERSION. The root
pyproject.toml package version must match GEOMETER_RELEASE_VERSION.
Generated build metadata must use UTC. Rebuild the persisted dist/ artifacts
when version or interface values change.
To refresh the copied STEP fixtures, GLB display meshes, and manifest from an embedded model folder:
powershell -NoProfile -ExecutionPolicy Bypass -File scripts\prepare_embedded_model_fixtures.ps1The script copies STEP/STP files into tests/fixtures/step/embedded_models/,
converts each one to GLB under tests/fixtures/glb/embedded_models/, and writes
tests/fixtures/embedded_models_manifest.json.
Serve the repository root and open the viewer:
python -m http.server 8123 --bind 127.0.0.1http://127.0.0.1:8123/examples/wasm/embedded_model_viewer.html
The viewer loads the GLB for the 3D pane and sends the matching STEP bytes to the browser WASM HLR API for the projection pane.
The HLR timing page runs the same browser worker projection path across the fixture set and reports STEP fetch-to-bytes timing separately from HLR timing:
http://127.0.0.1:8123/tests/wasm/hlr_benchmark.html
After a native CMake build:
ctest --test-dir build -C Release --output-on-failureAfter a WASM build, validate the browser STEP-byte-to-GLB-byte export:
node tests\wasm\step_to_glb_bytes_validation.jsTo benchmark the browser C ABI planar batch solver against a packed request:
node tests\wasm\planar_batch_solve_bytes_benchmark.js request.bin response.bin --warmup 1 --repeat 5 --metrics metrics.jsonTo run the same benchmark in headless Chrome, including the Web Worker path:
python tests\wasm\planar_batch_solve_bytes_chrome_benchmark.py request.bin --worker --warmup 1 --repeat 5 --metrics metrics.jsonThe Rack metadata under tests/ describes test strata, but the C++ tests are
registered through CTest.
Current limitation: the top-level CMake configure always resolves OCCT first.
That means even low-level tests need the native dependency setup when run
through CMake. For isolated low-level work before .deps/ exists, direct
compile checks are acceptable, but they are not a replacement for the CMake/CTest
path before publishing changes.
Example isolated contour test compile:
New-Item -ItemType Directory -Force out\developer | Out-Null
clang++ -std=c++17 -Isrc\cpp\lib src\cpp\lib\planar_contours.cpp tests\cpp\planar_contours_test.cpp -o out\developer\geometer_planar_contours_test.exe
.\out\developer\geometer_planar_contours_test.exe
Remove-Item .\out\developer\geometer_planar_contours_test.exeFormat touched C++ files with the repository .clang-format:
clang-format -i <files>Useful lightweight checks:
git diff --check
clang++ -std=c++17 -Isrc\cpp\lib -fsyntax-only <files>Run the full native build and CTest path before treating C++ changes as ready.
Release signoff also requires:
python -m pytest tests\L99_release -q
python scripts\validate_native.py
python scripts\validate_python_package.pyIf CMake cannot find OCCT:
python scripts\build_occt.py
cmake --preset defaultIf OCCT configure/build state looks stale:
python scripts\build_occt.py --clean
python scripts\build_occt.pyIf CMake cached the wrong dependency path, remove or recreate build/ and
configure again:
Remove-Item -Recurse -Force .\build
cmake --preset defaultBefore deleting generated directories, verify the path is inside this repository.
Never delete .deps/ or build/ paths computed from an untrusted variable.