Overview · CoScad

coscad

A concise glyph/pipeline CAD language in Haskell with its own geometry: parts are tessellated by the compiler and combined by the pinned Manifold kernel, with an assembly layer, a manufacturing stage that emits print-ready per-bed STLs, build plans with rendered step images, and stress analysis. Intelligence lives in the compiler — anchors, beziers, attachments, fit, orientation, packing, meshing and images are all computed here, so nothing else is needed between a .coscad file and the slicer. OpenSCAD and BOSL2 are not used (since 1.6.0.0); a part can still be exported as .scad for people who continue in OpenSCAD.

.coscad  ──coscad stl──▶ .stl                          (one part)
.assemble ─coscad───▶  asm + plate PNGs + _design.json  (design stage)
.assemble ─coscad next▶ bedN.stl + _manifest.json       (manufacturing)
manifest  ─bambu-slice▶ bedN.gcode.3mf → printer        (scripts/bambu-slice.py)

Install

On Apple Silicon with macOS 15 or newer, install the prebuilt native-kernel release from the shared Homebrew tap:

brew install hyperswine/tap/coscad
coscad --version

This downloads a checksum-verified binary; GHC, Cabal and a source checkout are not required. It installs coscad, man coscad, the docs under $(brew --prefix)/share/doc/coscad, and the examples under $(brew --prefix)/share/coscad/examples.

Prebuilt binaries for macOS and Linux are attached to each GitHub release (coscad-<os>-<arch>.tar.gz) with the man page and examples; put coscad on your PATH and, if you want man coscad, copy man/coscad.1 into a man1 directory on your MANPATH. Or build from source with cabal and GHC 9.8 (both installable with ghcup; the dependency set is pinned in cabal.project.freeze). That is the whole toolchain: the pinned native geometry kernel (Manifold 3.5.2 plus Clipper2, native-kernel.lock.json) is vendored under third-party/ and compiled into the coscad library, so cabal build needs no CMake, pkg-config or download:

cabal update
cabal install exe:coscad --install-method=copy --installdir=bin   # binary lands in bin/

The kernel is required: there is no build without it, and no other renderer.

macOS note: ghcup's GHC links against the Command Line Tools SDK. If a CLT update leaves that SDK newer than Xcode's linker, linking fails with tapi error ... unknown architecture arm64e.x1-macos. Until Xcode and the CLT match again, tell GHC to link with the CLT toolchain by adding to cabal.project.local (git-ignored):

package coscad
  ghc-options: -pgml /Library/Developer/CommandLineTools/usr/bin/clang

Every command renders natively: stl, check, next and stress mesh parts with the compiler and the kernel, and plan --png, site and the design stage draw images with a built-in CPU renderer, without a display server or a GPU. The renderer caches meshes across steps, fits an orthographic view, highlights new parts and uses a depth buffer with antialiasing. Nothing external is needed. --backend=native is accepted for older scripts; --backend=openscad is an error that points at the .scad export. Check the binary with:

coscad doctor

which reports the linked kernel and renderer and renders a boolean test part against its known volume.

Quick start

The working compiler-to-printer toolchain is recorded in pipeline.lock.json, with QOS-style repository revisions and content hashes for local tools, profiles and deployed artifacts. Use python3 scripts/pipeline-lock.py --scope source --scope render --check to detect drift in a plain checkout, or python3 scripts/pipeline-lock.py --scope source --scope render --run coscad doctor to run through the checked entry point. A bare --check (no --scope) is the complete check: it also SSHes to the print host (root@localhost-0) and hashes the local printer profiles, so it only passes on the maintainer's machine. See pins, deliberate upgrades and graceful recovery.

coscad part.coscad           # -> part.scad (an export for OpenSCAD users)
coscad validate part.coscad # source checks, no output files
coscad validate model.assemble # source + rigid constraint checks
coscad validate --json --stdin part.coscad < buffer  # JSON diagnostics for unsaved text
coscad validate --json --project-stdin model.assemble < snapshot.json # recursive editor buffers
coscad capabilities --json # build features, supported backends and materials
coscad stl part.coscad       # -> part.stl (+ part.scad export), prints volume + bounds
coscad frame.assemble        # -> _asm.png view, packed _plate(N).png, frame_design.json (+ .scad exports)
coscad next frame.assemble   # -> frame_bed1.stl ... + frame_manifest.json
coscad check frame.assemble  # interference / clearance check on real meshes
coscad stress bracket.coscad # deflection, peak stress and safety factor from the part's support/load lines
coscad plan frame.assemble   # numbered build steps, preload sheet, BOM, step scenes
coscad calibrate times.json  # fit the plan's cost weights to timed builds (seconds per term)
coscad site out/ *.assemble  # phone-first static site: step images, search across builds
scripts/bambu-slice.py frame_manifest.json [--print 1]  # slice beds with Bambu Studio, optionally print over LAN
scripts/bambu-print-run.py frame.assemble  # photo, model → slice → print, watch, photo, verdict
coscad --help                # command + language cheat sheet

