NAME
coscad —
glyph/pipeline CAD language compiler:
.coscad and .assemble to native STL, print beds, build plans and stress
reports
SYNOPSIS
coscad |
part.coscad [-o
part.scad] |
coscad |
validate [--json]
[--stdin |
--project-stdin] part.coscad
| spec.assemble |
coscad |
stl part.coscad
[-o part.stl] |
coscad |
spec.assemble |
coscad |
next [--strict]
spec.assemble |
coscad |
check spec.assemble
[--keep-temp] |
coscad |
plan spec.assemble
[--png] |
coscad |
calibrate times.json |
coscad |
site dir
spec.assemble ... |
coscad |
doctor |
coscad |
capabilities --json |
coscad |
stress part.coscad
[-o report.json]
[--full] |
coscad |
--version | --help |
DESCRIPTION
validate checks a .coscad or .assemble
file, including recursive part sources and rigid mating constraints, without
writing output files or rendering meshes. It is suitable for editor
diagnostics. It does not run interference or instruction-access checks. With
--json it prints one JSON document on standard
output (schema ‘coscad.validate/1’:
‘valid’,
‘exit’, and
‘diagnostics’, each with severity,
file, line, column, message and source excerpt) and nothing on standard
error; the exit status is unchanged. With --stdin
the file's text is read from standard input (an unsaved editor buffer); part
and subassembly references still resolve from the file's directory. With
--project-stdin the input is a JSON snapshot with
schema coscad.project/1 and a files array of objects containing absolute
path and text strings. All supplied buffers are used for recursive
references; other sources come from disk. The response includes canonical
dependency paths, including missing references. The snapshot limit is 256
buffers and 8 Mi characters. Aliases of the same buffer path are rejected.
Neither input mode writes source files.
capabilities --json reports schema
coscad.capabilities/1, compiler version, build default and geometry backend
(‘native’), feature names (including
‘openscad-retired’) and material
names/moduli.
stress renders and analyses the part's
support/load/stress directives. The full report includes node and cell
fields. Exit 2 means the requested safety factor was not met, 1 means
analysis could not run. Validation checks analysis settings and datum
identities without solving the part. coscad meshes
CoScad parts itself (its own tessellation plus the pinned Manifold kernel)
and writes STL, print beds, build plans with rendered step images, and
stress reports. It does not use OpenSCAD or BOSL2; a part can be exported as
OpenSCAD source for people who continue in OpenSCAD. A
.coscad file is a list of definitions, one per line;
the definition named ‘main’ is the
rendered shape. An .assemble file declares
physically separate parts, how many to print, and how they sit together; the
manufacturing stage orients and packs them onto print beds.
Anchors, attachments, bezier outlines, lofts, fit checks, print orientation, and bed packing are all computed in the compiler, and any expression, including composites, can be attached to again. The exported OpenSCAD source stays plain (translate, rotate, union, difference, BOSL2 primitives).
COMMANDS
coscadpart.coscad [-oout.scad]- Export one part as OpenSCAD source, for use in OpenSCAD. Nothing in
coscadreads it. Writes part.scad next to the input unless-ois given. coscadstlpart.coscad [-oout.stl]- Mesh the part natively and write an ASCII STL, then print the triangle
count, volume, and bounding box. The part's OpenSCAD source is written
beside the STL as an export. Geometry comes from
coscad's own tessellation and the pinned Manifold kernel: boxes, polygonal cylinders, spheres, cones, frusta, regular prisms, tubes, wedges, prismoids, chamfered and rounded cuboids and cylinders, ordinary tori, transforms, booleans, hulls, Minkowski sums, extrusions, round 2D offsets of a single outline, and lofts (triangulated the way BOSL2's skin does it). Unsupported operations are errors; there is no fallback.--backend=native(or the earlier--backend=native-experimental) is accepted by every rendering command for older scripts;--backend=openscadnames the backend retired in 1.6.0.0 and is an error. Manufacturing manifests record the geometry backend (‘native’). coscadspec.assemble- Design stage. Renders the assembled view
(spec_asm.png) and the packed plate(s)
(spec_plate.png, or
spec_plate1.png, spec_plate2.png
...) natively, writes the same geometry and one print-oriented
spec_part_<variant>.scad per unique (part,
orientation) as OpenSCAD source exports
(spec_asm.scad, spec_plate.scad
...), and the design manifest
spec_design.json (schema
coscad.design/1). It never writes spec_manifest.json; if it finds a design manifest left there by coscad 1.2 or earlier it prints a note to standard error and leaves the file in place. coscadnext[--strict] spec.assemble- Manufacturing stage. Renders each variant once, chooses a print
orientation where none was declared (scoring bed contact, overhang,
height, and slenderness on the real mesh), packs instances largest-first
across as many beds as needed, and writes
spec_bed<N>.stl plus
spec_manifest.json (schema
coscad.next/1), recording per-variant orientation and printability and per-instance placements for slice-once, stamp-many slicing. Printability warnings go to standard error and to the manifest, each with a code in ‘warning_codes’ (‘small_contact, ‘’). Withtall_narrow, ‘’flat_overhang, ‘’elevated_start’--strict, any warning makesnextexit 2 after the outputs are written. Instances with a brim (see ASSEMBLIES) are packed with the brim reserved around their footprint; each placement records ‘brim_mm’. coscadcheckspec.assemble [--keep-temp]- Interference and clearance check on real meshes. Every part is rendered as
placed by the ‘
asm’ expression; close pairs are intersected exactly by the native kernel. Reports ‘OVERLAP’ with its volume, ‘contact’ for touching faces, and ‘WARN’ for gaps below the declared ‘clearance’. Exit status 1 if any overlap. Scratch renders go to the system temp directory and are removed unless--keep-temp. coscadplanspec.assemble [--png]- Assembly instruction generator. From the
‘
asm’ placement, part hints, and ‘fastener’ and ‘joint’ lines, meshes every part and measures how each can come in (swept against every other part), derives the requires-before graph (T-nuts before the slot they need is covered, host and clamped part before each screw, a part before anything that would block its every way in), checks every step for support and balance on the real contact patches, a clear path and finger room, driver access, nut entry and screw joints turning freely, counts the hands each step needs, and searches rest-face phases with weighted cost terms so the build needs few flips and few extra hands. Writes spec_plan.md (bill of materials, T-nut preload sheet, numbered steps with torque per host material), spec_plan.json, and one spec_stepN.scad scene per step (this step orange, earlier parts grey, screw heads red, a grey bench slab);--pngalso renders them to 1200 by 900 PNGs with a built-in CPU renderer: native meshes, cached across steps, orthographic framing, depth testing, lighting and antialiasing. No GPU or display is needed. The screw-head proxies, part highlights and rest-face bench are drawn too. The step scenes are also written as OpenSCAD source exports. A failed render fails the command instead of publishing a successful plan with missing images. Design errors (colliding screws, too little thread reach, a clamp not on its host face) are reported and exit 2. See ASSEMBLIES. coscadcalibratetimes.json- Fit the
plancost weights to timed builds. times.json lists plan JSON files and the seconds each step took (‘{"builds": [{"plan": x_plan.json, step_seconds: [40, null, ...]}]}’); the weights that best explain the times (non-negative least squares on each step's cost terms) are printed as a ‘plan’ line, in seconds. Terms no timed step used keep their defaults. coscadsitedir spec.assemble ...- Companion site: runs
planwith renders for every assembly and writes a static, phone-first site under dir: one page per build (step cards with images, rest face, parts, T-nut preloads, screws with torque, warnings; prev/next navigation, filter within the build) and an index that searches across builds by name, part, material, or screw spec. Plain HTML, no server needed: open dir/index.html from a file or host the folder anywhere. coscaddoctor- Report the linked geometry kernel and the image renderer, and render a boolean test part natively against its known volume. Exit status 1 if the round trip disagrees.
--version,--help- Print the version, or the command list with a language cheat sheet.
ENVIRONMENT
coscad reads no environment variables for
rendering: geometry and images need nothing outside the binary. The test
suite (not the installed command) uses
COSCAD_OPENSCAD and
COSCAD_BOSL2 to find OpenSCAD and BOSL2 for its
optional oracle checks.
LANGUAGE
One definition per line, ‘name =
expression’. Definitions resolve by dependency, not order.
Comments start with ‘//’. A line
beginning with ‘|>’ continues the
previous definition. The first code line may select a vocabulary:
‘!glyph’ (glyph operators),
‘!simple’ (ASCII words such as
‘Box’,
‘Translate.x’,
‘Hull’,
‘*’ for union and
‘-’ for difference), or neither, which
accepts both.
Numbers
A definition whose right-hand side is arithmetic is a numeric
binding. Numbers may reference numbers, in any order, and are usable
wherever a number goes. Argument positions take one atom (a literal, a name,
‘-name, or a parenthesized
expression’) so juxtaposed arguments stay unambiguous:
w = 20 t = w / 5 - 1 main = box w (w / 2) t |> cutat top 0 0 (-t / 2) (zcyl 1 50)
Shapes
| syntax | shape |
| ■ s | cube, corner at the origin (OpenSCAD) |
| ▬ x y z | box, corner at the origin |
| ● r | sphere |
| ◎ r h | cylinder, z from 0 to h |
| ▻ r h | cone |
| ⎏ n r h | n-sided prism |
| cube s / box x y z | centered cuboid (BOSL2) |
| sphere r / cyl r h | centered sphere, cylinder |
| xcyl r l / ycyl / zcyl | axis-aligned centered cylinders |
| tube or ir h | centered tube |
| torus R r | torus |
| wedge x y z | right-triangular prism, profile in the YZ plane |
| ▣ x y z c | cuboid, chamfer c |
| ◙ x y z r | cuboid, rounding r (< half the smallest dimension) |
| ⌭ r h c / ⌽ r h r2 | cylinder with chamfered / rounded ends |
| ⊚ or ir h | tube |
| ⏢ x1 y1 x2 y2 h | prismoid |
| ◉ R r | torus |
| ⊿ x y z | wedge |
2D profiles
‘△ r’,
‘⬠ r’,
‘⭘ r’ (triangle, pentagon,
circle) and ‘✎ x0 y0 x1 y1 ...’
(a closed piecewise-cubic bezier outline, 3k+1 control points, evaluated by
the compiler). Extrude with ‘⮕ h
profile’ or ‘|> extrude
h’; revolve with ‘⟲ deg
profile’ or ‘|> revolve
deg’, with an optional segment count
‘⟲ deg segs profile’; the
profile's y becomes height and only the +X half is kept. The
‘.scad’ export intersects a crossing
profile with its +X square first, since OpenSCAD refuses crossing profiles.
Offset with ‘profile ↯ ●
r’. A profile used where a solid is expected, or the reverse,
is a compile error.
Lofts
‘loft z0 p0 z1 p1 ...’
(glyph ‘⟰’,
‘Loft’ in !simple) skins 2D profiles
at ascending z into one solid, triangulated the way BOSL2's
skin()
does it. Profiles may differ in vertex count. ‘p0
|> loft z1 p1’ starts a loft with
‘p0’ at z = 0; further
‘|> loft z p’ stages append.
Profiles must be single closed outlines: the 2D primitives, beziers, and
their in-plane transforms (χ ψ ω ⬈ ⇋
↯); booleans between profiles, ζ, θ, and ϕ are
rejected.
Booleans
‘⊕’ union,
‘⊖’ difference (left minus
right), ‘∩’ intersection,
‘⇓’ hull,
‘⊞’ minkowski.
Left-associative, one precedence level.
Transforms
‘χ ψ ζ
d’ translate along x/y/z; ‘θ
ϕ ω deg’ rotate about x/y/z (about the origin);
‘⬈ sx sy sz’ scale;
‘⇋ nx ny nz’ mirror;
‘⚓ anchor’ re-origin a shape at
one of its own anchors. A transform applies to the next primary expression;
use parentheses or ‘$’
(‘χ 5 $ a ⊕ b’) for a
compound argument.
Pipelines and anchors
‘a |> stage |> stage
...’ applies postfix stages: ‘x y z
d’, ‘move dx dy dz’,
‘rotx roty rotz deg’,
‘scale’,
‘mirror’,
‘extrude h’,
‘revolve deg [segs]’,
‘loft z p’,
‘anchor v’,
‘add cut isect hull mink shape’, and
the relational stages ‘at v [dx dy dz]
child’ (union; the child's opposite anchor meets anchor v),
‘on v [dx dy dz] child’ (union; child
rotated so +Z points along v, bottom mated there), and
‘cutat v [dx dy dz] cutter’
(difference; the cutter is centered at the anchor). Anchors are bounding-box
points of the shape so far: ‘top bot lft rt fwd bak
ctr’, combinable as
‘top+rt’ or
‘lft+fwd’. Glyph forms:
‘a ⌖ v b’ positions,
‘a ⋈ v b’ attaches.
ASSEMBLIES
plate 250 250 6 180 brim=8 // bed width depth [margin [max height]] [brim=N|auto] clearance 0.15 // minimum gap for `coscad check` tolerance 0.00001 // loop closure: mm [rotation] (defaults 1e-6, 1e-8) gap = 2 // numbers work here too center ← center.coscad ×1 larch ← l_arch.coscad ×1 ▽top seam=rear rail ← rail.coscad ×0 // ×0: premade, assembly-only corner ← corner.assemble ×2 // recursive subassembly asm = center ⊕ (larch |> move (55 + gap) -13 -7) ⊕ rail
‘▽anchor’ declares
which face goes on the bed; undeclared parts get the automatic orientation
search in next.
‘key=value’ entries are free hints
carried into the manifest. The optional 4th
‘plate’ argument is the tallest part
the printer builds in mm (default 250; 180 for an A1 mini).
next never stands a part on a face taller than that,
and exits 2 when a declared ‘▽’
or every face is too tall; the design stage only warns. The packer turns
parts 90° about Z when that saves a shelf or a plate (recorded as
‘rot_z’ in each placement) and fills
gaps with later, smaller parts before it opens another plate.
‘fixed’ poses and the
‘roll=’ /
‘gap=’ options of
‘mate’ take numeric atoms: a literal,
a numeric binding, ‘-w’, or
‘(w / 2)’.
‘tolerance mm [rotation]’ sets how far
a mating loop may miss and still close. A frame inside a constrained
subassembly is addressed through instance aliases:
‘mate l @a s @q.b’.
‘brim=N’ reserves an N mm brim around
each instance when packing; ‘plate …
brim=N’ reserves it in next for parts
whose printability recommends a brim (small first layer, tall and narrow);
‘brim=auto’ reserves 5 mm. A part's
own ‘brim=0’ opts out. For
plan, the hints
‘profile=2020’ (a T-slot rail: four
slot faces, ends open unless
‘ends=open,blocked’),
‘material=aluminium|printed|steel’
(torque class; printed parts default to printed, premade
‘×0’ parts to aluminium),
‘torque=Nm’, and
‘mass=g’ describe the parts, and one
line per screw group describes the joints:
fastener M5x10 br#1 rx#1 bot at=30 // spec clamped host face fastener M5x10 px ry#2 rt ×2 // two screws along the overlap fastener M5x10 br1 railA top through=6 // counterbored: 6 mm under the head fastener M3x10 corner#1 bar#1 top nut=none // threads into the printed bar fastener peg strut#1 corner#1 rt // friction joint, no screw plan beam=30 rest=bot,top // optional search settings
Instance names take ‘#n’
when a part is placed more than once. The part hint
‘after=instance[,instance...]’
requires those instances to be placed first (for example, before a cap
covers a rotor's entry). Unknown, ambiguous, and cyclic references are
errors. Options: ‘×n’,
‘at=mm[,mm]’ (from the host's marked
end), ‘nut=dropin|slidein|hex|none’,
‘pocket=face’ (hex nuts: the host face
the nut pocket opens on;
‘pocket=clamped.face’ for a pocket in
the clamped part),
‘head=button|socket’,
‘through=mm’,
‘inset=mm’ (peg joints only: seating
plane this far inward from the named host face),
‘torque=Nm’. The
‘plan’ line takes
‘beam=N’ (search width, default 30),
‘rest=faces’, and the cost weights
‘flip vertical driver shrink sibling
loose’.
For a screw located by model frames, replace the host face with
‘@hole’ and use
‘seat=@head_seat’ on the clamped part.
The hole and head frames must be coaxial, with matching outward normals.
With ‘nut=hex’,
‘pocket=@nut_seat’ names the host nut
bearing seat (or
‘pocket=clamped.@nut_seat’ for a seat
in the clamped part), with its normal opposing the head. Thread reach is
computed from the distance between bearing seats. Frame-based joints take
one screw per declaration, without
‘at=’,
‘through=’, or
‘inset=’.
The part hint ‘preload=hand’
loads nuts into the independent part before placing it. The default
‘preload=placed’ loads nuts after
placement and checks access against the bench and other parts.
EXIT STATUS
coscad exits 0 on success and 1 when a
command could not run: a usage error, a missing file, a syntax or geometry
error, or a kernel failure. It exits 2 when the input is well formed but the
design has a problem, so a pipeline can tell "fix the model" from
"fix the setup": validate and
plan with design errors (colliding screws, too
little thread reach, a clamp off its host face),
check when any overlap is found, and
next --strict when any part
has a printability warning, and the design stage and
next when a part does not fit the plate (its
footprint either way round, or, in next, its height
over the plate's max height). Every command that loads an assembly
(validate, the design stage,
next, plan,
check, site) also exits 2
when its mating constraints contradict each other (a loop that does not
close, or two fixed roots that disagree), when a part is reached by no fixed
root, or when an instance is fixed twice. Unknown instances or frames,
syntax errors and non-finite values in constraints still exit 1.
doctor exits 1 when the native round trip fails.
Every diagnostic goes to standard error as
‘Error: file:line:col: reason’,
optionally followed by a source excerpt with a caret; design errors from
plan read ‘Error:
file.assemble: design: reason’. A constraint conflict is
reported at the constraint declared last on the loop and is followed by one
‘ note: file:line:col: constraint’
line for every constraint on the loop, any of which may be the wrong one; a
floating part is reported at its declaration. Progress and results go to
standard output.
EXAMPLES
A bracket built relationally, then rendered:
// bracket.coscad plate = box 5 15 20 flange = box 16 15 5 main = plate |> at top 5.5 0 -5 flange |> cutat lft 3.5 0 0 (xcyl 2.7 12) // M5 clearance |> cutat top 2.5 0 -4 (zcyl 1.4 10) // M3 pilot $ coscad stl bracket.coscad Wrote bracket.scad Wrote bracket.stl: 664 triangles, volume 2638.931 mm^3, bounds ...
A funnel from numeric bindings and lofts:
r_top = 20 r_bot = 8 h = 40 wall = 2 outer = loft 0 (⭘ r_bot) h (⭘ r_top) inner = loft -1 (⭘ (r_bot - wall)) (h + 1) (⭘ (r_top - wall)) main = outer ⊖ inner
Generate build instructions for a 2020 frame:
$ coscad plan cube.assemble --png plan: 29 part instances, 38 fasteners on 12 hosts Wrote cube_plan.md (29 steps, 4 flips, cost 183) Wrote cube_step1..29.scad and .png
Manufacture a three-part assembly and check its fits:
$ coscad next bow3.assemble Wrote bow3_bed1.stl (3 parts) Wrote bow3_manifest.json $ coscad check bow3.assemble check: 3 parts, clearance = 0.0mm contact center x larch contact center x rarch check: 0 overlaps, 0 clearance warnings
Check the binary on a new machine:
$ coscad doctor geometry native CoScad tessellation + Manifold 3.5.2 kernel, linked statically (pinned by native-kernel.lock.json, with CrossSection) images native built-in CPU renderer for plan --png and site (no display or GPU needed) external none OpenSCAD and BOSL2 are not used; `coscad part.coscad` still exports .scad for OpenSCAD users trip native ok box 10 10 10 ⊖ zcyl 2 20 -> 874.67 mm^3 (expected 874.67), 216 triangles all good
DIAGNOSTICS
Errors carry ‘file:line:col’
and quote the offending line. Distinguished cases: syntax errors, undefined
names, circular dependencies (only the cycle is listed), duplicate
definitions, a number used as a shape or a shape as a number, 2D/3D
mismatches (extruding a solid, offsetting a solid, combining a profile with
a solid, a 2D ‘main’), and invalid
loft profiles. A kernel failure or an unsupported operation is an error,
never a silent fallback.
SEE ALSO
The full language reference, the topological-modelling guide, and the manufacturing guide ship as LANGEXTENSION.md, TOPOLOGICAL.md, and MANUFACTURING.md under the documentation directory (share/doc/coscad with Homebrew). The exported .scad files open in OpenSCAD with the BOSL2 library: https://openscad.org and https://github.com/BelfrySCAD/BOSL2.
AUTHORS
Jasen Qin