cadjoint

Differentiable code-first CAD. Sketches, constraints, SDF geometry, meshing, and finite-element simulation compose into one function JAX can differentiate end to end — from the first named dimension to the last solver residual.

Warning

The API is not stable. Expect breaking changes.

The cadjoint playground: the scene program on the left, the parametric heat sink rendered live on the right, and the declared optimization in the side dock

Everything above happens in the browser: model the part, mesh it, declare the physics, and descend the objective, with every edit written back into the Python source that produced it. Open the playground guide.

The layers

Layer What it does
Geometry Parametric Vector / Scalar carrying free / fixed flags
SDF Primitives, boolean ops, 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
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

The chain

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 agree on how to compute a derivative, and one of them cannot be differentiated at all. On the parametric L-bracket the composed loop drops the objective 73% in 30 steps, with the adjoint checked against finite differences at every boundary.

Quick start

Three distance constraints fully determine an unknown point, and the solved scene is an ordinary JAX function afterwards.

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

Then get started for the rest of the arc.