Max/Jitter externals that mirror Blender Geometry Nodes, so that a Blender geometry-node tutorial can be rebuilt in Max without inventing a different graph.
254 externals, one Max object per Blender node, plus the bridges Jitter needs
that Blender does not have (matrix in/out, OBJ/PLY/STL import, jit.gl.mesh
export).
- Reference: Blender · geometry nodes source · shader nodes source
- Platform: macOS (x64, arm64). Windows is structurally supported by the build but has never been compiled or run — treat it as untested, not as working.
- Status: 0.1.0 beta. See Known limitations; one of them changes how patches must be written.
| Family | Count | Examples |
|---|---|---|
| Volume & grid (SDF, OpenVDB-style) | 50 | sdf_sphere, grid_advect, volume_to_mesh |
| Attributes, fields & control flow | 45 | store_named_attribute, capture_attribute, switch |
| Utility, math & colour | 28 | math, vector_math, map_range, color_ramp |
| Mesh & curve primitives | 32 | primitive_cube, curve_primitive_spiral |
| Procedural textures | 10 | noise_texture, voronoi_texture, gabor_texture |
| Instances, topology, sampling, I/O, material bridge | rest | instance_on_points, sample_curve, to_gl_mesh |
Every object ships a help patch — 254 of them, generated rather than
hand-maintained — plus 11 runnable example patches under examples/.
Scope decisions live in adrs/ (12 ADRs). What is deliberately not ported, and
why, is written down there rather than left to guesswork: render-context nodes
(Fresnel, Light Path), Blender datablock nodes, simulation zones, and the legacy
texture-node tree.
- Max 8.0 or later (this is what
package-info.jsondeclares; development and all verification happen on Max 9) - macOS: Xcode command line tools. Accept the licence first:
sudo xcodebuild -license - CMake 3.19+
git submodule update --init --recursive # max-sdk-base
cmake -S . -B build -DBUILD_TESTING=ON
cmake --build build
ctest --test-dir buildThe build writes one .mxo bundle per object into externals/. Together with
package-info.json at the repository root, that folder is a Max package.
For the sanitizer build used during development, add -DGEOJITTER_SANITIZE=ON.
Max loads packages from its search path
(Cycling '74 docs). Put the
whole repository folder — it already has externals/ and package-info.json —
into a package location:
- macOS:
~/Documents/Max 9/Packages/ - Windows:
%USERPROFILE%\Documents\Max 9\Packages\
so you end up with Packages/GeoJitter/externals/jit.geo.primitive_cube.mxo and
so on. Then restart Max, or use File → Rescan.
For development, symlink instead of copying — a rebuild is then picked up on the next rescan.
Externals are not re-read from disk once Max has loaded them. A rebuilt object needs a Max restart, not a rescan. This surprises everyone once.
Create an object box and type jit.geo. — autocomplete lists everything. Then:
tutorials/— eight narrated, runnable patches rebuilding the canonical Blender Geometry-Nodes workflows, from displacement to repeat zones. Start here (index)extras/GeoJitter Node Overview.maxpat— every object in one browsable patchexamples/DISPLACEMENT_TUTORIAL.md— grid → noise → set position, the canonical first geometry-nodes exerciseexamples/SHADER_TUTORIAL.md— noise → colour ramp → set positionexamples/FLUID_TUTORIAL.md— the grid/volume sidehelp/jit.geo.<name>.maxhelp— one per object, reachable with right-click → Open Help
Geometry travels between objects as a pointer inside a Max message. The sender releases it, the receiver retains it (ADR-010) — so a geometry outlet can feed several inlets without copying, and nothing has to be freed by hand in a patch.
The maths lives in pure C with no Max or Jitter API, which is what makes it testable at all: 78 test binaries run under AddressSanitizer in CI on every push.
Each of the 254 nodes is classified by what can honestly be asserted about it —
tests/golden/COVERAGE.md names the class and the oracle for every one:
| Class | Nodes | Oracle |
|---|---|---|
| GOLDEN | 57 | compared against Blender ground truth, generated headless |
| ANALYTIC | 96 | closed-form output; the formula is the specification |
| INVARIANT | 78 | deterministic but Blender's exact output is not canonical (tessellation, element order), or randomized — structural invariants asserted instead |
| BRIDGE | 8 | no Blender equivalent (I/O, render, matrix); round-trip and format tests |
The point of the split is not to flatter the coverage number. A node whose tessellation legitimately differs from Blender's cannot be golden-tested, and saying so is more useful than a green tick that means nothing.
Setting an attribute and banging in the same message chain uses the previous
value. [rotation_y 45, bang( sends the bang before the attribute arrives.
Drive the two from separate events instead — a [t b b] with the bang on the
left outlet, or two message boxes. This is a regression from the attribute
rewrite in GJ-152…155 and is not yet solved; the measurements and the candidate
fixes are in plans/BUG-getattr-crash.md.
Windows is untested. The build declares it and the code avoids platform specifics, but nobody has compiled it there.
The material bridge is a bridge, not a renderer. jit.mat.* maps shader
parameters onto jit.gl.material and OB3D attributes. Jitter has no closure
renderer, so the same graph gives a plausible image, not Blender's image. ADR-012
says this in more detail; no golden parity is claimed for those objects.
| Path | Contents |
|---|---|
source/projects/geojitter/ |
main_<node>.c (Max wrapper) + jit_geo_<node>.c (Jitter object) + shared pure-C cores |
tests/ |
unit/ pure-core tests, mock/ ownership and protocol tests, golden/ Blender comparison |
tutorials/ |
eight narrated workflow patches + their write-ups (generated by build_tutorials.py) |
help/, examples/, extras/, patchers/ |
generated help patches, demo patches, the overview patch, UI abstractions |
adrs/ |
scope and architecture decisions |
plans/ |
per-node plans, trackers, open investigations |
doc/license/ |
the licence audit and the third-party licence texts |
scripts/ |
plan and help generators, the licence audit |
max_mcp/ |
an MCP server for generating .maxpat files from an editor — a development aid, not part of the package (setup) |
Help patches and examples are generated (examples/build_*.py). Edit the
generator, not the .maxhelp.
GPL-3.0-or-later, and the version matters.
Most of the ported maths comes from GPL-2.0-or-later files of Blender. Six files
come from the Cycles kernel and the shared radial-tiling maths, which Blender
licenses Apache-2.0. Apache-2.0 is compatible with GPL-3.0 but not with
GPL-2.0, so the combination can only be distributed under GPL-3.0-or-later —
which is why LICENSE carries the v3 text. The v2 text is kept at
doc/license/GPL-2.0-license.txt, because that is the licence the individual
ported files name.
Every source file carries an SPDX header, so the licence of any one of them can
be read off the file rather than inferred. doc/license/LICENSE-AUDIT.md lists
all 747 and where each obligation comes from; regenerate with
python3 scripts/license_audit.py, and --check fails on a file that has no
header or that transcribes Blender code without naming a licence.
The counts are worth knowing: 41 files carry a Blender GPL obligation and 6 are Apache-2.0 — those cannot be relicensed by anyone here. The remaining 700 are GeoJitter's own code with no inbound obligation at all; they are GPL-3.0-or-later because the project chose that, not because anyone upstream requires it.
One question is open, and this repository cannot settle it. The externals link
against MaxAudioAPI and JitterAPI, closed-source frameworks inside Max.app.
The Max SDK headers are plain MIT and pose no problem — that was checked, not
assumed — but GPL code linking a proprietary library is the classic
plugin-in-a-proprietary-host case, and GPLv2 §3's "major components of the
operating system" exemption does not obviously cover an application. Anyone
redistributing this as a binary package should get that answered first. The audit
sets out the facts so the question can be asked precisely.