Skip to main content

cadjoint

PyPI Python License

Differentiable code-first CAD: sketches, constraints, SDF geometry, meshing and FEM simulation composed into one function JAX can differentiate end to end.

The bracket scene in the cadjoint playground: with the pointer in vertex selection, the tip of the bracket's triangular gusset rib is dragged up the vertical web. The rib re-attaches where the pointer puts it and the smooth union re-blends the joint at frame rate while the plate and web stay put; the editor on the left holds the highlighted line rib_tip = Vector2(value=[-0.9, -0.62], free=True, ...). Then the camera makes one full turn around the bracket.

The handle under the pointer is a named Vector2 in scene.py, and the joint it moves is a smooth union of three extrusions. Dragging it writes the parameter buffer the shader already reads, so the blend follows at frame rate; releasing it patches the literal in the source. The source is the model.

The chain

One Python program declares the whole thing, and every arrow is a derivative JAX can take:

sketch vertices ─► constraints ─► SDF ─► mesh ─► FEM solve ─► objective
       └─────────────────────────── ∂J/∂θ ◄───────────────────────────┘

A sketch profile is a list of named Vector2 parameters. Constraints are residuals on those parameters, solved by Newton projection onto the constraint manifold. Extruding or revolving the profile produces an SDF that shares those parameter objects. Dual contouring turns the field into a surface, TetGen or Gmsh fills it, and jax-fem or CalculiX solves on it. A study is declared in the scene beside the geometry it loads — here the end cap's ThermalStudy, a heat flux on the bearing boss and a fixed temperature on the flange — and solving it is one click, meshing included:

The end cap's cap-conduction thermal study is run from the Simulate panel. Its mesh is declared as cut cells, so no volume mesh is built: the job chip counts up while the lattice's cut cells are assembled and solved, then the temperature field lands on the solid with its legend, and the part is turned with the field on it.

The same study is a function of the sketch, so jax.grad reaches from its objective back to a fin's tip coordinate. An Optimization declared in the scene descends on that gradient, every step a mesh, a solve and an adjoint, projected back onto the constraints. The heat sink below starts deliberately overbuilt and slims as its peak temperature falls:

The heat sink's cool-sink optimisation is run from the Optimize panel. Eight gradient steps, each a mesh, a thermal solve and an adjoint, replayed as a time-lapse: the fin comb reproportions as the peak temperature falls in the panel's trace.

A work plane taken from a face (SketchPlane.on(body.cap("+"))) is an expression over the parent feature's parameters, so a boss extruded from it differentiates with respect to its parent's depth. While sketching, the plane of the sketch you touch is drawn with its origin and normal (every plane, in every mode, is one switch away); the sketch tool shows where a new one will land before it exists, and a plane written as literals can be taken by its frame and moved, which rewrites the SketchPlane(origin=...) in the source:

The heat sink in the playground, switched to Sketch mode: no plane is drawn yet. The sketch tool is armed and a ghost frame labelled new sketch follows the pointer across the floor. Then the pointer lands on one of the fin comb's handles and the comb's plane appears — a translucent frame, an origin cross and a normal glyph, named — and stays once the handle is clicked; the plane is taken by its frame edge, the gizmo appears at its origin, and dragging the gizmo's Z arrow lifts the whole comb; the editor's SketchPlane(origin=[...]) literal is rewritten on release.

No boundary representation is stored: the field is the model, and every surface is derived from it at a resolution you choose.

