Mod3D brings Open Cascade Technology (OCCT) to Python through pybind11, with a Python-first API for 3D CAD modeling, topology construction, Boolean operations, geometry creation, tessellation, and STEP exchange. It preserves OCCT's modeling capabilities while replacing much of its boilerplate with plain Python objects, lists, and NumPy arrays.
This binding layer focuses on simplifying the OCCT interfaces and leveraging standard Python containers (list, numpy arrays) instead of OCCT-specific collection types (TColgp_Array1OfPnt, TColStd_Array1OfReal, …). The goal is to offer a natural, Pythonic experience while retaining full access to the underlying geometry kernel.
Key design choices:
- Simplified interfaces – verbose OCCT patterns are wrapped behind concise constructors and properties. For example,
BSplineCurveaccepts plain Python lists ofgp.Pntor NumPy arrays directly. - Standard containers everywhere – where OCCT expects
TColgp_*orTColStd_*arrays, the bindings accept and return regular Python lists and NumPy arrays. - Direct transformation methods on shapes and geometry – instead of creating a
gp.Trsfmanually, you can call convenience methods such as.translated(),.rotated(),.scaled(), and.mirrored()directly on geometric objects:
from mod3d import gp
ax = gp.Ax1(gp.Pnt(0, 0, 0), gp.Dir(0, 0, 1))
# Translate a point without creating a Trsf
p2 = gp.Pnt(1, 2, 3).translated(gp.Vec(10, 0, 0))
# Rotate an axis system in place
ax2 = ax.rotated(gp.Ax1(gp.Pnt(0, 0, 0), gp.Dir(0, 0, 1)), 1.57)
# Scale a circle
circ = gp.Circ(gp.Ax2(), 5.0)
circ_big = circ.scaled(gp.Pnt(0, 0, 0), 2.0)
# Mirror a plane
pln2 = gp.Pln(gp.Ax3()).mirrored(gp.Pnt(10, 0, 0))These methods return a new, transformed copy — the original object is never mutated.
- CMake 3.30+ with a C++23-capable toolchain.
- Python 3.10+ interpreter and development headers.
- Open Cascade Technology (7.6+ recommended). Set
OpenCascade_DIRto the installation's CMake config directory. - pybind11 (must be findable by CMake).
mkdir -p build
cmake -S . -B build -DOpenCascade_DIR="C:/Program Files/OpenCASCADE/lib/cmake/OpenCASCADE"
cmake --build build --config ReleaseOn Linux or macOS, adapt the generator and toolchain flags accordingly (-G Ninja, -DCMAKE_BUILD_TYPE=RelWithDebInfo, etc.).
The mod3d package exposes the following submodules:
| Module | Description |
|---|---|
gp |
Basic geometric primitives: Pnt, Vec, Dir, XYZ, Ax1, Ax2, Ax3, Trsf, Pln, Lin2d, Pnt2d, Dir2d, Ax2d |
Geom |
3D geometry: Line, Circle, BSplineCurve, BSplineSurface, TrimmedCurve, CylindricalSurface, OffsetSurface, SurfaceOfRevolution |
Geom2d |
2D geometry: Line2d, Circle2d, BSplineCurve2d |
GeomAbs |
Enumerations: CurveType, SurfaceType, Shape, IsoType |
GeomAPI |
Curve interpolation and approximation: Interpolate |
GeomFill |
Sweep trihedron modes: Trihedron |
BRepBuilderAPI |
Shape construction: MakeVertex, MakeEdge, MakeWire, MakeFace, MakeBox, MakeSphere, MakeCylinder, MakeCone, MakeRevolution, MakePipe, MakePipeShell, MakePrism, MakePolygon |
BRepOffsetAPI |
Offset/filling operations: MakeFilling |
BooleanOp |
Boolean operations: Common, Cut, Fuse, Section |
BRepFillet |
Fillet operations: MakeFillet, MakeFillet2d |
BRepGProp |
Global properties (mass, centre of mass, volume, surface area): GProps, BRepGProp |
BRepExtrema |
Distance computation: DistShapeShape |
TopoDS |
Topology data structures: Shape, Vertex, Edge, Wire, Face, Shell, Solid, Compound |
TopExp |
Topology exploration: get_vertices, etc. |
TopAbs |
Topology enumerations: ShapeEnum |
Adaptor |
Curve/surface adaptors: GeomCurve |
ShapeFix |
Shape healing: Wire |
StepControl |
STEP I/O: Reader, Writer, ReturnStatus, StepModelType |
Render |
Tessellation: extract_tessellation |
ShapeRenderer |
Interactive 3D visualization widget |
from mod3d import gp
# Points, vectors, directions
p = gp.Pnt(1.0, 2.0, 3.0)
v = gp.Vec(1.0, 0.0, 0.0)
d = gp.Dir(0.0, 0.0, 1.0)
# Transformations
trsf = gp.Trsf()
trsf.set_translation(gp.Vec(5.0, 0.0, 0.0))from mod3d import BRepBuilderAPI, gp, Geom
# Primitives
box = BRepBuilderAPI.MakeBox(10.0, 10.0, 10.0).shape()
sphere = BRepBuilderAPI.MakeSphere(5.0).shape()
cylinder = BRepBuilderAPI.MakeCylinder(2.0, 15.0).shape()
cone = BRepBuilderAPI.MakeCone(10.0, 5.0, 20.0).shape()
# Edges and wires
p1, p2 = gp.Pnt(0, 0, 0), gp.Pnt(10, 0, 0)
edge = BRepBuilderAPI.MakeEdge(p1, p2).edge()
wire = BRepBuilderAPI.MakeWire(edge).wire()
# Faces from wires or surfaces
face = BRepBuilderAPI.MakeFace(wire).face()import numpy as np
from mod3d import gp, Geom
# Using gp.Pnt list
curve = Geom.BSplineCurve(
poles=[gp.Pnt(0, 0, 0), gp.Pnt(1, 1, 0), gp.Pnt(2, 1, 0), gp.Pnt(3, 0, 0)],
knots=[0.0, 1.0, 2.0],
multiplicities=[3, 1, 3],
degree=2,
)# Using numpy arrays
curve = Geom.BSplineCurve(
poles=np.array([[0, 0, 0], [10, 0, 0], [10, 10, 0], [10, 10, 10]]),
weights=[1.0, 1.2, 0.8, 0.5],
knots=[0, 1],
multiplicities=[4, 4],
degree=3,
)import mod3d
sphere = mod3d.BRepBuilderAPI.MakeSphere(5.0).shape()
box = mod3d.BRepBuilderAPI.MakeBox(10.0, 10.0, 10.0).shape()
# Move box to overlap
trsf = mod3d.gp.Trsf()
trsf.set_translation(mod3d.gp.Vec(-10.0, -10.0, -10.0))
box = box.moved(trsf)
# Intersection, subtraction, union, section
common = mod3d.BooleanOp.Common(sphere, box).shape()
cut = mod3d.BooleanOp.Cut(box, sphere).shape()
fuse = mod3d.BooleanOp.Fuse(box, sphere).shape()
section = mod3d.BooleanOp.Section(box, sphere).shape()from mod3d import BRepFillet
# 3D fillet on Boolean result edges
fillet_maker = BRepFillet.MakeFillet(result)
for edge in fuse.section_edges():
fillet_maker.add(1.0, edge)
filleted = fillet_maker.shape()
# 2D fillet on a planar face
fillet2d = BRepFillet.MakeFillet2d(face)
fillet2d.add_fillet(vertex, 1.0)
fillet2d.build()from mod3d import BRepOffsetAPI, GeomAbs
fill_op = BRepOffsetAPI.MakeFilling()
fill_op.add(edge1, GeomAbs.Shape.C0, is_bound=True)
fill_op.add(edge2, GeomAbs.Shape.C0, is_bound=True)
fill_op.add(gp.Pnt(10, 5, 0)) # optional interior constraint
fill_op.build()
face = fill_op.shape()from mod3d import BRepBuilderAPI, Geom, gp
# Simple pipe
pipe = BRepBuilderAPI.MakePipe(spine_wire, profile_wire)
pipe_shape = pipe.shape()
# Multi-profile pipe shell
pipe_shell = BRepBuilderAPI.MakePipeShell(spine_wire)
pipe_shell.add(profile1_wire)
pipe_shell.add(profile2_wire)
pipe_shell.build()
result = pipe_shell.shape()from mod3d import gp, GeomAPI
points = [gp.Pnt(0, 0, 0), gp.Pnt(1, 1, 0), gp.Pnt(2, 0, 0)]
interp = GeomAPI.Interpolate(points)
interp.perform()
curve = interp.curvefrom mod3d import BRepExtrema, BRepBuilderAPI, gp
box1 = BRepBuilderAPI.MakeBox(10.0, 10.0, 10.0).shape()
box2 = BRepBuilderAPI.MakeBox(gp.Pnt(20.0, 0.0, 0.0), 10.0, 10.0, 10.0).shape()
dist = BRepExtrema.DistShapeShape(box1, box2)
print(dist.value) # 10.0from mod3d import BRepGProp, BRepBuilderAPI
box = BRepBuilderAPI.MakeBox(10.0, 10.0, 10.0).shape()
props = BRepGProp.BRepGProp.linear_properties(box)
print(props.centre_of_mass) # (5.0, 5.0, 5.0)from mod3d import StepControl, BRepBuilderAPI
# Reading
reader = StepControl.Reader()
reader.read_file("model.step")
reader.transfer_roots()
shape = reader.one_shape
# Writing
writer = StepControl.Writer()
writer.transfer(shape, StepControl.StepModelType.AsIs)
writer.write("output.step")from mod3d import Render, BRepBuilderAPI
box = BRepBuilderAPI.MakeBox(10.0, 20.0, 30.0).shape()
faces, edges = Render.extract_tessellation(box, linear_deflection=0.1)
# Each face: (triangles, vertices, normals, uvs) as numpy arrays
for triangles, vertices, normals, uvs in faces:
print(f"{vertices.shape[0]} vertices, {triangles.shape[0]} triangles")from mod3d import ShapeRenderer, BRepBuilderAPI, gp
# Build a scene with multiple primitives
box = BRepBuilderAPI.MakeBox(10.0, 10.0, 10.0).shape()
sphere = BRepBuilderAPI.MakeSphere(gp.Pnt(15, 5, 5), 4.0).shape()
cyl = BRepBuilderAPI.MakeCylinder(2.0, 12.0).shape()
renderer = ShapeRenderer()
renderer.angle_deflection = 5
renderer.linear_deflection = 0.01
renderer.width =800
renderer.height=600
renderer.add_shape(box, {'surface_color': '#ff6600'})
renderer.add_shape(sphere, {'surface_color': '#0066ff'})
renderer.add_shape(cyl, {'surface_color': '#00cc44'})
renderer.render(background='lightgray')pytest tests/pybind11must be available to CMake (install viapip install pybind11or system package).- The build sets
CMAKE_POSITION_INDEPENDENT_CODEso the module can be linked into larger Python extension packages. - If you need to customize the OCCT search paths, pass
-DOpenCascade_DIRor setOPEN_CASCADE_DIRbefore configuring. - Optional pygbs integration is available when
MOD3D_USE_GBS=ON(default) and theGbspackage is found.






