PyOCC is a modern, Pythonic computer-aided design (CAD) library built on top of pythonocc-core. It provides a clean, intuitive API for geometric modeling, visualization, and analysis while leveraging the power of the OpenCASCADE geometric modeling kernel.
- Mixin-Based Design: Compose functionality from reusable components
- Lazy Evaluation: Compute expensive operations only when needed
- Type Safety: Comprehensive type hints throughout the codebase
- NumPy Integration: Geometric data as NumPy arrays for scientific computing
- 3D Solids: Volumes with Boolean operations (union, difference, intersection)
- Surfaces & Faces: UV-parameterized surfaces with curvature analysis
- Curves & Edges: Parametric curves with arc-length parameterization
- Topological Analysis: Rich connectivity queries and adjacency relationships
- Interactive 3D Viewer: Cross-platform desktop visualization
- Jupyter Integration: WebGL-based notebook visualization
- Offscreen Rendering: Batch image generation and multi-view rendering
- Custom Materials: Advanced shading and lighting controls
- STEP/IGES: Industry-standard CAD file formats
- STL: 3D printing and rapid prototyping
- Native OCC: High-performance binary format
- Mesh Formats: Triangle mesh export for analysis
- conda or mamba (pythonocc-core is distributed via conda-forge only)
- Python 3.10 or higher
- pythonocc-core 7.9+ (OpenCASCADE), NumPy, SciPy
pythonocc-core (the OpenCASCADE bindings) is not available on PyPI β it is
distributed through conda-forge. Use the provided conda/mamba environment, which
also installs the visualization, Jupyter, and graph dependencies:
git clone https://github.com/LatticeLabsAI/pyocc.git
cd pyocc
# Create and activate the environment (mamba is faster; conda also works)
mamba env create -f environment.yml
mamba activate pyocc
# Install PyOCC itself (editable)
pip install -e .
pip install -e .installs the pure-Python dependencies and PyOCC, but not OpenCASCADE β that comes from the conda environment above. A common mistake ismamba create env pyocc; the correct form ismamba env create -f environment.yml.
PyOCC uses a mixin-based architecture that composes functionality from specialized components:
class Solid(Shape,
VertexContainerMixin, WireContainerMixin, ShellContainerMixin,
SurfacePropertiesMixin, VolumePropertiesMixin, TriangulatorMixin,
BottomUpFaceIterator, BottomUpEdgeIterator):
"""A 3D solid composed from Shape plus capability mixins."""
pass
# `Shape` already provides FaceContainerMixin, EdgeContainerMixin, and
# BoundingBoxMixin, so subclasses must NOT re-list them (doing so breaks the MRO).| Mixin | Functionality |
|---|---|
VertexContainerMixin |
Access and iterate over vertices |
EdgeContainerMixin |
Edge operations and adjacency queries |
FaceContainerMixin |
Face access with topological relationships |
BoundingBoxMixin |
Axis-aligned bounding box calculations |
SurfacePropertiesMixin |
Area, center of mass, moments of inertia |
VolumePropertiesMixin |
Volume calculations and mass properties |
TriangulatorMixin |
Triangle mesh generation |
import numpy as np
from pyocc.solid import Solid
from pyocc.viewer import create_viewer
# Create a box
box = Solid.make_box(10, 20, 30)
# Create a sphere
sphere = Solid.make_sphere(radius=15, center=np.array([25, 0, 0]))
# Boolean operations (static methods taking two solids)
union_result = Solid.union(box, sphere)
difference_result = Solid.difference(box, sphere)
# Display the result
viewer = create_viewer()
viewer.display(union_result, color='blue', transparency=0.3)
viewer.show()# Geometric properties
volume = box.volume()
area = box.area()
center = box.center_of_mass()
bbox = box.box()
print(f"Volume: {volume}")
print(f"Surface Area: {area}")
print(f"Center of Mass: {center}")
print(f"Bounding Box: {bbox}")
# Topological queries
num_faces = box.num_faces()
faces = list(box.faces())
vertices = list(box.vertices())
print(f"Number of faces: {num_faces}")
print(f"Number of vertices: {len(vertices)}")from pyocc.face import Face
# Get a face from the solid
face = next(box.faces())
# UV parameterization
u, v = 0.5, 0.5
point = face.point(u, v)
normal = face.normal(u, v)
curvature = face.gaussian_curvature(u, v)
# Closest point queries
query_point = np.array([5, 5, 5])
closest_data = face.find_closest_point_data(query_point)
print(f"Closest point: {closest_data.point}")
print(f"Distance: {closest_data.distance}")from pyocc.edge import Edge
# Create curves
start_point = np.array([0, 0, 0])
end_point = np.array([10, 10, 0])
line = Edge.make_line_from_points(start_point, end_point)
# Parametric evaluation
t = 0.5 # Parameter from 0 to 1
point = line.point(t)
tangent = line.tangent(t)
length = line.length()
# Arc creation
p1 = np.array([0, 0, 0])
p2 = np.array([1, 1, 0])
p3 = np.array([2, 0, 0])
arc = Edge.make_arc_of_circle(p1, p2, p3)from pyocc.viewer import Viewer
viewer = Viewer()
# Display with custom materials
viewer.display(box, color='red', transparency=0.2)
viewer.display(sphere, color='blue', material='metal')
# Lighting and shading
viewer.add_directional_light(direction=[1, 1, -1], intensity=0.8)
viewer.use_phong_shading()
viewer.enable_antialiasing()
# Background
viewer.set_background_gradient('white', 'lightblue')
viewer.show()from pyocc.viewer import OffscreenRenderer
renderer = OffscreenRenderer(width=1920, height=1080)
renderer.display(union_result, color='gold')
renderer.set_camera_position(eye=[50, 50, 50], target=[0, 0, 0])
# Render single image
image = renderer.render()
renderer.save_image('output.png')
# Multi-view rendering
views = renderer.render_multiple_views([
{'eye': [100, 0, 0], 'target': [0, 0, 0]},
{'eye': [0, 100, 0], 'target': [0, 0, 0]},
{'eye': [0, 0, 100], 'target': [0, 0, 0]}
])from pyocc.jupyter_viewer import JupyterViewer
# In a Jupyter notebook
viewer = JupyterViewer(width=800, height=600)
viewer.display(box, color='orange')
viewer.display(sphere, color='cyan', transparency=0.5)
viewer.show() # Displays interactive WebGL widgetfrom pyocc.io import load_step, save_step, load_stl, save_stl
# STEP files (industry standard)
shapes = load_step('model.step')
save_step([box, sphere], 'output.step')
# STL files (3D printing)
mesh_shape = load_stl('part.stl')
save_stl(box, 'box.stl', linear_deflection=0.1)
# Native OCC format (fastest)
box.save_to_occ_native('box.brep')
# Multiple shapes
Solid.save_shapes_to_occ_native('assembly.brep', [box, sphere])# Generate triangle mesh
vertices, triangles = box.get_triangles()
print(f"Mesh: {len(vertices)} vertices, {len(triangles)} triangles")
# Customize tessellation
box.triangulate_all_faces(linear_deflection=0.01, angular_deflection=0.1)
refined_vertices, refined_triangles = box.get_triangles()from pyocc.graph import face_adjacency, vertex_adjacency
# Create NetworkX adjacency graphs for analysis
face_graph = face_adjacency(box)
vertex_graph = vertex_adjacency(box)
# NetworkX integration
import networkx as nx
print(f"Face graph has {face_graph.number_of_nodes()} nodes")
print(f"Graph diameter: {nx.diameter(face_graph)}")# Geometric transformations
translated = box.translate(np.array([10, 0, 0]))
rotated = box.rotate_axis_angle(axis=[0, 0, 1], angle=np.pi/4)
scaled = box.scale(np.array([2, 1, 0.5]))
# Matrix transformations
transform_matrix = np.array([
[1, 0, 0, 10], # Translation in X
[0, 1, 0, 0], # No change in Y
[0, 0, 1, 5], # Translation in Z
])
transformed = box.transform(transform_matrix)# UV grid evaluation
face = next(box.faces())
u_values = np.linspace(0, 1, 20)
v_values = np.linspace(0, 1, 20)
# Evaluate points on surface
points = []
normals = []
for u in u_values:
for v in v_values:
point = face.point(u, v)
normal = face.normal(u, v)
points.append(point)
normals.append(normal)
points = np.array(points)
normals = np.array(normals)from pyocc.batch.render_multiview import render_multiple_shapes
# Process multiple shapes
shapes = [box, sphere, union_result]
render_configs = [
{'color': 'red', 'transparency': 0.2},
{'color': 'blue', 'transparency': 0.2},
{'color': 'green', 'transparency': 0.2}
]
# Batch render multiple views
render_multiple_shapes(shapes, render_configs, output_dir='renders/')PyOCC includes comprehensive testing for geometric operations:
# Geometric validation
assert box.valid() # Check geometric validity
assert box.volume() > 0 # Ensure positive volume
assert box.is_closed() # Verify no boundary edges
# Topological consistency
analyzer = box.check_unique_oriented_edges()
assert analyzer # All edges properly orientedpyocc/
βββ src/pyocc/
β βββ base.py # Core mixin classes
β βββ shape.py # Base shape class
β βββ solid.py # 3D volume operations
β βββ face.py # Surface operations
β βββ edge.py # Curve operations
β βββ wire.py # Connected edges
β βββ vertex.py # Point operations
β βββ viewer.py # 3D visualization
β βββ jupyter_viewer.py # Notebook integration
β βββ io.py # File I/O operations
β βββ geometry/ # Geometric utilities
β β βββ interval.py # 1D intervals
β β βββ box.py # Bounding boxes
β β βββ mesh_utils.py # Triangle meshes
β β βββ geom_utils.py # Coordinate conversion
β βββ batch/ # Batch processing
β βββ analysis/ # Analysis tools
βββ tests/ # Comprehensive test suite
βββ examples/ # Usage examples
βββ docs/ # Documentation
We welcome contributions! Please see our Contributing Guide for details.
git clone https://github.com/LatticeLabsAI/pyocc.git
cd pyocc
mamba env create -f environment.yml
mamba activate pyocc
pip install -e .
pytest- Follow PEP 8 guidelines
- Use type hints for all public APIs
- Comprehensive docstrings (Google style)
- Mixin composition over inheritance
- API Reference: docs/api/
- Examples: examples/
- Developer Guide: docs/developer_guide.md
- Architecture Guide: docs/architecture.md
- OpenCASCADE: The geometric modeling kernel powering PyOCC
- pythonocc-core: Python bindings for OpenCASCADE
- occwl: Original inspiration for the API design
- NumPy: Scientific computing foundation
This project is licensed under the MIT License - see the LICENSE file for details.
- Name: Ryan O'Boyle
- GitHub: @LayerDynamics
- Email: layerdynamics@proton.me
PyOCC: Making CAD programming accessible, powerful, and Pythonic.