emcad
A 2D polygon/CAD geometry kernel with a Rust core, plus a from-scratch ODB++ PCB design-format parser.
Written with AI assistance. The vast majority of this codebase was written by Claude Code, under human direction and review. See
LICENSEfor the license and an explicit note on what that means for reuse.
What this is
emcad is two largely independent things that happen to share one package:
- A 2D polygon boolean/geometry kernel (
emcad.kernel/emcad.poly) -- exact-predicate union / intersect / subtract / fragment, robust hole and nested-island handling, RDP simplification, and a "de-zigzag" cleanup pass for the tiny step artifacts that round-capped, tessellated-circle unions tend to leave behind. Built around an exact-predicate half-edge arrangement engine (grid-snapped integer coordinates, no epsilon-tuning), so degenerate cases -- T-junctions, same-side overlaps, islands nested inside dynamically-formed holes -- resolve correctly instead of needing ad-hoc tolerance fixes. - A native-Python ODB++ reader (
emcad.odbpp) -- parses the ODB++ product-model tree (matrix, layers, features, symbols, drill tools) into a typed object graph, then resolves it into per-layer, boolean-unified 2D polygons and a Z-stack, ready to hand to a mesher or FEM tool.
The kernel is a compiled Rust extension (emcad._rs, built from rust/ with
PyO3). It's a port of the original Numba/Python implementation (emcad 0.1)
that reproduced its results bit-for-bit while running typically 20-100x
faster; see benchmarks/RESULTS.md. There's no
external geometry library dependency (no Shapely/CGAL/etc).
Installation
pip install emcad
Prebuilt wheels cover Linux (x86_64, aarch64), macOS (Intel, Apple
Silicon) and Windows (x64) for every CPython >= 3.10. Building from source
needs a Rust toolchain (rustup), since the package is built with
maturin. Core dependencies: numpy, loguru,
matplotlib (used by the debug plotting helpers), msgpack (used by the
optional parsed-geometry cache).
See DEMO.md for a visual walkthrough of all of this rendered
with emcad.plot.GeometryPlotter, including holes/nested islands, the
dezigzag() cleanup pass, and via_wall_polygons.
Quick start: the polygon kernel
import emcad as cad
a = cad.Polygon([0, 10, 10, 0], [0, 0, 10, 10])
b = cad.Polygon([5, 15, 15, 5], [5, 5, 15, 15])
union = cad.add_polygons(a, b) # [Polygon] -- fused into one shape
intersection = cad.intersect_polygons(a, b)
difference = cad.subtract_polygons((a,), (b,)) # a minus b
grown = a.offset(0.5) # [Polygon] -- boundary moved 0.5 outward, round corners
shrunk = a.offset(-0.5) # negative = shrink (may split or vanish)
# In-place cleanup, both fail-safe (never turn a valid polygon invalid --
# see Polygon.simplify()/.dezigzag()'s own docstrings for why):
union[0].simplify(1e-6) # Ramer-Douglas-Peucker point reduction
union[0].dezigzag(1e-5) # collapse tessellated-circle "step" artifacts
union[0].regularize(0.25e-3) # snap edges back onto straight lines, drop small bumps
regularize(tol, dangle_deg=5.0) is map-making style outline regularization,
meant for copper outlines whose straight edges are littered with via pads
poking out after a union. RDP can only remove those bumps by keeping their
tips, which tilts the long edges; regularize instead anchors on edges lying
on a dangle_deg direction grid, flattens protrusions smaller than tol,
and rebuilds corners hidden under pads, keeping real arcs. In the ODB++
pipeline it's the regularize=True option of resolve_layer_polygons /
generate_traces (ODBImportConfig.trace_regularize).
offset(distance, max_angle_deg=15.0) (or cad.offset_polygon) is a true
dilation/erosion: the polygon plus or minus a strip along every edge and a
round sector at every corner that opens up, resolved in one boolean, so holes
filling, slots closing, necks splitting and the whole shape vanishing all come
out exactly. Corner arcs are circumscribed (never closer than distance); a
max_angle_deg above a corner's turn angle gives a miter join there.
Polygon.holes nests arbitrarily deep (a hole can carry its own holes, i.e.
islands, to any depth), and every boolean op / cleanup pass handles that
nesting automatically via the even-odd rule -- no manual depth bookkeeping.
Quick start: parsing an ODB++ board
from emcad.odbpp import PCBView
pcb = PCBView("/path/to/MyBoard.odb", thickness_stack=[35e-6, 508e-6, 35e-6])
xs, ys = pcb.get_board_polygon() # board outline, in meters
for z1, z2, material in pcb.iter_pcb_layers():
... # physical copper/dielectric stack
for layer in pcb.iter_geo_layers():
polygons = pcb.resolve_layer_polygons(layer) # boolean-unified per layer
for poly in polygons:
xs, ys = poly.xs, poly.ys # ready to feed to a mesher
for hole in pcb.iter_drill_holes():
... # plated + non-plated drill holes
PCBView parses the board itself (via ProductModel.from_path, its
one-step wrapper around the lower-level parser) -- the raw parsed object
graph is still available afterward as pcb.pm, if you want to walk it
yourself. If you already have a ProductModel (e.g. from your own call to
ProductModel.from_path(...)), pass that instead and PCBView reuses it
rather than parsing twice:
from emcad.odbpp import ProductModel, PCBView
pm = ProductModel.from_path("/path/to/MyBoard.odb")
pcb = PCBView(pm, thickness_stack=[35e-6, 508e-6, 35e-6])
Every coordinate everywhere in emcad.odbpp is a plain float in meters --
emcad.odbpp.units is the only place raw mm/inch/mil/micron conversion
happens, so nothing downstream needs to think about units.
Re-parsing and re-resolving a large board on every run gets expensive; if you're iterating on downstream code against the same board repeatedly, cache the resolved geometry once:
from emcad.odbpp import save_pcb_cache, load_pcb_cache
save_pcb_cache(pcb, "board.pcbcache")
pcb_fast = load_pcb_cache("board.pcbcache") # no re-parsing, no re-resolving
load_pcb_cache returns a drop-in stand-in for PCBView -- same public
methods, so existing code doesn't need to change.
Architecture
See CLAUDE.md for a detailed map of both subsystems --
module-by-module responsibilities, the exact-predicate arrangement engine's
design, and the ODB++ object-graph layering. It's written for an AI coding
assistant working in this repo, but it's an accurate, up-to-date architecture
reference for a human reader too.
Testing
uv sync --group dev # builds the Rust extension
uv run pytest
uv run python benchmarks/bench.py # kernel benchmark
uv sync/uv run rebuild the extension automatically when anything under
rust/ changes. tests/test_09_golden.py pins every kernel operation to
golden results recorded from the original implementation.
The suite is almost entirely regression coverage for the boolean kernel (basic ops, touching polygons, holes, islands nested in holes, bounding-box clustering, de-zigzag, ring self-intersection safety) plus a round-trip test for the ODB++ geometry cache. It doesn't depend on any ODB++ board files being present.
License
MIT -- see LICENSE, which also explains what the AI-authorship
note above means for reuse in more detail.
Metadata
Release files for emcad 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| emcad-0.2.0.tar.gz | 113.8 kB | With provenance | Details |
Built distributions (wheels)
| File | Reset | |||
|---|---|---|---|---|
| emcad-0.2.0-cp310-abi3-win_amd64.whl | CPython 3.10 | abi3 | Windows x86-64 | Details |
| emcad-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl | CPython 3.10 | abi3 | Linux musl 1.2+ x86-64 | Details |
| emcad-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ x86-64 | Details |
| emcad-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl | CPython 3.10 | abi3 | Linux glibc 2.17+ ARM64 | Details |
| emcad-0.2.0-cp310-abi3-macosx_11_0_arm64.whl | CPython 3.10 | abi3 | macOS 11.0+ ARM64 | Details |
| emcad-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl | CPython 3.10 | abi3 | macOS 10.12+ x86-64 | Details |
Total release size: 3.8 MB
Release files / emcad-0.2.0.tar.gz
| Download URL | emcad-0.2.0.tar.gz |
|---|---|
| Size | 113.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
46369ee7b9cbbf77f149b6f15deaa7a6770aaf567c0382f6abffb5658f0a0c66
|
|
BLAKE2b-256 checksum How to use checksums |
e60a744f39b5acbfbf58884bbded24bc2d448e151f032e8003387fa9328c0db9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / emcad-0.2.0-cp310-abi3-win_amd64.whl
| Download URL | emcad-0.2.0-cp310-abi3-win_amd64.whl |
|---|---|
| Size | 506.7 kB |
| Tags | CPython 3.10 Windows x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
a80629f594fd813a6b9b22a92a65008001c5ccd7eb332a539a8cbc945e657897
|
|
BLAKE2b-256 checksum How to use checksums |
e9a8caa95d07804bba73f5397373ce5103650c9ababaa0660425755244dd51df
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / emcad-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl
| Download URL | emcad-0.2.0-cp310-abi3-musllinux_1_2_x86_64.whl |
|---|---|
| Size | 835.8 kB |
| Tags | CPython 3.10 Linux musl 1.2+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
bcc358e14e35771ad61e2dfb6ccb8af2eb3545a744d0d95673806c28a594cb63
|
|
BLAKE2b-256 checksum How to use checksums |
cd6f5ff7c4919e576eaa08095481b3130824d74c59ad236654da73db12aeee1d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / emcad-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
| Download URL | emcad-0.2.0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl |
|---|---|
| Size | 624.9 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
fc88c4dc4ed78aff2008b47e4ba31282665a253d56bc5208c217ec47a20ccd46
|
|
BLAKE2b-256 checksum How to use checksums |
364f26897348a07c3b359b1fc5f7a8372a5c1331ad42a9aea370f821d2944a85
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / emcad-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
| Download URL | emcad-0.2.0-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl |
|---|---|
| Size | 601.8 kB |
| Tags | CPython 3.10 Linux glibc 2.17+ ARM64 abi3 |
|
SHA-256 checksum How to use checksums |
2b242a1f42390e57334c85226b6a1ce5eae4c7044d93f3e5951a475468804d17
|
|
BLAKE2b-256 checksum How to use checksums |
8f96098a23a9194180ca5c9438f92adab75bb6ff6d1eb7f8a4b1d05c7f13f9b7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / emcad-0.2.0-cp310-abi3-macosx_11_0_arm64.whl
| Download URL | emcad-0.2.0-cp310-abi3-macosx_11_0_arm64.whl |
|---|---|
| Size | 566.1 kB |
| Tags | CPython 3.10 abi3 macOS 11.0+ ARM64 |
|
SHA-256 checksum How to use checksums |
9ecc2caa05b823eafe182a8c98b0bbfa8f2b9e825f5a4db912ae4f80124a4daf
|
|
BLAKE2b-256 checksum How to use checksums |
28085cfdfa22df12b9c6a46d95cd938b4d70aa0f4d98678723d54f30c12ce25b
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logRelease files / emcad-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl
| Download URL | emcad-0.2.0-cp310-abi3-macosx_10_12_x86_64.whl |
|---|---|
| Size | 589.4 kB |
| Tags | CPython 3.10 abi3 macOS 10.12+ x86-64 |
|
SHA-256 checksum How to use checksums |
0ce493247f9c937d57d97ade984eaff5ca2f6a2602556158de3761d9778505a8
|
|
BLAKE2b-256 checksum How to use checksums |
e56036ccc898972e269a17dfd09d5048acf1b32e7ab2be8508e7755c8be8b62e
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logSecurity for emcad 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 7 files in this release have been uploaded with provenance metadata.
Attestation for emcad-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 15, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.