cadjoint
cadjoint is differentiable code-first CAD. Sketches, constraints, SDF geometry, meshing and finite-element simulation compose into one function JAX can differentiate end to end.
The shipped scenes with their free parameters, construction handles, wireframe and STL export. It runs entirely in the browser, with nothing to install: WebGPU draws the model, and meshing falls back to WebAssembly without it.
The API is not stable. Expect breaking changes.
The playground runs the whole chain in the browser (modelling, meshing, studies and optimization) and writes every edit back into the Python source.
pip install cadjoint installs the library. Getting started covers installation, supported platforms and the playground command; the playground guide covers the app.
Components
| Layer | What it does |
|---|---|
| Geometry | Parametric Vector / Scalar carrying free / fixed flags |
| SDF | Primitives, booleans and transforms, all differentiable |
| Sketches | 2D profiles on work planes, extruded or revolved into solids that share their parameters |
| Constraints | Geometric relationships that reduce DOF, with Newton projection onto the constraint manifold |
| Functionalize | Convert a parametric scene to a pure JAX function for jit / grad / vmap |
| Meshing | Dual contouring built bottom-up in JAX; exact gradients through every continuous stage; OBJ / STL / STEP export |
| Simulation | Declared SimMesh and thermal/elastic studies, node selections, and pluggable solver backends |
| Flow | A prototype lattice-Boltzmann conjugate heat-transfer solver on the scene’s own field, with no mesh |
| Optimization | Declarative descent over named CAD parameters with the simulation as objective |
| Plugins | Meshers and solvers behind one apply/vjp contract, so one jax.grad crosses every tool boundary |
| Rendering | Forward sphere tracing, plus StableHLO → WGSL shader compilation |
| The playground | The whole toolchain as a browser app, with the Python source as its only state |
Pipeline
CAD parameters θ ──► constraints ──► SDF (JAX autodiff)
│ │
│ ▼
│ dual-contoured surface (JAX autodiff:
│ │ Newton on the true SDF)
│ ▼
│ ┌─ tetfill Tesseract ─────┐ (TetGen; frozen topology,
│ │ surface → TET4/TET10 │ exact pass-through VJP)
│ └───────────┬─────────────┘
│ ▼
│ ┌─ solver Tesseract ──────┐ (jax-fem adjoint, or
│ │ thermal / elastic FEM │ CalculiX Fortran adjoint)
│ └───────────┬─────────────┘
│ ▼
└────────── ∂J/∂θ ◄── objective J (JAX autodiff)
No two stages compute their derivative the same way, and TetGen has none; the frozen-topology pass-through supplies it. On the parametric L-bracket the composed loop lowers the objective 73% in 30 steps, with the adjoint checked against finite differences at every boundary.
Quick start
Three distance constraints determine an unknown point; the solved scene is an ordinary JAX function.
import jax.numpy as jnp
from cadjoint import extract_parameters, functionalize
from cadjoint.constraints import DistanceConstraint, solve_constraints
from cadjoint.geometry.parameters import Vector
from cadjoint.sdf.primitives import Sphere
from cadjoint.sdf.transforms import Translate
# Unknown point — wrong initial guess
p = Vector(jnp.array([0.5, 0.5, 0.0]), free=True, name="p")
scene = Translate(Sphere(radius=0.5), offset=p)
# Fixed anchors
anchor_a = Vector(jnp.array([0.0, 0.0, 0.0]), free=False, name="a")
anchor_b = Vector(jnp.array([4.0, 0.0, 0.0]), free=False, name="b")
anchor_c = Vector(jnp.array([2.0, 3.0, 0.0]), free=False, name="c")
true_p = jnp.array([2.0, 1.0, 0.0])
for anchor in (anchor_a, anchor_b, anchor_c):
DistanceConstraint(p, anchor, float(jnp.linalg.norm(true_p - anchor.value)))
solved = solve_constraints(scene)
_, fixed, _ = extract_parameters(scene)
sdf = functionalize(scene)(solved, fixed)
print(sdf(true_p)) # ≈ -0.5 — the solved sphere is centred on the true point
Modelling covers constraints and degrees of freedom; the guides continue through meshing, simulation and optimization.