What ships

  • Geometry — SDF primitives, smooth booleans, transforms, patterns; sketches on work planes extruded, revolved or lofted into solids that share their parameters; reference planes taken from faces, offsets and midplanes
  • Constraints — distance, angle, coincident, horizontal, vertical, parallel, perpendicular, equal-length, point-on-line, fixed
  • Materials — a sourced catalogue carrying density, conductivity, elastic constants and yield strength in SI, sampled per element by the solver
  • Meshing — dual contouring with sharp-feature vertices; TET4/TET10 through TetGen, second-order tets through Gmsh, or a voxelize-and-snap HEX8 path; OBJ, STL and faceted STEP export
  • Simulation — thermal and linear-elastic studies with programmatic node selections, solved by jax-fem or CalculiX 2.23; a lattice-Boltzmann flow prototype
  • Optimization — declared in the scene, descended with optax, every step projected back onto the constraints
  • Plugins — every non-JAX component (meshers, solvers) is a Tesseract behind one apply/vjp interface, run in-process, in a container or remotely
  • Rendering — a forward raymarcher in JAX, and a StableHLO → WGSL shader compiler for the browser
  • Playground — a WebGPU app where every edit rewrites the Python source that produced it

Install

pip install cadjoint         # pin an exact version: the API is not stable

From a checkout:

git clone https://github.com/andrinr/cadjoint
cd cadjoint
uv sync                      # CPU JAX — macOS, Linux, Windows
uv sync --extra cuda         # Linux + NVIDIA GPU instead

Everything beyond the geometry core is an extra, one --extra per name:

Extra Pulls in
fem jax-fem finite-element stack (basix, meshio, petsc4py, gmsh)
tesseract tesseract-core + tesseract-jax, the plugin runtime
gmsh Gmsh in-process for the tet10 mesher (GPL, hence its own extra)
viewer the playground server (pydantic), the Jupyter widget (anywidget) and the process monitor (psutil)
editor playground lint, completion and signature help (ruff, jedi)
stepcheck OCCT validation of STEP exports (dev)
docs Quarto API reference (quartodoc)

Avoid --all-extras on macOS: cuda has no macOS wheels.

fem is the one extra that compiles. petsc4py publishes no wheels, so uv sync --extra fem builds PETSc from source and needs a C compiler, gfortran and BLAS/LAPACK headers (apt install build-essential gfortran libopenblas-dev on Debian/Ubuntu). To skip the toolchain, run the solvers as Tesseracts: their images get PETSc from conda-forge and are published prebuilt to GHCR for amd64 and arm64, so Docker is the only requirement (see prebuilt images).

Supported platforms

Platform uv sync uv sync --extra fem
Linux x86_64 yes compiles PETSc
Linux ARM64 yes no: gmsh has no Linux ARM wheel
macOS arm64 yes compiles PETSc
macOS x86_64 (Intel) no: jaxlib has no Intel-Mac wheel for Python 3.11+ no
Windows x86_64 yes no: PETSc does not build natively

Every "yes" is a fresh uv sync --locked and import cadjoint in install.yml, run on each push. Where the fem column says no, the prebuilt solver Tesseracts above run the same solvers in Docker.

The playground

The playground ships inside the cadjoint package: the built UI is package data and cadjoint-viewer is its console script. It needs the viewer extra (the request schema imports pydantic), and editor adds lint, completion and signature help. A plain pip install cadjoint installs the command, but it stops at startup without them.

Without a checkout:

uvx --from "cadjoint[viewer,editor]" cadjoint-viewer --open

or into an environment of your own:

pip install "cadjoint[viewer,editor]"
cadjoint-viewer --open             # serves http://127.0.0.1:8765/

From a checkout, where scenes/ holds the example scenes:

uv sync --extra viewer --extra editor
uv run cadjoint-viewer --open

Scenes are opened from and saved to ./scenes under the directory the server was started in (CADJOINT_SCENES_DIR points it elsewhere). Outside a checkout that directory starts empty and the playground opens on its built-in starter. It runs in any browser with WebGPU: current Chrome, Edge and Safari.

The editor on the left holds scene.py; the viewport shows the compiled field. Vertex handles, gizmos, material swatches, face picks, constraint chips and solver runs all write back into the source. A properties window follows the selection and shows every argument the call was written with: literals as fields you can edit, expressions as the text they are.

The properties window in the playground. Clicking the fin comb's extrude row in the object tree fills the window with the call's arguments: the profile, the depth, which is the named parameter fin_depth, and the material. Typing a new depth and pressing Enter rewrites fin_depth's declaration in the editor and the comb deepens; choosing copper from the material dropdown rewrites the extrude call and the comb changes colour.

