Python bindings (Cython) for the CGAL 2D Arrangements package
(Arrangement_on_surface_2, CGAL 6.1).
An arrangement is the planar subdivision induced by a set of curves: vertices, edges and faces stored in a doubly-connected edge list (DCEL) that you can traverse and edit (insert / split / merge / remove), query (point location, ray shooting, zone, vertical decomposition), overlay with other arrangements, and combine through 2D Boolean set operations. Everything is computed exactly — rational and algebraic arithmetic, no tolerances, no degenerate case that silently goes the wrong way.
import arrangement_2d as a2
arr = a2.Arrangement("segment")
arr.insert([a2.Segment((0, 0), (4, 0)), a2.Segment((4, 0), (4, 4)),
a2.Segment((4, 4), (0, 4)), a2.Segment((0, 4), (0, 0)),
a2.Segment((-1, 2), (5, 2))])
print(arr) # Arrangement(kind='segment', vertices=8, edges=9, faces=3, curves=5)
face = arr.locate((2, 1)) # -> Face
for he in face.outer_ccb():
print(he.source.point, "->", he.target.point)
face.data = {"name": "lower half"}
print(a2.regions.face_area(face)) # 8 -- an exact Fraction- Seven geometry kinds, one CGAL traits instantiation each — see the table below.
- Exact arithmetic everywhere. Integers,
float(its exact binary value),Fraction,Decimaland numeric strings go in;Fraction,SqrtExtension(a, b, c)(=a + b·√c) orAlgebraic(approximation + certified, refinable interval) come out..approxgives correctly roundedfloats whenever you want them. - Curve history in every arrangement: which input curve induced which edge,
which curves an edge originates from, and
remove_curve(). - Python data on every element.
vertex.data,halfedge.data,face.datahold an arbitrary object; it survivescopy()and can be produced by overlay callbacks, and reference cycles through it are still garbage-collected. - Observers — a Python subclass notified of every DCEL change (create / modify / split / merge / move / remove of vertices, edges, faces and CCBs).
- Overlay with the full ten-callback
OverlayTraitsinterface, or theon_vertex/on_edge/on_faceshorthand. - Point location with the naive, simple, walk, landmarks and trapezoid strategies
(attachable and kept up to date), plus ray shooting, batched location,
zone()and vertical decomposition. - 2D Boolean set operations (
PolygonSet,|,&,-,^,~) for the four kinds CGAL supports them for. - Input cleanup (
arrangement_2d.cleanup) — tolerance-based snapping of messy segment soups (endpoint gaps, near T-junctions, duplicates, dangling pieces) before the exact arrangement, with a near-miss report to choose the tolerance. - Region helpers (
arrangement_2d.regions) — merge faces, split a face with a curve, connected components, face areas, region extraction by predicate, union outlines. - Plot helpers (
arrangement_2d.plot) — matplotlib rendering of arrangements, polygon sets and curve lists, imported lazily. - Safe handles. A
Vertex/Halfedge/Face/CurveHandlewhose element is gone raisesInvalidHandleErrorinstead of crashing the interpreter, and CGAL's preconditions stay compiled in and become Python exceptions.
| kind | curves | topology | Boolean ops | coordinates |
|---|---|---|---|---|
segment |
line segments | bounded plane | yes | rational |
linear |
segments, rays, lines | unbounded plane | – | rational |
circle_segment |
circular arcs, full circles, segments | bounded plane | yes | a + b·√c |
polyline |
polylines | bounded plane | – | rational |
bezier |
polynomial Bézier curves (any degree) | bounded plane | yes | algebraic |
conic |
conic arcs (ellipses, parabolas, hyperbolas, circles, segments; rational quadratic Bézier) | bounded plane | yes | algebraic |
sphere |
geodesic arcs on the unit sphere | sphere | – | rational directions |
Requirements: a C++17 compiler, CGAL 6.1 headers, GMP, MPFR, Boost headers, Python ≥ 3.10,
Cython ≥ 3. On macOS: brew install cgal gmp mpfr boost.
$ pip install -e . # or: python setup.py build_ext --inplace -j 8
$ pytest -qnumpy (bulk exports as arrays) and matplotlib (arrangement_2d.plot) are optional.
See setup.py for the environment variables that control library discovery and build
flags.
- docs/user_guide.md — concepts, every kind with worked examples, history, data, observers, overlay, point location, Boolean operations, the region and plot helpers, the exactness and error models, and the CGAL 6.1 limitations in user terms.
- docs/api_reference.md — every public class, method and
function, generated from the docstrings by
python docs/gen_api_reference.py. - docs/dev/DESIGN.md — architecture: the type-erased C++ core, the handle/validity model, how Python data lives on DCEL elements.
- docs/dev/CGAL_TRAPS_CHECKLIST.md — every CGAL 6.1 trap this binding works around, with file and line.
- docs/dev/cgal61_api/ — compile-verified maps of the CGAL 6.1 API used here.
Boolean operations on exact circular-arc regions:
left = a2.PolygonSet("circle_segment")
left.insert(a2.Polygon([a2.Circle((0, 0), 2)]))
right = a2.PolygonSet("circle_segment")
right.insert(a2.Polygon([a2.Circle((2, 0), 2)]))
lens = left & right
print(lens.number_of_polygons_with_holes, lens.oriented_side((1, 0))) # 1 1Overlaying two arrangements and labelling every result face with the pair of input faces it came from:
other = a2.Arrangement("segment")
other.insert([a2.Segment((2, 2), (6, 2)), a2.Segment((6, 2), (6, 6)),
a2.Segment((6, 6), (2, 6)), a2.Segment((2, 6), (2, 2))])
result = arr.overlay(other, on_face=lambda fa, fb: (fa.data, fb.data))Merging every face that carries a label, region by region:
from arrangement_2d import regions
for group in regions.extract_regions(arr, lambda f: f.data is not None):
regions.merge_faces(arr, group)Drawing:
import matplotlib.pyplot as plt
a2.plot.plot_arrangement(arr, tolerance=1e-2, face_colors="data")
plt.show()