A model is
a document
you can diff.
forma-dsl writes geometry as configuration, in an HCL-like syntax you can already read: declare parameters, nest blocks, reuse components. The document is solved by the Manifold CSG kernel compiled to WebAssembly - unchanged in Node and in the browser.
Watch it build something- Package
- npm i forma-dsl
- Version
- -
- Kernel
- Manifold · WASM
- Runtime deps
- -
- Output
- Mesh · binary STL
- Licence
- MIT
The language, running
Nothing here is a recording. The source types itself, and the solid beside it is the real library compiling that text in your browser - the same code the editor runs.
What you get
Parameters, not constants
A param block declares a typed input with bounds. The editor reads them and builds the controls; your build script passes them in.
Components that compose
Name a parameterised block once and call it like any builtin. Nesting is the operator: children in a row are a union, and that is the whole rule.
Content-addressed geometry
Every node carries a 128-bit digest of its content. A component used twenty times is one node, evaluated once - and an edit re-solves only what changed.
Errors that point at a line
Every failure carries a source location, so the editor underlines the mistake instead of printing a stack trace at you.
One runtime dependency
Just manifold-3d. Hand-written TypeScript declarations ship with the package, so the API is typed from either language.
Node and browser, same code
Render to a mesh for a viewer or to binary STL for a slicer. No build step, no platform branch, no headless browser in CI.
Start in a minute
Install it, write a document, and hand the parts to a viewer or a slicer. One
context is reused across renders: the digest cache is what makes the second one
cheap, and collect is what keeps a long run bounded.
Three calls carry most of the work:
-
render(source, options)Compiles a document and evaluates one model into parts, each carrying a mesh for a renderer and the solid it came from. -
describeParameters(source)Reads the declared inputs without solving anything. It is how the editor builds its controls, and how a build script knows what it may pass. -
toBinarySTL(part.concrete)A buffer a slicer will open, written straight to disk or handed to the browser as a download.
The same document renders in CI. There is no browser to drive and no display to fake: the kernel is WebAssembly, so a part that comes out of your laptop comes out of a runner too, and a pull request can check the geometry rather than the code that produced it.
The reference manual covers every declaration, block, function and error, each with usage and troubleshooting - and the editor runs the whole thing in the browser with no install at all.
Or build it from a Makefile
forma-dsl-cli
is the same kernel with no browser around it. forma render part.forma -o
part.stl solves a document and writes a binary STL, which is what puts a
.forma file in a build, a CI job or a release pipeline.
Three commands, and the pipe is the point:
-
forma renderSolves a document and writes the STL.-o -sends it to stdout and keeps the summary on stderr, so it pipes into whatever comes next. -
forma paramsThe document describing itself, as a table or as--json- which is how a script builds every variant without being told what they are. -
forma modelsNames the models a document declares, so--modelcan be given one that exists.
Three exit codes, because a script has to tell them apart: 0 worked,
1 the document did not build, 2 the invocation was
wrong. A failure prints as file:line:column: with the source and a
caret under the column - where an editor and a CI annotation can both find it.
It knows nothing about the language itself. Every block it accepts and every error it reports comes from the same package the editor and the server import, so a block added to the language is one it renders without being told.
Or describe it, and let a model write it
forma-dsl-mcp
is a Model Context Protocol
server over stdio. It carries the language - the guide, the block reference, the
worked examples and the full manual - and it runs the geometry kernel, so the
model it is talking to is answered with a solved part rather than with its own
guess about one.
Three tools, and the measurements are the point:
-
forma_writeBuilds the document and, only if it renders, writes it. What comes back is a table of every part with its bounding box, volume, triangle count and genus - so "is this the size I asked for, and is it actually a solid?" is answered before the work is handed over. -
forma_readReads a document back and builds it, so an existing file can be measured before it is edited. -
forma_export_stlRenders a part - or every part union - straight to binary STL.
A document that does not render is refused rather than written, and a part that silently solved to nothing is called out by name. Where the model guesses a block or an attribute that does not exist, the refusal names every one that does.
Where the files go is the client's decision, not the server's: it asks over MCP's roots capability and reads and writes only inside the directories it is handed.