The two Bambu scripts need Bambu Studio; printing over the LAN also needs python3 -m pip install -r scripts/requirements.txt (paho-mqtt) and the bambu-lan scripts (BAMBU_LAN_DIR). The VS Code extension lives in vscode/ (npm install && npm test && npm run package, or take the .vsix attached to each release).

Errors carry file:line:col and quote the offending line — an unknown name, a stray glyph, a circular definition, a duplicate, or a 2D/3D mismatch (extruding a solid, offsetting a cube, unioning a profile with a box) all fail at compile time instead of rendering silently wrong. A kernel failure or an unsupported operation is an error, never a silent fallback. Every diagnostic goes to stderr as Error: file:line:col: reason, optionally followed by an excerpt; exit 1 means the command could not run, exit 2 means the input was fine but the design has a problem (validate/plan design errors, check overlaps, next --strict printability warnings, a part that does not fit the plate in the design stage or next, and, from every command that loads an assembly, mating constraints that contradict each other, parts no fixed root reaches, or an instance fixed twice). A constraint conflict is reported on the line that closed the loop, followed by one note: file:line:col: … line per constraint on the loop. Each kernel operation has a cooperative 60-second deadline.

Two syntaxes, one language

Glyph style and pipeline style are interchangeable and mix freely:

// glyphs
bracket = (plate ⊕ flange) ⊖ χ 10 (zcyl 2.75 12)

// pipelines + word aliases
bracket = plate
  |> add flange
  |> cutat top 2.5 0 -4 (zcyl 2.75 12)

A line starting with |> continues the previous definition.

Feature map

See the shelf bracket integration workflow for editor diagnostics, backend selection and a reproducible stress-to-slice example.

Module map

src/Coscad/Shape.hs     AST + bezier evaluation (pure data)
src/Coscad/Linalg.hs    vectors, 3x3 matrices, rotations, bed orientation
src/Coscad/Geometry.hs  bboxes, anchors, attachment resolve (re-exports Linalg)
src/Coscad/Codegen.hs   .scad export (OpenSCAD source for people who continue in OpenSCAD)
src/Coscad/Dsl.hs       Haskell-embedded glyph DSL
src/Coscad/Parser.hs    .coscad parser facade (glyphs, words, pipelines), over:
  Parser/Core.hs        lexer, numbers, contexts, anchor/frame references, reserved words
  Parser/Grammar.hs     expression grammar: pipelines, booleans, transforms, shapes
  Parser/Program.hs     definitions, variable resolution, program entry points
src/Coscad/Dim.hs       2D/3D dimensionality check
src/Coscad/Validate.hs  shared numeric/quantity checks and suggestions
src/Coscad/Constraint.hs  mate/fixed constraint solver, conflicts, tolerance
src/Coscad/Assemble.hs  .assemble design stage
src/Coscad/Variants.hs  print variants: names, instance ids, orientation, plate packing
src/Coscad/Next.hs      manufacturing stage (orientation, beds, manifest)
src/Coscad/Printability.hs  bed contact, overhang, slenderness, warnings
src/Coscad/Check.hs     assembly interference / clearance checker
src/Coscad/Plan.hs      `coscad plan` CLI and summaries, over:
  Plan/Model.hs         instances, fasteners and joints, requires-before relation, weights, settings
  Plan/Geometry.hs      mesh mass, contacts, approach sweeps, joint alignment and turning, burial order
  Plan/Search.hs        support/holding/access predicates, A*/beam search, steps
  Plan/Render.hs        plan Markdown, JSON, per-step scenes
  Plan/Calibrate.hs     `coscad calibrate`: fit cost weights to timed builds
src/Coscad/Solid.hs     kernel solids kept alive across queries: posed gap and overlap
src/Coscad/Image.hs     cached native scene meshing, CPU depth-buffer rendering and PNG encoding
src/Coscad/Site.hs      `coscad site` static build-instruction pages
src/Coscad/Mesh.hs      ASCII STL in/out, volume, bounds
src/Coscad/Json.hs      minimal JSON encode/decode for manifests
src/Coscad/IO.hs        UTF-8 file IO, scratch directories
src/Coscad/Report.hs    command failures (exit status + lines) and warning emitters
src/Coscad/Format.hs    number formatting (showD)
src/Coscad/Mesh/Distance.hs triangle/edge surface distance with bounding-volume pruning
src/Coscad/Mesh/Indexed.hs  validated indexed mesh contract; connectivity independent of position
src/Coscad/Backend.hs   STL/check/next backend boundary: build-time default, native meshing and CSG
src/Coscad/Loft.hs      lofts tessellated as BOSL2 skin() builds them (reindex/distance, min_edge, caps)
src/Coscad/Manifold.hs  optional pinned C++ geometry kernel bridge
src/Coscad/Stress/Experiment.hs  bounded isotropic Hex8 solver and beam validation
src/Coscad/Stress/Cad.hs  exact rectilinear CAD grid, supports and surface loads
src/Coscad/Stress/Tetra.hs  experimental conforming Tet4/Tet10 solver for curved parts
src/Coscad/Stress/Linear.hs  shared sparse solver and residual checks
src/Coscad/Stress/Spec.hs    support/load/settings directives
src/Coscad/Stress/Material.hs material properties and strength assumptions
src/Coscad/Stress/Hex20.hs   quadratic hexahedra and stress recovery
src/Coscad/Stress/Sparse.hs  CSR matrix and preconditioned solver
src/Coscad/Stress/Voxel.hs   mesh voxelization and connectivity
src/Coscad/Stress/Part.hs    part regions, solve and safety report
src/Coscad/Stress/Command.hs stress CLI and report output
src/Coscad/Part.hs      single-part compile / stl commands
src/Coscad/Stdlib.hs    printable connection recipes (threads, globe joints, captive hardware)
src/Coscad/Diagnose.hs  `coscad validate`, structured diagnostics, --json output
src/Coscad/Doctor.hs    `coscad doctor`
src/Lib.hs              re-export shim for the embedded Haskell DSL
app/Main.hs             CLI dispatch only
experiments/Main.hs     coscad-experiments numerical trial CLI (JSON results)
examples/haskell/       embedded-DSL samples (compile-checked by the haskell-examples suite)