Three desks, Model, Sketch and Simulate, share one viewport. Studies solve with one click, results land in a labelled legend, and a declared optimization streams step by step and writes its optimized values into the program.

The server only listens on localhost and compiles each edit in a timed child process, but it executes Python on your machine: only run code you trust.

The playground guide walks through every window with recorded clips.

In Python

from cadjoint.construction import PolygonProfile, SketchPlane, extrude
from cadjoint.constraints import DistanceConstraint, satisfy_constraints
from cadjoint.geometry import Scalar, Vector2
from cadjoint.meshing import GridSpec, extract_mesh

a = Vector2([0.0, 0.0], free=True, name="a")
b = Vector2([1.0, 0.0], free=True, name="b")
c = Vector2([1.0, 1.0], free=True, name="c")
d = Vector2([0.0, 1.0], free=True, name="d")
DistanceConstraint(a, b, 1.2)

depth = Scalar(0.5, free=True, name="depth")
block = extrude(PolygonProfile([a, b, c, d], plane=SketchPlane()), depth=depth)
satisfy_constraints(block)          # projects a and b onto the constraint, in place

grid = GridSpec.from_bounds((-0.5, -0.5, -0.5), (2.0, 2.0, 1.5), 32)
mesh = extract_mesh(block, grid)

functionalize(block) returns a pure function of the free parameters, so jax.grad of anything downstream reaches a, b, c, d and depth. The getting-started guide continues from here to meshing, a thermal study and an optimization; examples/fem_bracket_optimization.py runs the full chain from the command line, with the adjoint checked against finite differences at every boundary.

Documentation

Developing

uv run pytest tests -q --ignore=tests/fem   # fast gate
uv run pytest tests/fem -q                  # needs the fem extra; CalculiX via CADJOINT_CCX
uv run pre-commit install                   # ruff on commit

The playground UI is a Solid + TypeScript app in frontend/, built into cadjoint/viewer/static and committed, so installing cadjoint needs no Node toolchain. npm run dev proxies to a running server, npm run build refreshes the bundle, npm test and npm run e2e run the unit and Playwright suites.

Docs need Quarto and the docs extra: uv run quartodoc build then quarto preview.

Performance work is measured, not guessed: benchmarks/jax_compile_profile.py breaks a worker request into trace, lower, XLA compile and cache time per program, and research/performance.md records what each lever bought.

History

cadjoint was an entry in the Tesseract Hackathon 2026 (Track 01, inverse design and shape optimization). That state is frozen on the tesseract-hackathon-2026 branch. Inspired by Fidget and Inigo Quilez's distance functions.

License

Apache License 2.0.

Metadata

Release files for cadjoint 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for cadjoint 0.1.1
File Size Uploaded
cadjoint-0.1.1.tar.gz 1.6 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for cadjoint 0.1.1
File Interpreter ABI Platform
cadjoint-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 3.3 MB

Release files / cadjoint-0.1.1.tar.gz

Download URL cadjoint-0.1.1.tar.gz
Size 1.6 MB
Tags Source
SHA-256 checksum
How to use checksums
208bcfe25e65080a6601d5b7beddc48fda216f0357999e9aa5e0c44e84b10a20
BLAKE2b-256 checksum
How to use checksums
7b9d8acd8a976702393fbe4808917bbd3db4b7b3f45d13ff5401c5df23f9cbc1
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release files / cadjoint-0.1.1-py3-none-any.whl

Download URL cadjoint-0.1.1-py3-none-any.whl
Size 1.7 MB
Tags Python 3
SHA-256 checksum
How to use checksums
58d03d328f3b94684c9c13bb2aef28b228cb1789a63b1cd07e89822c4ad3e722
BLAKE2b-256 checksum
How to use checksums
a2160bd718dfc18cd487d2cbe5acff635b56194d7a2e08cc709d04a83159e843
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 17, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page