Changelog · CoScad
Changelog for coscad
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to the Haskell Package Versioning Policy.
Unreleased
cabal buildis the whole build. The pinned geometry kernel (Manifold 3.5.2 and its CrossSection dependency Clipper2 2.0.1,native-kernel.lock.json) is now vendored underthird-party/and compiled into thecoscadlibrary bycxx-sources, soscripts/build-manifold.py, CMake, pkg-config,PKG_CONFIG_PATHand the.native/prefix are gone and nothing is downloaded during a build. The-Dflags incoscad.cabalmirror the lock's old CMake configuration (MANIFOLD_CROSS_SECTION,MANIFOLD_PAR=-1,CLIPPER2_MAX_DECIMAL_PRECISION=8),third-party/manifold/include/manifold/version.his the generated header checked in instead, andcabal-versionrises to 2.4 so the vendored trees can be described with file globs (older glob syntax silently dropped dotted names such asclipper.core.hfromsdist). Upgrading needs GHC 9.8 and a C++17 compiler.- Surfaces of revolution.
⟲ angle profile(pipeline|> revolve angle,Revolvein!simple) revolves a 2D profile about the Y axis through the pinned Manifold kernel; profile y becomes height and only the +X half is kept, matching OpenSCAD'srotate_extrude. An optional second number sets the segment count (⟲ 360 64 p,|> revolve 90 64); without it the kernel default applies. Union, difference and intersection distribute over the revolve; hull and minkowski under a revolve are refused with a fix. The.scadexport emitsrotate_extrude, intersecting crossing profiles with their +X square first (OpenSCAD refuses crossing profiles). coscad planworks on the meshes. Mass and centre of mass, bench footprints and part-on-part contact patches come from the native meshes, not bounding boxes. Every part's ways in (a 26-direction lattice plus its joint, mate-frame and own axes) are swept against every other part, so a path that is blocked, too tight for fingers, or comes up through the bench is refused, and a part that would bury another's last way in is ordered after it (capafter=hints are no longer needed). See docs/2026-10-29-PLAN-EASE.md.joint push|drop|screw moving host [pitch=] [axis=]declares how a printed joint is made, its axis taken from the mate. Screw joints count turns (engaged length over pitch) and must spin clear of placed parts and leave room to grip; drop joints hold only by gravity or capture.- Ease of assembly. Joint alignment costs its Fitts
index (engaged length over play); the planner counts the hands each move
and each flip needs (a part is held by a made joint, a stable rest or
capture; anything else needs a hand) and charges per hand beyond two;
finger room is costed. New weights
insert turn hand finger part fastener nut; the thresholds behind the old weights are plan settings (driver_angle,standing_angle,slender,driver_length,driver_margin,phase_tau,step_parts,finger_size,contact,settle,sweep_step,shell,infill). coscad calibrate times.jsonfits the weights to timed builds (non-negative least squares on each step's cost terms) and prints aplanline in seconds. Plan JSON records each step's terms and cost, the direction each part goes in, part masses, and joint kinds and turns.- Tesseract III's build instructions are generated with
jointlines and no ordering hints. The old bolted tesseract is now reported as unbuildable from rigid parts (its box-based plan ended by placing the inner cube inside the closed frame). capabilities --jsonaddsplan-geometry,plan-jointsandcalibrate.
1.6.0.0 - 2026-10-29
1.5.0.0 was recorded here but never tagged or published, so this is the first release to carry its changes (native planner and site images, mandatory print preflight, VS Code 0.6.0) as well as the ones below.
- OpenSCAD retired. Geometry is always native:
CoScad's tessellation plus the pinned Manifold kernel, which every build
now links (the Cabal
manifoldflag and the OpenSCAD-only-f-manifoldbuild are gone).--backend=openscadis an error that points at the.scadexport;--backend=nativeand--backend=native-experimentalare accepted as no-ops. Manifests and stress reports record the backend asnative(previouslynative-experimental).capabilities --jsonlists onlynativeand adds theopenscad-retiredfeature. See docs/2026-10-29-OPENSCAD-RETIRED.md. coscad doctorreports the linked kernel and the image renderer and runs a native round trip; it no longer probes OpenSCAD or BOSL2. The compiler reads noCOSCAD_OPENSCAD,COSCAD_BOSL2orCOSCAD_OPENSCAD_TIMEOUT.- The design stage (
coscad spec.assemble) rendersspec_asm.pngandspec_plate.png(orspec_plateN.png) natively, so its output is viewable without OpenSCAD. The.scadfiles it writes remain as exports. checkintersects close pairs with the kernel only; the OpenSCAD polyhedron export, far-away marker and CGAL operand-order retry are removed.- OpenSCAD is the test suite's oracle only. The
runner moved to
test/Oracle/OpenScad.hs. The geometry tier renders every example natively againsttest/golden/geometry.txt(goldens are now recorded from native renders) and, when OpenSCAD 2025.05+ is installed, also checks OpenSCAD's renders and the independent probes. Without OpenSCAD, or withCOSCAD_OPENSCAD=none, those checks are skipped and say so.test/golden/native-supported.txtis removed: every golden must render natively. - CI replaces the OpenSCAD-only job with a no-OpenSCAD job that runs
the whole suite with the oracle off; the release workflow runs every
tier, geometry included.
pipeline.lock.jsonmoves the OpenSCAD and BOSL2 pins to thetestscope with unchanged hashes. - Model build, render and verify scripts, the manual build and the pipeline comparison script no longer call OpenSCAD.
- VS Code 0.7.0: refuses an
openscadbackend setting on a 1.6 compiler with the reason, mapsnative-experimentaltonative, renames "Compile to OpenSCAD" to "Export OpenSCAD Source", and fixes an intermittentEPIPEin its language-server test that turned CI red.
1.5.0.0 - 2026-10-28
- Native planner and site images. Kernel builds now
render
plan --pngandsitethrough native meshes and a built-in CPU renderer, without launching OpenSCAD or requiring a GPU/display. Cache meshes across steps; retain highlights, rest orientation, screw-head markers and bench geometry. Add orthographic framing, depth testing, lighting, antialiasing and PNG output.--backend=openscadremains an explicit alternative. Failed image generation fails the command instead of silently publishing missing images. - Mandatory print preflight. Check the actual sliced plate type and bed/nozzle commands against intended presets before every Bambu dispatch. Validate first/subsequent-layer temperatures and uploaded content hashes; refuse mismatches, missing or ambiguous data and stale validation reports.
- Add threaded tesseract and printed 2020 shelf prototypes, fit-test layouts, revised brim/adhesion trials and owner-reported physical outcomes. Preserve evidence of the incorrect 35°C slices and corrected 55°C P1S retry.
- VS Code 0.6.0. Honor image backend selection and refuse unsupported selection on older compilers. Avoid false analysis-directive errors from stale compilers and explain how to update them.
- Pin PNG dependencies and add renderer/CLI/site regression coverage. Validate 67 PNGs across six existing builds with OpenSCAD forbidden, including the cube and bolted tesseract.
1.4.0.0 - 2026-10-28
Release the native geometry and stress workflows as prebuilt macOS ARM64 and Linux x86_64 archives. Include pinned native-kernel license notices, build provenance and checksums. The shared Homebrew tap installs the macOS archive without GHC, Cabal or a source checkout.
coscad stress part.coscad [-o part_stress.json] [--full]. A part file may now carrysupport,loadandstresslines; the command renders the part with the selected geometry backend, voxelizes it, solves small-strain elasticity with 20-node hexahedra in Haskell and reports deflection, peak stress, across-layer tension and safety factors against a material table. Exit 2 when the requested safety factor is not met. No Python or Gmsh is needed;scripts/mesh-fea-tetra.pyis removed andcoscad-experiments voxel-meshwrites tetrahedral meshes for the earlier Tet10 experiments. See docs/2026-10-27-STRESS-COMMAND.md.The native geometry backend is the default. In a build with the pinned kernel (the Cabal flag
manifold, now on by default),coscad stl,coscad checkandcoscad nextrender through the Haskell tessellation and Manifold 3.5.2 without launching OpenSCAD;--backend=openscadopts out per command, and--backend=nativeis accepted as an alias ofnative-experimental.Coscad.BackendexportsdefaultBackend(native with the kernel, OpenSCAD without);--helpandcoscad doctorname the default of the binary at hand, and doctor reports the linked kernel version and renders its test part through every available backend. A build made with-f-manifoldis the OpenSCAD-only build of before and refuses--backend=native. OpenSCAD remains required for the opt-out, forplan --pngandsitestep images, and for the test suite's geometry tier.Native coverage: 2D offsets and lofts. The
↯operator renders natively through Manifold's CrossSection (Clipper2, the library OpenSCAD itself uses;native-kernel.lock.jsonnow pins Clipper2 at Manifold's own revision andscripts/build-manifold.pybuilds it in tree withMANIFOLD_CROSS_SECTION=ON, still without downloading during the kernel build). Lofts are tessellated by the newCoscad.Loftexactly as BOSL2'sskin()builds them: the same profile vertex order, the samereindexanddistancecorrespondences, themin_edgequad split and the orientation rule;examples/loft.coscadmatches OpenSCAD's volume to the third decimal. Every tracked example (75) now renders natively and is listed intest/golden/native-supported.txt. An offset inside a loft profile, an offset of a composite 2D shape and nested offsets are still explicit errors, never a fallback.OpenSCAD STL renders use OpenSCAD's Manifold backend when the installed OpenSCAD understands
--backend(2025.05 or newer), as the planner images already did. This removes the CGAL assertion onmodels/beach-outrigger/beach-outrigger.coscadand makes the opt-out path 15 to 25 times faster on boolean-heavy parts.openscadStlArgstakes the flag;openscadStlArgsIOprobes once per call;renderStlFileWithlets a caller pick CGAL explicitly, which the test suite's exact symmetric-difference probes do (OpenSCAD's multithreaded Manifold backend is not run-to-run stable on exactly coincident faces).Build and CI.
ci.ymlandrelease.ymlbuild and cache the pinned kernel withscripts/build-manifold.pybeforecabal build; one CI job keeps building with-f-manifold. The Homebrew formula is unchanged and will need the kernel build step (python3 scripts/build-manifold.pyplusPKG_CONFIG_PATH) or-f-manifoldbefore the next release.
1.3.0.0 - 2026-10-21
coscad validate --jsonand--stdin.--jsonprints one JSON document (coscad.validate/1:valid,exit, anddiagnosticswith severity, file, line, column, message and excerpt) instead of stderr text; the exit status is unchanged.--stdinvalidates the file's text from standard input (an unsaved editor buffer); references still resolve from the file's directory. The structuring lives inCoscad.Diagnose, so tools stop re-parsing compiler output.loadAssemblyTextloads an assembly from supplied text.Library commands return their failures. No module under
src/exits the process or prints diagnostics any more. Commands (processAssemble,processNextWith,processPlan,processCheck*,processSite,compilePart,renderPart,runDoctor) returnIO (Either Failure ...), where aFailurecarries the exit status and the exact lines to print, and take anEmitcallback for warnings and notes (Coscad.Report:stderrEmit,silent,collecting).loadAssemblytakes the emitter too.app/Main.hsprints and exits. CLI messages and exit statuses are unchanged, except thatplan'snote:/warning:lines are nowNote:/Warning:like every other command, andplandesign errors print after its progress lines. A test forbids exits, and stderr writes outsideCoscad.Report, in library code.Anchor warnings point at the call and reach the editor. Attachment nodes now carry the line and column of their keyword or glyph, so a floating anchor is reported as
Warning: file:line:col: 'at top' ..., also inside other definitions, on continuation lines and in an assembly'sasm.coscad validateprints these warnings for parts too (exit status stays 0), which is what the editor runs on save.Silent cases now reported.
- A printed part whose
×ndiffers from its placements inasmwarns at its declaration:nextprints n copies whileplanandchecksee the placements. It is a warning, since printing spares while showing one copy is deliberate in some assemblies.×0parts and subassembly placements are unaffected. - An anchor that does not sit on its geometry warns when the part is
compiled:
atoronon a face a cut removed entirely, and any anchor on a part turned by a non-right angle (see docs/2026-07-24-TOPOLOGICAL.md). k = 2 ^ 3(and**) now says^is not an arithmetic operator instead of "expecting a shape". A boolean operator after a pipeline (a |> move 1 2 3 ⊕ b) now asks for parentheses instead of "expecting |> or end of input".
- A printed part whose
Print photos in Git LFS. Camera photos under
models/(27 files, 2.8 MB) are now stored in Git LFS (.gitattributes); renders and diagrams stay in git. History is not rewritten: earlier commits still hold the original images. Rungit lfs install(orgit lfs pullafter cloning) to get the photos rather than pointer files.Repo hygiene: generated files are no longer committed.
- 191 generated files were removed from git (they stay on disk). These
are example scenes (
_asm,_plate,_part_,_next_,_step*.scad/.png),_design.json,_plan.json/_plan.md, and the example manifests, including the 9 that had drifted from what the current compiler writes..gitignorenow covers them, and the tesseract.gitignorethat re-included its step scenes is gone. - Still committed: every print record under
models/(thenextmanifest, the_next_part scenes,print-run.json,validation.json), the orbit-tower manifest, the tesseract adhesion-retryverification.json, the greaser reference manifest, and the hero/photo images. - The plan tests for the ball and the bolted tesseract now generate
_plan.mdin a scratch copy instead of reading a committed one. - The design PDF moved to
docs/design/assembly-instruction-generator.pdf. openscad-stl.shmoved toscripts/openscad-stl.sh.- Removed
zip-project.sh(usegit archive), the unused defaultSetup.hs, andexamples/archive/debug*/test_*scratch files. - New
haskell-examplestest-suite keepsexamples/haskell/compiling. examples/assemble/tesseract/requirements.txtpins exact versions.- New
.gitattributeskeeps macOS._*files out ofgit archive. - The README says a bare
pipeline-lock.py --checkalso SSHes to the print host. In a plain checkout, use--scope source --scope render.
- 191 generated files were removed from git (they stay on disk). These
are example scenes (
Docs.
docs/2026-08-17-SYNTAX.mdsaidcoscad checkexits 1 on an overlap; it exits 2.- Dropped the stale "
.assemblefiles always resolve in legacy mode". - The README and
--helpexit-status text now lists every exit-2 case the man page lists:next --strict, and a part that does not fit the plate. - The README module map lists every module.
- CI runs
scripts/check-docs.py, which fails when the README, man page and--helpdisagree on the command list or the exit-2 cases, when known stale text comes back, or when a module is missing from the map.
Faster name resolution. Definitions still resolve in rounds, dependencies first, but a pending definition is parsed again only when a name in its text was resolved in the previous round. Before, every pending definition was re-parsed each round. A 1200-definition chain now validates in about 0.2 s instead of 6.5 s; a 400-definition chain takes 0.04 s instead of 0.7 s. That helps validate-on-save in VS Code. Results and error messages are the same, including which error is reported when several apply.
Internal: the test suite is split by tier.
test/Spec.hs(2284 lines) is now the entry point. The tiers live intest/Tier/*.hs, andtest/Harness.hsholds the shared helpers. The tests themselves are unchanged.Internal: export lists and a split planner. Every module now has an explicit export list.
Coscad.Plankeeps its API and sits overCoscad.Plan.Model,Coscad.Plan.SearchandCoscad.Plan.Render. The embedded-DSL surface (Lib) is unchanged. No behaviour change.Internal: zero GHC warnings,
-Werrorin CI. The library, CLI and test suite build without warnings under the package's-Wallset. The fixes: signatures for the embedded DSL and Codegen helpers,_ <-for discarded parser results, renamed shadowing bindings, and total code instead ofhead/tailincoscad plan. CI writescabal.project.localwith-Werror, so new warnings fail CI. A user'scabal buildis unaffected, so a newer GHC that adds warnings will not break it. No behaviour change.Internal:
Coscad.Variants. Variant naming, instance ids, bed orientation and plate packing moved out ofCoscad.AssembleintoCoscad.Variants, shared by the design stage andcoscad next. Both stages now look variants up in aMapkeyed by (source, orientation) instead of withheadover lists; an impossible miss is reported as an internal error (exit 1) instead of crashing. No behaviour change: every generated file is byte-identical.Frames through nested subassemblies (additive). A constraint-solved assembly now tags each placed instance with its alias, so a parent can pick one copy of a frame with a dotted path:
mate l @a s @q.b, or@s.q.bone level further up. An unqualified frame present in several instances is still ambiguous, and the error now lists the qualified choices (@p.b, @q.b); an unknown frame lists the instance aliases. The generated.scadis unchanged (tags emit nothing).Numeric expressions and a tolerance for assembly constraints (additive).
fixedposes and theroll=/gap=options ofmatenow take a numeric atom, as.coscadarguments do: a literal, a numeric binding of the.assemblefile (defined anywhere in it),-atom, or parenthesized arithmetic, e.g.fixed l2 w (w / 2) 0 0 0 -45orroll=(a / 2). Existing files parse as before.tolerance <mm> [<rotation>]sets how far a mating loop may miss and still close; the defaults stay 1e-6 mm and 1e-8. A loop that does not close now reports the tolerance next to its residuals.- Errors in these values are input errors (exit 1) at their token: an
undefined name, a part name used as a number, a non-finite result,
arithmetic outside parentheses.
roll=abcused to say "expected a finite angle in degrees"; it now saysundefined variable 'abc'.
Internal: one linear-algebra module.
Coscad.Linalgnow holds the vector and matrix helpers, the rotations and the bed-orientation helpers (downRot,downMat) that were copied acrossGeometry,Next,Plan,AssembleandCheck. The design stage's variants andcoscad next's chosen orientation are records instead of tuples. There is no behaviour change: generated.scad, STL files and manifests are byte-identical.Bed height and packing (bed layouts change).
plate W D margin maxZ: an optional 4th argument gives the printer's build height in mm. It defaults to 250, the valuecoscad nextalways used, so existing assemblies are unchanged; an A1 mini isplate 180 180 6 180. Both manifests record it (bed.max_zincoscad.next/1; the design manifest'splategainsmax_z).coscad nextrefuses a part that is taller thanmaxZin its declared▽, or on every face, with exit 2 (a design problem) and a message naming the part, its height, the limit and the faces it would fit on. It used to exit 1 with "declared ▽… exceeds bed height". An undeclared part still lies on the best face that fits.- The design stage warns (exit 0) when a part's bounding box is taller
than
maxZ, since it doesn't render meshes. - A footprint bigger than the plate either way round is now a design
error too: exit 2 instead of 1, from both stages, and
the message starts with the
.assemblepath. - The packer is now first-fit decreasing over shelves. A bed stays
open until every remaining part has been tried, so smaller, later parts
fill the gaps, and a part is turned 90° about Z when that fits it on an
existing shelf or a new one. A part that fits the plate only turned is
turned instead of refused. Layouts the old packer filled without gaps
are unchanged. Every placement in
coscad.next/1recordsrot_z(0 or 90) withw/das placed, and the bed STL carries the turned part. Design placements carryrot_zonly when it is 90. Readers that ignorerot_zstill get correct bounding boxes fromx,y,w,d. examples-next/enclosure2x2now packs on 4 beds instead of 5 (the brackets fill the two panel beds). The committed example manifest is regenerated; it was also stale from beforeschemaandprintability.bambu-slice.pywarns when the manifest'sbed.max_z, or a part on the selected beds, is taller than the printer'sprintable_height.
Printability you can act on.
coscad next --strictexits 2 when any part has a printability warning. It still writes the beds and manifest so you can inspect them. Without--strict,nextbehaves as before.- Each manifest variant's
printabilitygainswarning_codes, one per warning:small_contact,tall_narrow,flat_overhangorelevated_start. scripts/bambu-slice.pyprints the warnings for each bed and records them in<base>_print.json(printability, andprintability_warningsper bed).bambu-slice.pyrefuses to slice (exit 2) when a warning isn't addressed by the process. Small contact and tall/narrow need an explicit brim (brim_typeouter/inner andbrim_width> 0; auto brim doesn't count). Flat overhang and elevated starts needenable_support.--accept-printabilityslices anyway and records it.bambu-slice.py --bed Nwith no such bed is now an error instead of an empty_print.json.
Brim reservation (packing positions change when a brim is reserved).
- A part hint
brim=Nmakes the packer grow that part's footprint by N mm on every side, so the margin is kept outside the brim. plate W D [margin] brim=N(orbrim=autofor 5 mm) does the same incoscad nextfor parts whose printability recommends a brim. A part's ownbrim=Nwins, andbrim=0opts out. The design stage applies the hints only.- Each
coscad nextplacement recordsbrim_mm, andbed.brim_mmrecords the plate setting. Design placements carrybrim_mmonly when it is non-zero. - Without any brim settings, packing is unchanged.
bambu-slice.pyprints the reserved width as an outer brim when the process overrides don't set one, and warns when the brim it prints is wider than a part's reservation plus half the margin.
- A part hint
The tesseract adhesion retry declares
brim=8on its parts instead of a hand-raised 12 mm plate margin and a brim inprocess-overrides.json. Its supports, speeds and cooling stay explicit overrides.bambu-slice.pyremoves its temporary preset and slice directories on every failure path.scripts/test-bambu-slice.pynow runs in CI.coscad nextprintability warnings: a new flat overhang warning for down faces that are neither bridged nor within 2 mm of a supporting wall (a mushroom cap, a hook arm; an upside-down cup's ceiling is a bridge and does not warn). Tall and narrow now uses the narrowest footprint width (rotating calipers) instead of the bounding box, with a limit of 2.5 + 10/h, so a 2 mm wall turned 45° warns and the 7×7×18.8 fit-test bar no longer does. Small contact looks at the first layer (down faces within 0.3 mm of the bed, projected) against max(25 mm², 0.05·h²), so lying rods no longer warn. The manifest'sprintabilitygainsoverhang_mm2,first_layer_mm2andmin_width_mm; thresholds are inPrintabilityConfig.coscad next: bed contact has one definition, shared by the orientation score and the manifest'sprintability.bed_contact_mm2(down-facing triangles with every vertex within 0.01 mm of the bed, or 1e-5 of the part size above a metre). The score used to count faces up to 0.3 mm above the bed as contact and the manifest used an exact 1e-5 mm plane, which missed quantised vertices on rotated faces. Contact areas andfff_scorevalues can shift slightly (the tesseract corner's contact goes from 102.55 to 103.27 mm², matching an independent trimesh measurement); no example changes orientation or placement. Printability is analysed once per variant.Manifest contract change. The design stage (
coscad foo.assemble) now writesfoo_design.json("schema": "coscad.design/1") instead offoo_manifest.json, which is now written only bycoscad next("schema": "coscad.next/1"). Previously the design stage overwrote the print manifest with a differently shaped file. A design manifest left under the old name gets a stderr note and is not deleted.bambu-slice.pychecksschema, still accepts pre-schemacoscad nextmanifests, and refuses design manifests with the command to run.Constraint errors report the whole loop and exit 2. A mating loop that does not close (or two fixed roots that disagree) is reported on the constraint declared last on it, followed by a
note: file:line:col:line for every constraint on the loop; previously one joint was named, wherever the breadth-first walk happened to meet, often not the closing mate. Floating parts are reported at their declaration instead of the first constraint, and a duplicatefixedpoints back at the first one. Constraint conflicts, floating parts and duplicatefixednow exit 2 (design problem) fromvalidate, the design stage,next,plan,checkandsite; unknown instances or frames and syntax errors stay exit 1. Scripts that treated these failures as exit 1 will see 2.Windows is no longer supported: CI and release builds are macOS/Linux only (no
coscad-windows-x86_64archive), and the Windows OpenSCAD and library search paths, lock branch and extension quoting are removed.plan --pngandsiterender step images fully with OpenSCAD's Manifold backend when available (2025.05 or newer): no z-fighting on mated faces, at preview speed. Older OpenSCAD builds keep the preview with a note. The bench is a solid slab stopped just short of the rest face, so it survives a full render and cannot flicker in a preview.
1.2.0.0 - 2026-10-13
CLI contract for editors and pipelines: every diagnostic goes to stderr and starts with
Error: file:line:col: reasonon one line (the source excerpt follows); assembly loader and fastener errors name the file and line;plandesign errors go to stderr asfile: design: reason. Exit 2 now means "the design has a problem" (validate/plandesign errors,checkoverlaps) and exit 1 "could not run". Barecoscadprints usage to stderr.validateon a missing part file says so.JSON: manifests, plan JSON and the site's build index are emitted through a real encoder (
Coscad.Json, also a decoder for tests and tools), so quotes, control characters and</script>in names, paths or hints can no longer corrupt them. Plan JSONnutvalues are thenut=words (dropin,slidein,hex,none) instead of Haskell constructor names.Rendering: each OpenSCAD run is killed after
COSCAD_OPENSCAD_TIMEOUTseconds (default 600) and reported, instead of hangingstl/next/check/plan --pngforever. A binary or truncated STL, a non-finite vertex, and an empty mesh are diagnosed instead of crashing with a UTF-8 orPrelude.readerror.checkanddoctoruse fresh per-run scratch directories (--keep-tempnames it), so concurrent runs no longer delete each other's renders./opt/homebrew/bin/openscadis a search candidate.nextrefuses an assembly with nothing to print instead of writing an empty manifest.VS Code extension: moved into this repository under
vscode/(0.4.0; see its CHANGELOG): probescoscad --versionbefore relying onvalidate, reads the new diagnostic layout and exit codes,coscad.workingDirectoryreplaces the misnamedcoscad.executablePath, terminal commands no longer depend on the shell, VS Code 1.82, GPL-3.0, a tenth of the package size. CI tests and packages it; releases attach the.vsix.Scripts:
scripts/requirements.txtlists the Python dependency of the Bambu LAN printing path (paho-mqtt).Tests: a
clitier spawns the built binary and checks exit codes, the stdout/stderr split,validate, and that manifests and plan JSON parse.Build: the project builds with cabal-install and GHC 9.8 (
cabal build,cabal test,cabal install exe:coscad); Stack files, hpack andpackage.yamlare gone andcoscad.cabalis the source of truth. Hackage dependencies are pinned incabal.project.freeze. CI, the release workflow, the Homebrew formula and the manual host's update script use cabal.Planner: locate screws with host
@frame, clampedseat=@frameand hex nutpocket=@framebearing seats. Derive transformed axes, head positions, thread reach and access from those declarations. Addpreload=handfor independent nut preparation, and validate hardware without artifacts viacoscad validate. Keep existing face/distance declarations supported.Examples: repair the old corner's reversed outer bracket, constrain both bores of each outer bracket, and describe all six M5 screws. Complete the toy bow's hardware metadata with four M4x12 screws and captive nuts. Generate both instruction guides from the planner; include the repaired corner in the manual site build.
Examples: migrate fourteen remaining assembly and packing examples to
fixed/mateauthoring, with independent coordinate fixtures and per-instance orientation probes. Preserve print quantities and instruction joints, including the tesseract's closed loops and Orbit Tower capture order.Manual: responsive content and cards, contained tables/code, and persistent navigation shared across documentation, build indexes and instruction pages. Keyboard step navigation ignores typing in search fields.
CLI:
coscad validate file.coscad|file.assemblevalidates source and solves constraints without producing artifacts, for editor diagnostics.Examples/planner: convert the 200 mm 2020 cube to named mating frames and rigid assembly constraints. Verify all 16 bracket poses and 32 bores against the coordinate reference, and regenerate its 28-step instructions. Replace the panel's mismatched four-hole pattern with six rail-aligned bores. Keep side-face screws on 2020 slot centres when a panel only partly covers the rail face; reject clamps that miss the slot. Plan JSON now includes each fastener's assembled host-face
position_mm.Language/assembly: oriented construction frames (
|> frame) and child-only rigid mating (|> mate) with explicit normal, up, roll and gap. Declarative.assemblefixed/mateconstraints propagate poses in both directions, validate closed loops and diagnose floating/conflicting assemblies. Design, manufacturing, interference checking and planning share the generated poses. Existing manual assembly expressions remain supported. Moving joints and collision solving are outside this first increment.Language: named construction datums (
|> datum name ref,@name), child-only placement against a separate shape (|> place parent target own), bounded rectangular feature grids (|> grid n m pitch_x pitch_y), and unary pipeline hull (|> hull). Datums follow transforms; unknown, duplicate and ambiguous references have explicit errors. Existing attachment and binary hull semantics are preserved. Seedocs/2026-07-24-TOPOLOGICAL.md.CI: temporarily remove the macOS test job because Homebrew's OpenSCAD cask is disabled by Gatekeeper. Refresh the bow3 limb geometry goldens after the paddle-tip/string-groove redesign and record missing ball and tesseract baselines; Linux and Windows retain the full rendering tests.
Language: reject spaced unary minus in numeric argument positions with a subtraction/negation hint; reject non-finite numeric bindings and arguments before geometry evaluation. Reject opposing/repeated anchor directions and combinations with the center anchor. Active shape and prefix-transform keywords cannot be definition names; stage-only words such as
xremain usable. Clarify compact glyph argument syntax.Plan:
pocket=clamped.facefor a hex nut pocket in the clamped part; a driver that would come up through the bench is a hard block (flip), not a cost. The tesseract example's second cut: 19 mm corner blocks, M3x16 + hex nut per bar end, D-profile struts that print on their flat.Plan:
nut=none(screw threads into the host) andfastener peg ...(friction joints: no screw, driver or torque). Driver clearance is tested against each part's oriented box, a part can be placed held against anything it will be joined to, slender parts stood on end are penalised like rails, and a clamped part resting on the bench no longer counts as loose.examples/assemble/tesseract/: a 100 mm wire-frame cube with a 30 mm cube hung on eight diagonal pegs, 29 parts on one bed.fastener ... nut=hex pocket=face: captive hex nuts in a pocket of a non-rail host. Preload, BOM and step wording follow (no T-slot positions), the pocket face may not be on the bench or covered while the nuts go in. Driver access now requires the driver to really enter another part's box, not merely touch it. When the beam search runs out of states the error lists what the last states could not do.examples/assemble/ball/: a bolted 40 mm ball (two hemisphere shells, core disc) that runs the full chain to a sliced bed.scripts/bambu-slice.py manifest.json [--print N]: slice the beds fromcoscad nextwith Bambu Studio's CLI (system presets flattened and the printer's template gcode files merged so filament settings and the real start/end sequences apply), write_bedN.gcode.3mfand_print.json, optionally upload and start a bed on a LAN-mode Bambu printer.scripts/bambu-print-run.py foo.assemble [--dry-run]: unattended run: camera photo, coscad → next → plan → slice → print, watch to the end, photo,foo_run.jsonwith a verdict.coscad plan foo.assemble [--png]: assembly instruction generator.fastener spec clamped host face [×n at= nut= head= through= torque=]lines and part hints (profile=2020,material=,ends=,mass=,torque=) describe the joints; the planner derives the requires-before graph (nut-first, slot-end sealing, host and clamp before screw), checks support, balance, driver access, and nut entry on every step, searches rest-face phases with a cost table (flip, rail not flat, driver off vertical, shrinking support, sibling screw, loose parts), and writes_plan.md(BOM, T-nut preload sheet, numbered steps with torque),_plan.json, and_stepN.scadscenes (--pngrenders them). Design errors: colliding screws, too little thread reach, a clamp that does not sit on its host face. Fixtures underexamples/assemble/plan/.coscad site DIR spec.assemble ...: static phone-first companion site from the build plans (per-build step pages with images and torque, an index with search across builds).Assembly and part files: expression continuation lines may start with a boolean glyph (
⊕ ...), and comment-only lines may sit between continuation lines.Assembly checks retain hidden parts as anchor references, preserving relational placements during isolation, including chained attachments.
Loft profiles reject vector Z translations and oblique or zero mirror normals instead of silently dropping components; Z-normal reflection correctly leaves an XY profile unchanged.
Examples: migrate fourteen remaining assembly and packing examples to
fixed/mateauthoring, with independent coordinate fixtures and per-instance orientation probes. Preserve print quantities and instruction joints, including the tesseract's closed loops and Orbit Tower capture order.Manual: responsive content and cards, contained tables/code, and persistent navigation shared across documentation, build indexes and instruction pages. Keyboard step navigation ignores typing in search fields.
CLI:
coscad validate file.coscad|file.assemblevalidates source and solves constraints without producing artifacts, for editor diagnostics.man coscad: full manual page (commands, environment, language cheat sheet, assemblies, examples), shipped in release tarballs.Homebrew formula in
Formula/(tap this repository; builds with Stack and Homebrew's ghc@9.8; installs the man page, docs, and examples) andscripts/update-formula.shto pin it to a tagged release.
1.1.0.0 - 2026-09-10
CLI:
--help(with a language cheat sheet),--version,-oforcoscad part.coscadand the newcoscad stl part.coscad(compile + render + volume/bounds),coscad doctor(finds OpenSCAD and BOSL2, reports versions, renders a test part),coscad check --keep-temp.coscad checkrenders into the system temp directory and cleans up; no more_chk_*files next to the assembly.Design stage (
coscad foo.assemble) packs across as many plates as needed (foo_plate1.scad, ...; manifest placements carryplate) instead of failing on overflow, matchingcoscad next.OpenSCAD is located via
COSCAD_OPENSCAD, PATH, then the usual install paths; BOSL2 viaCOSCAD_BOSL2or the OpenSCAD library folders (its parent is added to OPENSCADPATH when running OpenSCAD). OpenSCAD WARNING/ERROR output fails a render instead of being ignored.Emitted .scad: numbers print as
10not10.0, rotation noise like3.06e-16prints as0, identity translates/rotates from attachment desugaring are elided.Source files are read and written as UTF-8 regardless of locale.
Embedded-DSL sample modules moved from
src/toexamples/haskell/.CI on Linux/macOS/Windows with Stack (build,
coscad doctor, full test suite with OpenSCAD + BOSL2) and tagged releases with prebuilt binaries.License id corrected to
GPL-3.0-only.Numeric bindings:
w = 20,r = w / 2 - 1(arithmetic with parens, numbers may reference numbers); usable in any numeric argument position as a name or a parenthesized expression. Using a number as a shape or a shape as a number is a compile error naming the binding.Lofts:
loft z0 p0 z1 p1 ...(glyph⟰,Loftin !simple, and the pipeline stage|> loft z p) skin 2D profiles into a solid through BOSL2skin()(method="reindex"when vertex counts match,"distance"otherwise); profiles are emitted as path expressions. Invalid profiles (solids, booleans, out-of-plane rotations) are rejected at compile time.examples/loft.coscad.Diagnostics: errors carry
file:line:coland quote the offending line. Syntax errors, undefined names, circular dependencies (only the cycle is listed), and duplicate definitions are distinguished; the old catch-all "Cannot resolve variables (possible circular dependency)" is gone..assemblepart-option errors point at the real position too.Dimensionality check: extruding a 3D solid, offsetting a solid, combining a 2D profile with a solid, or a 2D
mainare compile errors instead of silently wrong OpenSCAD output.Multi-line pipelines keep their newlines, so a trailing
//comment on the first line no longer swallows the|>continuation lines.OpenSCAD is located via
COSCAD_OPENSCAD, PATH, then the macOS app bundle, and STL export explicitly requests ASCII.Regression suite (
stack test): diagnostics, example compilation,.scadsnapshots, and an OpenSCAD geometry tier with golden volume/bounds per example plus cross-checks (tbracket == bracket,nextconserves volume,checkpasses on bow3).Examples: tbracket resynced with bracket (tunnel d10); the zigzag springs extrude a circle instead of a sphere (holes now cut);
corner/frame/autotest/example_checkassemblies reference files that exist.
0.1.0.0 - YYYY-MM-DD
A lot of stuff
1.0.0.0 - 2026-07-24
First edition of Coscad with Next.