Manual

man coscad (source: man/coscad.1) covers every command, the environment variables, a language cheat sheet, the assembly format, and worked examples. coscad --help is the short form.

Manual site

The generated Orbit Tower instructions walk through building the 20-piece spinning desktop kit, with a render for each step. A separate hand-written guide includes handling and print notes.

scripts/build-manual.sh DIR renders this documentation, the man page, and the example build plans into a static site (needs pandoc and mandoc; plan images are rendered by coscad itself). manual.cswine.cloud rebuilds it from main automatically; see deploy/README.md.

Releasing

  1. Bump version in coscad.cabal and the CHANGELOG, commit, tag v<version>, push the tag: the release workflow runs every test tier (native geometry included, OpenSCAD oracle off), checks the tag against coscad --version, and attaches binaries.
  2. After the release succeeds, run scripts/update-release.py coscad v<version> in the shared homebrew-tap repository. It downloads the archive and verifies the published checksum and build metadata before updating the binary formula.

Camera photos from print runs (models/**/*.jpg, .jpeg, .heic, such as photos-before/ and *-bed-photos/) are stored in Git LFS. Install it once with git lfs install before cloning, or run git lfs pull afterwards. Without LFS those files check out as small pointer files. Nothing in the build or the test suite reads them. Renders and diagrams (*.png) stay as ordinary files.

Docs

docs/README.md indexes every page by date. The ones to start with:

Tests

cabal test                          # all tiers; OpenSCAD oracle checks run if OpenSCAD is installed
COSCAD_OPENSCAD=none cabal test     # as on a machine without OpenSCAD (oracle checks skipped, loudly)
COSCAD_RENDER=0 cabal test          # skip the geometry tier
COSCAD_UPDATE_GOLDEN=1 cabal test   # accept changed .scad / geometry snapshots (goldens come from native renders)

The suite's entry point is test/Spec.hs; the tiers live in test/Tier/*.hs and share test/Harness.hs. The main tiers: diagnostics (error messages carry position and cause), examples (every example compiles, every .assemble loads, emitted .scad matches test/golden/scad/), and geometry (every example renders natively and its mesh volume + bounds match test/golden/geometry.txt, plus cross-checks: the topological bracket equals the coordinate bracket, coscad next conserves volume across bed packing, coscad check passes on bow3), and cli (the built binary is spawned: exit codes, stdout/stderr split, validate, and the manifests parse as JSON). When a snapshot fails, the message shows golden vs current; accept deliberate changes with COSCAD_UPDATE_GOLDEN=1 and review the diff. Geometry entries still within tolerance keep their recorded values, so an update only rewrites the lines that drifted or were missing. OpenSCAD is used only as an independent oracle: with OpenSCAD 2025.05 or newer installed (found via COSCAD_OPENSCAD, then PATH, then the usual install locations, with BOSL2 via COSCAD_BOSL2), the suite also compares every example and a set of primitives, lofts and offsets against OpenSCAD's renders of the emitted .scad. Without it those checks are skipped and the skip is printed. A second, small suite, haskell-examples, only compiles examples/haskell/ and renders one shape from each sample.

Tests generate everything they read. Build output (scenes, manifests, plans, step images) is gitignored; only the print records under models/ and the tesseract verification.json are committed. See Examples for the list.

Verification culture

Every nontrivial part in the examples was validated numerically, not visually: trimesh watertightness + volume/bounds cross-checks, boolean interference tests for fits and fastener paths (expect 0 mm³), and ASCII occupancy rasters for 2D outlines. docs/SKILL.md describes the loop; copy it for new parts.

Printable connection standard library

Parameterized thread pairs, globe stand/socket joints, pin/bore fits and captive axle bearings are built into the native compiler and available in every syntax mode, with no runtime library lookup. See stdlib/README.md for parameter units, coordinate conventions, tolerances and runnable coupons. Haskell users can import Coscad.Stdlib (also re-exported by Lib).