Skip to content

Latest commit

 

History

History
506 lines (374 loc) · 18.5 KB

File metadata and controls

506 lines (374 loc) · 18.5 KB

Geometer Development

This document is the practical setup guide for building, testing, and working on Geometer from a fresh checkout.

Project Summary

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.

Repository Layout

  • 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.

Dependency Policy

.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

Workspace Copy Setup

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 --version

Do 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.

Prerequisites

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-venv

On macOS, install equivalent tools with Homebrew:

brew install cmake ninja python

WASM builds additionally require enough disk space for emsdk and a WASM OCCT build. The script manages emsdk locally under .deps/.

Native Build

From the repository root:

cmake --preset default
cmake --build build --config Release

From WSL2/Linux/macOS, the same preset is intended to work:

cmake --preset default
cmake --build build --config Release

The 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.py

Pass --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.py

That 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.json

The 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\*.whl

The 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.py

The 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.

macOS arm64 wheels

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-validation

Before 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.whl

The 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.whl

The 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.whl

For 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.

Manual OCCT Rebuild

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.

WASM Build

From the repository root:

python scripts\build_wasm.py

This script:

  1. Clones and activates pinned emsdk under .deps/emsdk/.
  2. Uses vendored RapidJSON and reuses or clones OCCT source under .deps/.
  3. Cross-compiles OCCT to .deps/occt-wasm-install/.
  4. Builds Geometer in build-wasm/.
  5. Copies the full browser/Web Worker C ABI outputs geometer.js / geometer.wasm into dist/wasm/browser/. This is the official application integration WASM and includes OCCT-backed STEP/HLR/GLB plus planar byte APIs.
  6. Copies the Node CLI parity/test outputs geometer-node-test.js / geometer-node-test.wasm into dist/wasm/node-test/.
  7. Copies the planar-only browser C ABI outputs geometer-planar-browser.js / geometer-planar-browser.wasm into dist/wasm/planar-browser/. This smaller build intentionally excludes OCCT/STEP and is retained for planar-only browser workers.
  8. Writes dist/README.md.

To remove WASM-specific generated state:

python scripts\build_wasm.py --clean

The 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.

Versioning

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.

Embedded Model Viewer

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.ps1

The 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.1

http://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

Tests

After a native CMake build:

ctest --test-dir build -C Release --output-on-failure

After a WASM build, validate the browser STEP-byte-to-GLB-byte export:

node tests\wasm\step_to_glb_bytes_validation.js

To 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.json

To 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.json

The 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.exe

Formatting And Checks

Format 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.py

Common Troubleshooting

If CMake cannot find OCCT:

python scripts\build_occt.py
cmake --preset default

If OCCT configure/build state looks stale:

python scripts\build_occt.py --clean
python scripts\build_occt.py

If CMake cached the wrong dependency path, remove or recreate build/ and configure again:

Remove-Item -Recurse -Force .\build
cmake --preset default

Before deleting generated directories, verify the path is inside this repository. Never delete .deps/ or build/ paths computed from an untrusted variable.