StepKit for Apple platforms
66 releases · latest 0.30.9 · 2026-10-12

Every release, measured.

StepKit is built against a 565-file corpus spanning 25+ CAD writers, with a regression gate on every change — so a release note here can say what moved and by how much, not just what was attempted. This is the whole history, from the first tagged parser to today.

0.30.9
Latest

ANSYS joins the corpus

Tooling
  • Two files exported from ANSYS DesignModeler, a writer the test corpus had not seen, join it: a torus pipe and a cylindrical battery cell, both from openly licensed repositories. Both come out watertight; 519 of 565 corpus files now do.
0.30.8

One bad face, not a bad file

Fixed
  • When one face of a model failed to close in the usual triangulation, the whole file was triangulated again a simpler way. On a fidget toy that closed a thread but drew the hole beside it faceted, with dark stripes. Now only the failing face is retried; the hole is smooth again and the model stays watertight.
0.30.7

Twelve new writers in the corpus

Tooling
  • The test corpus grows to 563 files with twelve that each bring a CAD system or exporter it had not seen: 3DEXPERIENCE (including AP242 edition 3), NX 2212, Creo Elements/Direct, KiCad's own exporter, Theorem Solutions translators, FreeCAD's InventorImporter and more, all from openly licensed repositories. All nine new solids are watertight; 517 of 563 corpus files now are.
0.30.6

Recipes, not geometry

Changed
  • Some files hold no shape at all, only the recipe for one: extrude this profile, fillet that edge, subtract a cylinder. Nothing can be drawn until a modelling kernel replays it. Loading such a file used to fail with a bare "no shell found"; the error now says the file holds only a construction history or CSG tree, and names what it found.
Tooling
  • step-inspect says why a file could not be loaded.
0.30.5

Loops read in chain order

Fixed
  • Some exporters list a face's edges out of sequence: every edge correct, only the order shuffled. Read as listed, the outline cut straight across the face and the part came out torn. StepKit now puts such a loop back in order when its edges form one closed chain; a helicopter base plate exported from FreeCAD goes from 56 open edges to watertight.
Added
  • A diagnostic, reorderedLoop, says when a loop was put back in order.
0.30.4

Straight pins, drilled cone tips

Fixed
  • Long, thin pins came out with their triangles running diagonally round them: the grid was coarsened along the pin's axis, but the triangulation still treated its cells as square. On cylinders, cones and extrusions it now accounts for the stretch. A printer frame's twist falls from 0.40 to 0.07, and one part's worst deviation from 3.9 to 0.8 mm.
  • A cone that runs to its tip with a hole drilled near it was drawn straight across the hole. The tip now closes at the apex and the hole stays open; a suspension assembly goes from 63 open edges to 3.
0.30.3

Edges without micro-folds

Fixed
  • A curve that doubles back by a few micrometres was sampled on both sides of the fold, and the faces beside it could not be refined. A kayak hull's worst deviation falls from 2.2 to 1.5 mm.
  • A closed spline edge ended a hair off its own vertex, leaving a tiny gap the faces either side could not share. A suspension assembly loses another 20 open edges.
0.30.2

Seams of closed splines

Fixed
  • On a spline surface closed on itself, a boundary point just past the seam could take its neighbour's place and drop out, leaving a small hole. A detailed lighthouse model becomes watertight; 508 of 551 corpus files now are.
0.30.1

Rims joined by angle

Fixed
  • The two rims of a cylinder or cone are now joined point by point round the axis, not by their order in each ring. Walls whose rims were sampled at different angles had come out twisted; twist falls on 15 files and a shaft's volume moves from 1.4 % over the true value to just under it.
  • Balls bounded only by slits were drawn as flat discs. They are now drawn whole, and a suspension assembly loses 40 open edges.
Tooling
  • The leak report welds each solid on its own, as the mesh metrics do, so parts that merely touch no longer show as defects.
0.30.0

A measure for twisted walls

Added
  • A new mesh metric, surfaceTwistMax, says how far the triangles of cylinders and cones lean along the circle. A wall laid in straight strips reads zero. A twisted one leans, even when the mesh is watertight and every triangle sits close to the surface, and until now nothing measured it.
Tooling
  • The corpus snapshot gains a twist column, and the regression check fails when a file's twist grows. A baseline written before this release has no twist column, so checking against it does not watch twist until the baseline is written again.
0.29.10

Large boards load fast again

Fixed
  • A large model with a few defects of its own could be tessellated three times over, because a second attempt that left only marginally fewer gaps replaced the first. A 16-inch laptop mainboard of nearly 50,000 faces took half as long again as in 0.29.6 and is now back to its old time. A second attempt now has to close the model or remove at least a tenth of its gaps. Meshes of the test corpus are unchanged.
0.29.9

Closed edges drawn closed

Fixed
  • Full circles and closed ellipses in the edge overlay were drawn one segment short of closing, leaving a visible gap beside their start point. Every closed edge is now drawn the whole way round. The triangle mesh itself is unchanged.
0.29.8

Straight cylinder walls

Fixed
  • A full circle written to run against its curve lost the sample next to its vertex. A gear's bore came out with one point fewer on one rim than on the other, and its wall was drawn twisted beside the seam. Such circles now keep every sample.
  • The two rims of a cylinder could be joined one step apart, twisting the whole wall, when a sample sat right where the angle count starts. The rims are now paired from matching points, and several parts come closer to their true volume: a screw from 3.0 % to 1.3 % under it.
0.29.7

Pointed hulls close

Fixed
  • A curved panel that tapers to a point, like the bow of a kayak hull, could fold over itself near its tip. Points close to the tip now keep to their own side of the panel.
  • The points of a very short edge on a large curved panel could all land in one place, leaving a gap. Each now gets its own, and tessellating the test corpus takes about a third less processor time.
  • Two neighbouring faces could both cover the same hairline sliver along their shared edge, leaving that edge doubled or open. Such a pair is now removed. The kayak model becomes watertight, and across the corpus 507 of 551 files now are.
0.29.6

Swept tubes and half balls

Fixed
  • A pipe made by sweeping a closed outline along a line, cut on a slant, was drawn with lids across its ends. It now closes as a tube, and a jack-in-the-box model becomes watertight.
  • Half a ball cut through its poles could be drawn as the other half when the file did not mark which boundary was the outer one. It now takes the half the boundary's direction says. Across the corpus 504 of 551 files are now watertight.
0.29.5

Pipes and rings round splines

Fixed
  • A band round a spline surface closed in one direction only, such as a ring round a revolved spline or a section of pipe, was drawn with lids across its two rims. It now closes as a tube. A housing model goes from more than four times its volume to within 0.2 % of the reference, and an exhaust manifold whose pipes were all but missing now has them.
  • Pipes that are straight in the middle and bend further along now get enough grid points along their length. The manifold's worst triangle drops from 6 mm to under 1 mm off the surface.
0.29.4

Past the apex

Fixed
  • A cone face beyond the cone's apex, and the inner part of a spindle-shaped torus written as an ordinary torus, were drawn mirrored across their axis. A screw model's cone sat 4.7 mm off its surface and its domed head came out more than half again too large. Both are now drawn where they are, every face within 1.5 % of the reference area.
0.29.3

Caps round a pole

Fixed
  • A sphere face holding one of its poles (a ball's cap above a ring) was folded over itself, or drawn as the whole sphere when the ring was a line of latitude. It is now closed round its pole. Two NIST test parts lose all their non-manifold edges, and a jack-in-the-box model goes from more than twice its true volume to within 5 %.
  • Thin flat faces, such as the end of a lead bent from 0.005 mm sheet, are zipped across like curved ones. Across the corpus 502 of 551 files are now watertight.
0.29.2

Sixteen more files close up

Fixed
  • Three more boundary shapes the triangulation could not close. A flat ring narrower than the sag of its rims' chords, such as a 1.3 µm step round a bore, is now stitched rim to rim. A cone tip whose apex is written as a separate point is fanned like the others. Two boundary points that land on one place in the surface's parameters, when a vertex sits a hair outside its surface, are now kept apart instead of one being dropped.
  • Across the test corpus 498 of 551 files are now watertight, up from 482. One speed-test print model goes from 834 open edges to none, and a push-button model's volume goes from 88 % too large to within 0.2 % of the reference.
0.29.1

Slivers and cone tips close up

Fixed
  • Faces narrower than the tolerance (strips left by a boolean between two nearly coincident surfaces) came out with holes, because their outline crossed itself once projected onto the surface. They are now zipped across directly. A kayak hull's open edges fall from 173 to 33.
  • A cone face bounded by a single loop round its axis is the cone's tip, and it was left open: drill points, the pointed ends of hexagon bars. It is now drawn as a fan from the apex. A suspension assembly's open edges fall from 217 to 137.
Tooling
  • The leak report in step-inspect counted every sharp edge as a hole and could run for more than ten minutes on a large file. It now finishes in seconds.
0.29.0

Narrow faces get their own points

Added
  • Deviation-driven refinement, a new option that is on by default. Where a face is narrower than its interior grid (a strip along a slanted trim, the end of a band), triangles used to join the boundary straight across the surface. Faces with triangles far off the surface now get extra points until the triangles lie on it. The worst deviation halves or better on 13 corpus files, one of them going from 8.6 mm to 1.7 mm, for 1.8 % more triangles. Heavy freeform files take from a few percent to about half again as long. Thumbnails skip the refinement, and turning the option off restores the old speed.
0.28.4

Freeform files fast again

Fixed
  • 0.28.0's deviation measurement made files with freeform (B-spline) faces many times slower to tessellate — a kayak hull took 25 s where 0.27.0 took half a second. The meshes were right, only slow. Each triangle is now checked with a few local steps towards the surface, and the kayak takes 0.66 s again.
Tooling
  • A test that counts surface evaluations per triangle, so a slowdown like this one fails a test instead of waiting to be noticed on a clock.
0.28.3

The inner portion of a spindle torus

Fixed
  • A spindle torus — tube wider than its ring — has an outer and an inner portion, and STEP says which one a face lies on. That choice was lost, so an inner face was filled in from the outer one: a domed cap came out warped and its part 4.8 % short of its volume. It is now within 0.8 %, and the STEP writer keeps the choice when it writes such a torus back.
0.28.2

Faces where their surface says they are

Fixed
  • Five cases where a face was drawn somewhere it is not: extruded surfaces longer than their extrusion vector, revolved straight lines, tubes bounded by two plain circles (with or without a window in the wall), bands round a torus in either direction, and holes that landed a whole turn away from their face. A robot arm model went from 31 times its true volume to within 3.2 %, a wheel assembly from more than double to within 0.4 %, and nine more corpus files close up (482 of 551 watertight).
0.28.1

Cancellation reaches the parser

Fixed
  • A cancelled load now stops inside the parse instead of after it. On an 18 MB file a cancel a fifth of the way in used to be answered after the whole parse and more; now it is answered in 25 ms. The parser checks once per 64 KB of input, so a file with millions of entities does not pay for the question millions of times. The file read before parsing is still not interruptible.
0.28.0

Large faces in their own measure

Fixed
  • Faces more than a metre in radius were triangulated as if they had a metre's radius, and a cone's step was sized from the radius written in the file, which can sit next to its apex. A 14 m pressure-vessel model drew a saw of teeth where its cylinder meets its cone and a flat wedge across the cone; both are gone, and its worst triangle went from 222 mm to 7.6 mm off the surface.
Changed
  • The surface-deviation metric now measures how far each triangle's centre is from the surface itself. The old measure could be fooled at a cone's apex or a sphere's pole and reported defects that were not there. Across the corpus, files more than 5 mm off fell from 27 to 15, and one more closes (473 of 551 watertight).
0.27.0

Cancellation everywhere in the loader

Fixed
  • A cancelled load of a file built from shells rather than solids ran to the end before reporting cancellation. Every branch of the loader, and each of its loops, now checks the token.
Changed
  • The README explains that from: in a package dependency is a range up to the next major version, and shows how to hold a version steady.
0.26.0

Progress you can show

Added
  • Progress reporting on the asynchronous load and tessellate calls: a stage, a count done and a total, so a viewer can show a real progress bar. Callers that pass no handler pay nothing.
Fixed
  • Three data races in the tessellator, found by running the suite under ThreadSanitizer.
0.25.1

A lone sphere measured by its vertices

Fixed
  • 0.25.0's relative tolerance ceiling measured a part by its vertices, and a full sphere has one, so its size was zero and a 10 mm sphere got 516 960 triangles — a bearing ball tessellated as its own part would have too. The ceiling now measures a box that also takes in closed circles, spheres, tori and B-spline surface control nets, while the large-model floor keeps the vertex box it always had, so nothing comes out coarser than before; the sphere has 1 368 triangles.
0.25.0

Small parts get their own tolerance

Fixed
  • Small parts were tessellated as coarsely as large ones: a 6 mm gear had its bore drawn as a 16-gon and a visible dent on its hub. The chord tolerance is now capped at a thousandth of the part's size (maxRelativeDeflection), so parts under 50 mm across get a finer mesh; larger parts are untouched. The gear goes from 0.90 % to 0.06 % of the reference volume, and 42 of the 47 corpus files that moved came closer to theirs.
  • A closed edge's direction was a coin flip re-tossed by every change in sampling density; it now follows same_sense and its ring starts at its vertex. One more corpus file closes (472 of 551 watertight) and the worst surface deviation falls sharply on five others.
  • A closed B-spline direction written unclamped is now recognised as closed, and a face skipped for a zero-area loop reports a diagnostic instead of vanishing.
Known issue
  • Five files' volumes moved away from the reference under the finer tolerance, among them key-ring, the B-spline-tube grid density already on the list.
0.24.1

Cancellation says cancellation

Fixed
  • A cancelled asynchronous load threw a loader error instead of CancellationError: cancelled before its first solid was mapped, the loader found its model empty and reported that as the file having no geometry. A viewer's integration test caught it. The loader now throws cancellation when its token is tripped, and the async runner reports cancellation for whatever a cut-short run produced.
0.24.0

A metric for what the last fix found

The wedges of 0.23.0 had every vertex on the surface and every metric green. This release measures the one thing that was off — the middles of the triangles — gates the corpus on it, and fixes the two largest causes the first sweep turned up.

Added
  • Surface deviation: for every triangle of a curved face, how far its centre sits from the surface. Vertices can all lie on the surface while the triangles span its curvature; this number sees that, and nothing else did. A face whose worst triangle exceeds eight chord tolerances raises a diagnostic naming it; step-inspect prints it, the golden snapshot carries it as a column, and the regression check fails when it grows.
  • The first sweep over the 551-file corpus is the audit: 34 files with triangle centres more than 5 mm off their surface, 28 between 1 and 5 mm, 444 within half a millimetre. The list is now a work queue that the check keeps from growing; two classes with a common cause are fixed below, the rest is recorded as known.
Fixed
  • Faces with a parameter span over 100 received no interior points at all — a guard meant for runaway grids also fired on any cylinder taller than 100 mm. A 150 mm bore then had 173 boundary points and nothing between them, and the triangles from its outer loop to its hole cut 15.6 mm inside the surface: 5 % of the volume, 6 % of the area. The point cap is what bounds a grid; a span now only has to be finite. That file comes to 0.004 % of the reference volume, and four leaky files close — watertight 467 → 471.
  • When a face wants more interior points than the cap allows, both grid steps were coarsened by the same factor — on a 254 mm pin of 1.59 mm radius that widened the angular step from 0.39 to 0.86 rad and put triangle centres 0.15 mm inside the surface: 12 % of the volume gone with the area intact. The cap now measures one step's sagitta in each direction and lets the flat one absorb the whole factor. Five SparkFun pins return to within 1–2 % of the reference.
Known issue
  • Files the sweep still flags, each a different cause: a moon-buggy assembly, an ACIS test pair, a lighthouse's base, a robot arm, a ship hull, a beer bottle, a NIST case, and on closed files daratech, a screw and a timing pulley. Each is a separate investigation; the metric now keeps count.
0.23.0

Triangulated the way 3D sees it

A viewer's user saw wedges across a switch track that were not in the file. Every point of the mesh was right; the triangles joined the wrong ones. Curved faces are now triangulated in a metric parameter space, and the corpus moves closer to the reference across the board.

Fixed
  • A 145 × 2.6 mm strip on a 186 mm cone is 0.78 rad × 2.6 in parameter space — taller than wide — so the triangulator, which only sees parameters, spanned the strip's length with two tip-to-tip triangles: 72 mm chords across a curved surface, 10 mm of sagitta, seven times the true area. Nothing leaked and no diagnostic fired, because every point was on the surface; only the connectivity was wrong. Parameters are now scaled by the surface's mean rate of change before triangulation, so the triangulator lays a ladder between the rails. The file's area now matches the reference to 0.001 % and its volume to 0.02 %.
  • Across the corpus, of 521 files with a reference volume from an independent kernel, 133 move closer and the mean deviation falls from 6.2 % to 4.3 %: a support ring from 6 472 to 12 440 mm³ (reference 12 458), a bearing ring from 7 097 to 23 956 (23 840), a bolt, a hex nut, a NIST test case each within a fraction of a percent. Nine leaky files close — watertight 458 → 467 of 551.
  • Two files opened a crack under the new triangulation, where a different diagonal met a T-junction the neighbour no longer conformed to. The decision is therefore made per file: when the metric pass leaves boundary or non-manifold edges, the raw pass runs too and the mesh with fewer keeps. Closed files — the large majority — pay nothing.
Tooling
  • step-inspect --faces lists every face with its surface kind, triangle count, 3D area and centroid, for matching against another kernel's per-face view when a whole-file number disagrees. It is how the two cone faces were found.
Known issue
  • A few closed files move slightly away from the reference — a timing pulley, a NIST case, a thin-tube key ring — where the new connectivity meets the interior-grid density on B-spline tubes. The interior grid is the next item.
0.22.0

What another kernel makes of a written file

A second writer gate — a foreign reader instead of our own — and the five defects it found the moment it ran: an assembly written per occurrence instead of per product, cone apexes written as empty loops, faceted solids that fell apart into single faces, and inch files read as millimetres.

Tooling
  • The writer's files are opened by other CAD systems, not by StepKit — crater, ReverseKit and FeatureKit build in BRepKit and export through StepWriter for Fusion, SolidWorks and FreeCAD to read. The round-trip gate only ever proved that StepKit could re-read its own output. A new gate has an independent kernel read both the original and the written file and compare validity, solid and face counts, volume and area; verdicts separate real writer defects from faces StepKit never models (PMI datum planes kept outside the solid) and from originals that kernel itself cannot close — three of which it closes in our version and not in theirs.
  • On the 583-file corpus: 510 files identical in a second kernel's eyes, and 13 with a measured difference, ten of them B-spline-heavy models whose volume differs by 0.2–1.7 % because pcurves are not yet written and the second kernel re-projects the edges. The thirteen BRepKit-born solids from the kernel's own export harness all read back valid at their expected volumes.
Fixed
  • A repeated sub-assembly wrote its children once per occurrence. STEP's assembly is a graph of product definitions that the loader expands into a tree; the writer walked the tree and emitted an edge for every node, so a product placed six times listed its two children six times, and a reader expanding the graph got thirty-six per placement. The CAx-IF as1 assembly came back through the reference kernel as 152 solids instead of 18, a BTT board as 7 528 instead of 732. Each product's children are now written exactly once.
  • A cone's apex or a sphere's pole is a VERTEX_LOOP; it was written as an edge loop with no edges, which a second kernel answered by splitting the face into two zero-area faces and declaring the solid unorientable. The loader keeps the loop's vertex and the writer writes the loop as what it is.
  • Faceted solids — POLY_LOOP faces over shared points, no vertices or edges of their own — were loaded as one island per face. StepKit's tessellator welds by position and never noticed; the writer serialised the unshared topology and the second kernel read a 155-face clip as 155 one-face shells and no solid. Vertices are now shared by point and edges by vertex pair.
  • Short-name files in inches loaded 25.4× too small, silently: the unit entities were missing from the short-name table, so the loader saw no length unit at all. A faceted assembly's parts also carried no geometry, because the resolver matched one solid type only.
  • The Linux runners had never run the swift-testing half of the suite — the per-suite filter matches XCTest classes and nothing else — so the BRepKit bridge's tests ran on one machine. Both runners now run that half separately and compare the count against the sources.
Added
  • Tessellator.parts(of:from:) — the instancing half of tessellateParts over a mesh the caller already has, so a viewer showing the whole model and its parts tessellates once instead of twice.
0.21.0

B-spline edges trimmed to their vertices

Fourteen more corpus files close, and the freeform frontier moves in bulk — from a defect that had hidden as cracks with no diagnostic since the first release.

Fixed
  • Some writers — ACIS and Spatial first among them — write one closed B-spline for a whole profile outline and let every edge around it reference that same curve between its own two vertices; others carry a short edge on a long open curve. StepKit sampled the curve's entire domain for every such edge, handing each face the whole outline: boundary points centimetres off the surface, a stalled triangulator on the end faces, a fifth of the volume gone. Because every neighbouring face received the same wrong polyline, it still welded — so the damage surfaced only as unexplained cracks. Circular arcs had been trimmed by their vertices since 0.10.3; B-splines never were.
  • Each vertex now locates its parameter on the curve and the edge is sampled between the two — for a closed curve the arc the edge's sense selects, wrapping the seam. A vertex within a micron-scale tolerance of a domain end snaps to it, so a curve that skims its own start cannot steal the vertex; a trimmed piece shorter than the chord tolerance becomes a single chord. Edges whose vertices already sat at the domain ends — every edge that was correct — are untouched.
  • Corpus of 551 files: watertight 444 → 458, no regressions. The NIST groove that exposed it goes from 156 boundary edges and −18.8 % volume to watertight and −0.05 % against the reference kernel. On the hardest freeform files the boundary-edge counts fall by half to two-thirds — Lighthouse 6761 → 2683, Stairs 1162 → 122, one blade to zero — which says a large share of what the audit filed as triangulation failures was never triangulation at all. A hand-written cube whose outlines are one shared closed curve each fails without the change and guards it.
0.20.1

A ball cut through its axis lands on both sides

Fixed
  • Some writers — Spatial InterOp, behind TraceParts' catalogues — split every bearing ball into two hemispheres by a plane through the axis: two meridian arcs, poles on the boundary, no seam edge. Every boundary point then sits on one of two parameter lines exactly half a period apart, and each unwrap step is a coin flip between +π and −π. Both hemispheres used to resolve to the same half of the sphere: overlapping triangles, non-manifold edges along the equator, the other half missing.
  • The loop's orientation decides it — an outer bound runs counter-clockwise in parameter space when the face agrees with the surface normal, clockwise when it does not — and a wrong-signed strip is moved to its complement. The tessellator had never consulted bound orientation before. Acts only when every boundary point lies on the two lines, so no other loop is touched.
  • Corpus: the first TraceParts file goes from leaking with no diagnostic to watertight (volume vs reference −3.4% → −0.65%); two already-watertight files carrying a mis-sided ball seat move toward the reference kernel. 0 regressions on 551 files; a hand-built two-hemisphere ball guards it.
0.20.0

StepBRep — the bridge to BRepKit gets a home

Added
  • StepBRep, a new target and product: the bridge between BRepKit's entity store and StepKit's model, in both directions — BRep → STEP for export, and STEP → BRep for import. It had lived in two byte-identical copies in downstream repositories; the stack grew a layer that needed it without pulling the whole parametric kernel in, and this is its dedicated home. A consumer of the parser alone never compiles it.
  • Placed here rather than in the kernel by measuring the dependency graph: the kernel is consumed by four packages that would otherwise inherit a STEP writer they never asked for, while StepKit's two consumers already depend on the kernel. A writer may know what it writes; a geometry kernel that knows a file format has the arrow backwards.
  • Round-trip tests through the public interface only: a generated solid crosses to the kernel and back with its face count intact, and an impossible solid refuses with a reason instead of crashing.
Fixed
  • The README's install snippet pinned a version seventeen minors old — a working wrong instruction, not stale prose.
0.19.0

The core leaves Apple: Linux builds

Parsing, the B-rep model, tessellation, mesh repair and the STEP writer now build and test off-Apple, so they can run on a server or in a Linux CI leg. Rendering stays Metal, and therefore Apple-only.

Changed
  • StepParser and StepKernel no longer require Apple's simd framework. The vector types were always Swift standard library; only the framework's free functions, the matrix types and the quaternion were Apple-only, and a compatibility layer supplies exactly those where the framework is absent — with identical column-major semantics. No API or behaviour change: on Apple the shim compiles to nothing and the system framework is used as before.
  • Verified by building in clean Swift 6.1 and 6.2 Linux containers, and by a differential check of the compatibility layer's matrix maths against Apple's simd over 20 000 random matrices — worst relative error 2.4e-12, quaternion→matrix 7.9e-16. New tests assert the layout, multiplication and inverse contract against known values on both platforms, so a divergence fails one of them.
  • The package manifest omits the Apple-only rendering targets off-Apple; the portable core is unchanged everywhere.
Fixed
  • The async API no longer blocks Swift Concurrency's cooperative pool. The async load and tessellate overloads ran the pipeline in a detached task — on the cooperative pool — while the pipeline blocks its thread for parallel work. That starves the pool; on Linux the next parallel tessellation after an awaited one deadlocked. The work now runs on a dispatch thread and bridges back through a continuation, with a regression test that reproduces the exact ordering.
  • Cancellation actually reaches the pipeline now. The per-face and per-solid checkpoints tested the task's cancellation flag, which reads false on parallel worker threads because they carry no task context — so a cancelled load or tessellation could never stop early on any platform. An explicit cancellation token replaces it.
Added
  • A Linux CI job (Swift 6.1 container) building the portable core and running the test suites on every change, so portability cannot silently regress.
0.18.0

Classified analytic edges for viewer overlays

Added
  • Real CAD edges as public data. The tessellator's shared per-edge discretizations — until now a private cache — are surfaced as polylines that are bit-identical with the mesh's face-boundary vertices, so an overlay line sits exactly on the shaded surface instead of z-fighting near it. Each carries its provenance back to the B-rep edge and its adjacent faces.
  • Every edge is classified: boundary (an open shell's rim), sharp (a crease), tangent (a smooth blend or fillet boundary, below a configurable angle) or seam (a periodic surface's seam, with the same face on both sides). Classification measures the dihedral angle between the adjacent faces' outward normals along the edge — closed-form for plane, cylinder, cone, sphere and torus, STEP pcurve evaluation for freeform surfaces, oriented facet normals as a last resort. A viewer can draw crisp outlines and hide fillet blends.
  • Assemblies transform edge polylines per occurrence alongside their faces. Off by default — existing behaviour, memory use and the golden corpus are unchanged.
0.17.0

Assembly transforms, disambiguated the way CAx-IF recommends

Fixed
  • Occurrence transforms now apply the item-defined transformation in the CAx-IF-recommended direction. The loader previously assumed one ordering unconditionally — correct only for exporters that follow it. It now swaps the two axes when each is an item of the other representation, and anchors the direction on whichever representation belongs to the child product. Matches the reference kernel 7.9.3 on every ordering permutation, verified through the differential harness at 100 % agreement.
  • The two-level assembly test fixture was regenerated from a real third-party export; the old hand-made one was degenerate — it named the same representation on both sides, so the transform direction was genuinely ambiguous. The degenerate file survives as its own the reference kernel-parity fixture, and three further ordering variants now guard the code path.
Changed
  • The writer makes each occurrence's target axis an item of the parent's shape representation, so readers that use item-to-representation membership to orient the transform — the reference kernel and StepKit's own loader included — resolve written assemblies without guessing.
0.16.0

Official iPad and iPhone support

Added
  • The package declares iOS 17 and the consumable libraries are verified to compile for arm64 iOS: no macOS-only API in any library target, cross-platform Metal rendering with runtime-compiled shaders, and ModelIO guarded behind a capability check. A CI job compiles the library stack for a generic iOS device on every push and pull request — so iPad compatibility is enforced, not discovered later. The command-line tools and the macOS window viewer stay macOS-only.
0.15.1

An honesty flag for last-resort repair

Added
  • The volumetric repair always produces a watertight solid — but for open surfaces or thin-walled input the shape can move drastically (one file −96.8 %, another +124.7 %). A reliability flag and a typed diagnostic now say which happened: reliable means a faithfully recovered solid within 30 % volume; unreliable means closed but not the real shape — raise the resolution or don't use it. Input that encloses no volume at all is returned unchanged with the diagnostic, instead of a vacuously watertight empty mesh.
0.15.0

Last-resort volumetric repair

Added
  • When a mesh cannot be closed by construction — input defects, sculpted cracks, non-manifold soup — an opt-in pass forces it into a guaranteed watertight, closed 2-manifold solid, accepting a change of shape. Clean-room and dependency-free: a generalized winding number (robust inside/outside even for broken input) over a narrow band with flood-fill sign propagation, then marching tetrahedra with globally edge-keyed vertices (watertight by construction), then Taubin smoothing to de-staircase without shrinking.
  • Never automatic, and it reports how far the volume moved. A leaky kayak hull with 213 boundary and 33 non-manifold edges comes back watertight at −1 % volume; a clean sphere round-trips at roughly 0 %.
0.14.0

Writer v2, async API, and a support declaration

Added
  • Writer v2 — face colours and assembly structure. Colours are written as one presentation-style chain per distinct colour, with signatures verified against the AP214 EXPRESS schema; assemblies get a product chain per distinct product and a usage-occurrence edge per instance. Occurrence transforms round-trip to 1e-9, and the reference kernel reads the written assembly bit-identical to the original. Pcurves and PMI remain unwritten.
  • Async, cancellable load and tessellate, with cooperative checkpoints per solid and per face.
  • A formal STEP support declaration — the reference document for product UIs: format coverage, writer guarantees, measured quality metrics, the diagnostics honesty model and the known defect classes.
  • A diagnostic for boundaries lying far off their referenced surface — a writer bug in some helical-strip exports — so the file is called inconsistent instead of showing unexplained holes.
0.13.0

Conics, trimmed curves, a fuzzer and CI

Added
  • Trimmed-curve edge geometry. Edges referencing a trimmed curve had collapsed to straight chords; the basis curve now resolves as the geometry and a reversed sense flips the edge so circular and elliptical bases sample the correct arc. 24 corpus files carry the entity — one drops from 1211 to 702 boundary edges.
  • Hyperbola and parabola edges, which previously collapsed to chords: both load, evaluate to the ISO parameterization, and sample adaptively through their exact parameter inversions. One turbine part gains roughly 2 400 triangles of true conic geometry.
  • The corpus grew to 451 files (Onshape AP242 and Fusion 360 exports), a CI workflow, a DocC catalog, and a corpus-mutation fuzzer: deterministic mutations — bit flips, truncation, chunk splices, hostile numerals — each run through the full parse → load → tessellate pipeline in an isolated subprocess with a hang timeout. STEP files are untrusted input in a viewer, so graceful rejection is the pass condition.
Fixed
  • Semantic PMI honours the measure's own unit. A dimensional callout can carry a different unit than the geometry around it — the NIST files put geometry in millimetres and callouts in inches — so those values read 25.4× too small (a 0.438-inch hole reported as 0.438 mm).
  • A multistart fallback for singular-chart inversion: a collapsed control row, such as a lofted blade tip, makes the Jacobian vanish and both usual seeds converged to the wrong domain corner, leaving 21 mm residuals. Points whose residual stays above tolerance now retry from a coarse deterministic start lattice.
0.12.0

The STEP writer

Added
  • StepKit writes STEP, not just reads it. A loaded model's B-rep serializes back to a Part 21 file: every surface type including rational B-splines, every curve type, full topology from vertex to solid — voids and loose shells included — and a unit context. Validated three ways: a load → write → re-load → tessellate → compare round-trip passing 392 of 392 loadable corpus files with face count, watertightness and volume preserved; unit-test fixtures; and the reference kernel / FreeCAD importing the output as valid solids, matching a FreeCAD ground-truth volume exactly. Colours, assemblies, pcurves and PMI are not yet written.
  • Apex and pole chart splitting. A boundary point sitting on the revolution axis — a cone apex, a sphere pole — has no defined angle, and the arbitrary value zigzagged the loop across the parameter chart, corrupting everything after it. The apex now snaps to the incoming branch and splits into one copy per adjacent edge. Five more watertight files, plus two volume fixes confirmed against the reference kernel where StepKit had been 17.8 % over and 16.4 % under.
0.11.0

Seam splitting and T-junction healing

Added
  • A periodic seam split for tube faces. A face whose two boundary loops each wind once around the periodic direction is an annulus of the period cylinder and simply cannot embed in a flat parameter chart — and some writers omit the seam edges most exporters insert. Such faces are now cut at an existing vertex pair and rebuilt as one simple polygon whose two seam sides share bit-identical 3D samples, so the seam welds shut and neighbouring faces are untouched. Eleven more watertight files and large crack reductions corpus-wide: one car wheel loses 1509 boundary edges, a French press drops from 401 to 13.
  • T-junction healing. When a vertex of one edge lies on the interior of another — common in real input — the triangulator now reports the split, the tessellator lifts it to 3D, verifies it sits on the chord, and inserts it into the shared edge discretization so both sides see the same chain. 332 insertions across 15 corpus files.
Fixed
  • Constraint recovery conforms to on-segment vertices after flip exhaustion instead of silently giving up — the previous behaviour left unrecovered constraints, which means cracks.
0.10.3

Arc sense — a project-lifetime bug

Fixed
  • Arc sampling honours the edge's sense flag. A reversed edge means the complementary arc joins the endpoints, but every circle and ellipse was sampled counter-clockwise regardless — 270° sweeps where the file meant 90°. Unmasked by a new corpus family whose writer uses the flag heavily. Seven more watertight files; one ventilator drops from 1362 to 52 boundary edges, another part from 2270 to 6.
  • Closed-direction Newton inversion wraps across the seam, normalized to the seed's branch. A degenerate toroidal surface loads as a torus.
Added
  • A UV inspector: a self-contained HTML parameter-space debugger with double-precision pan and zoom, layer toggles, a locked-crossing metrics table, edge provenance and inversion residuals — the tool most of the following releases were diagnosed with.
  • A diagnostic for boundaries winding the periodic direction twice or more — missing seam edges, invalid input, provably unrepairable — so they are attributed rather than left mysterious.
0.10.2

Loop-global branch selection

Added
  • When both the independent per-point inversion and the predictor-corrector trace leave a B-spline boundary self-intersecting in parameter space, up to four valid preimages per point are enumerated and a cyclic dynamic program picks one per point, minimising the closed path length — the loop-global choice a greedy trace cannot make. Every candidate reproduces its shared 3D point within tolerance by construction, and the result is adopted only when the loop comes out simple.
  • Instrumentation that answered a design question with a measurement: the locked constraint crossings turn out to be far apart in 3D, so the proposed splitting backstop would have fused genuinely distant geometry. It was declined on the evidence rather than shipped on the hunch.
0.10.1

Knot-aware B-spline edges

Changed
  • B-spline edges were sampled at a fixed multiple of their control-point count, silently ignoring the chord tolerance — over-sampling bland curves and under-sampling clustered-knot ones. They now seed at the curve's distinct knots, so continuity breaks become sample points, and bisect while the chord sag or the turn angle exceeds tolerance. Eight more watertight files, no regressions, 30–43 % fewer triangles on spline-heavy parts, and threaded-part volumes moving toward the reference kernel reference.
0.10.0

The audit release

A multi-agent expert audit of the whole STEP → mesh pipeline, followed by eight implementation waves. Watertight meshes went from 274 to 299 of 403 corpus files, results became deterministic (byte-identical corpus runs), the triangulator became jitter-free on exact predicates, and the harness got twice as fast.

Changed
  • The triangulator was rewritten around walk-based point location and connected cavity carving. The previous insertion scanned every triangle (quadratic build) and carved the cavity by a global test with no connectivity check — one predicate misfire on a distant triangle silently produced overlapping triangles. Fifteen more watertight files; one bolt goes from 15 988 boundary edges to 13, a hex nut from 4168 to 0, and one part's tessellation from 93 seconds to 19.
  • Giant-boundary faces are no longer dropped: the point cap rose from 8 000 to 100 000 and over-cap planar faces route through the triangulator instead of being skipped. One connector board falls from 190 417 boundary edges to 45.
  • Closed B-spline surfaces get seam handling. Every spline face had been tessellated as non-periodic, so a surface that closes on itself — extruded gears, bottles, revolved bodies exported as NURBS — had no seam treatment at all. A timing pulley drops from 1440 boundary edges to 19.
Added
  • Typed diagnostics replace plain strings on both channels, so previously silent B-rep losses — dropped face bounds, dropped coedges, substituted vertices, stalled ear clipping — are recorded. Leaky files with no explanation at all: 28 down to 21.
  • Exact triangulation predicates, now the default, with coordinate jitter retired: an adaptive double phase escalating to double-double arithmetic, plus everything jitter used to paper over — exact duplicate merging, both-sides cavity carving, splitting at exactly-collinear vertices. Five more watertight files.
  • Implicit-knot and polyline curve types as edge geometry — the single largest loader-side leak cause, at 1266 face-level losses where edges silently became straight chords.
Fixed
  • Solids with declared internal cavities were skipped entirely and no notion of a void shell existed, so hollow parts either lost geometry or counted their cavities as material. One connector's volume moved from 2.5 % over the reference kernel reference to 0.2 % under.
  • Edge endpoints are pinned to their topological vertices. Curve sampling re-evaluated endpoints, so a vertex sitting off-curve by more than the weld tolerance — STEP files routinely carry a looser vertex tolerance — gave adjacent edges two different mesh vertices at their shared junction: a guaranteed crack.
  • Tessellation is deterministic. Hole repair visited loops in dictionary order, so the fill commit order — and with it the corpus watertight total, 270 or 274 depending on the run — drifted between runs. Two consecutive full-corpus snapshots are now byte-identical.
  • Degree-based angle units were silently ignored, so cone semi-angles authored in degrees were read as radians; 71 corpus files declare degrees. Flattened assembly meshes became topologically sound: vertices weld per instance, mirrored instances flip winding, and metrics are computed on welded topology instead of reporting every sharp edge as a boundary.
0.9.0

Instancing, LOD and PMI

Four opt-in capabilities for large assemblies and manufacturing data, each additive and each shipping with zero watertight regressions and zero volume drift.

Added
  • Instanced parts: one mesh per distinct part plus its per-occurrence world transforms, instead of a flattened body. A 62-part, 408-occurrence board assembly drops to 22 % of the flat vertex memory — losslessly.
  • Decimation to a triangle budget for heavy assemblies, by linear-time vertex clustering: 1.9 million triangles down to 66 thousand. Opt-in; the full-resolution mesh is untouched.
  • Graphical PMI: AP242 tessellated annotation geometry becomes 3D polylines — dimension lines, leaders, symbol outlines — which the renderer can overlay unlit.
  • Semantic, machine-readable PMI parsed from the AP242 graph: dimensions with nominal values and asymmetric tolerance bands, geometric tolerances (flatness, perpendicularity, position, profile and more) with zone magnitude and referenced datums, and datum-feature labels. Verified against the NIST PMI test suite.
0.8.0

Mass properties and USDZ

Added
  • Volume, surface area, centre of mass and the inertia tensor of a closed solid, by the tetrahedron-covariance method, validated against an analytic sphere — enough for quoting and manufacturability checks.
  • USDZ export for AR Quick Look on Apple platforms.
Fixed
  • A curvature-adaptive interior grid for freeform faces, sized to each face's sagitta instead of a fixed grid, so a flat face gets no interior points at all. An 0402 resistor drops from 4952 triangles to 328; the corpus total falls 27 %, from 13.6 to 9.9 million — which is felt directly on device.
0.7.0

Short-form entities and metre-scale models

Added
  • Short-form STEP entity names — the abbreviated ISO type names — canonicalised at parse time, plus faceted breps and polygonal loops. Files that used to fail with “no solid found” now load.
Fixed
  • A relative-deflection floor caps absurd triangle counts on metre- and kilometre-scale models: a 732-metre part drops from 8.4 to 1.6 million triangles, while parts up to about half a metre are unchanged because the absolute tolerance still dominates.
Tooling
  • A differential harness comparing StepKit's mesh against an independent reference kernel's, file by file — the instrument most of the geometry fixes in the following releases were found with.
0.6.0

Pcurve-driven NURBS tessellation, opt-in

Added
  • A NURBS face boundary's parameter values can be read from the STEP pcurve through a one-dimensional search — no two-dimensional inversion, so no branch-jump tangle — and are used only when the resulting loop comes out simple. It cuts hairline cracks on freeform parts sharply (one button −20 % boundary edges) but perturbs a few already-watertight files, so it stays off by default. Shipping it honestly beats shipping it on.
0.5.0

Assembly placement

Added
  • Per-occurrence transforms compose down the assembly tree, so a part used N times is instanced N times at the places it actually belongs — instead of all copies collapsing onto the origin.
0.4.0

The product and assembly tree

Added
  • Product definitions and usage occurrences resolve into a named structure tree, with each leaf part associated to its faces — what a structure browser and per-part show/hide are built on. Geometry unchanged.
0.3.0

Colours

Added
  • Presentation colours resolve per face — a body base colour plus face-level overrides, including the ISO predefined colour names — and render as per-vertex colour. A red push-button or a 3 mm LED comes out in its real colours instead of one grey blob; colourless files keep the default grey.
0.2.6

glTF import

Added
  • Read binary or JSON glTF into a mesh, walking the scene graph with node transforms — the first import format beyond STEP, and a round-trip of the exporter shipped one release earlier.
0.2.5

Mesh export: STL, OBJ, glTF

Added
  • Lossless export to binary STL, Wavefront OBJ and self-contained binary glTF, in millimetres and native Z-up, with a command-line tool that picks the format from the output extension. StepKit's first interop writer — until now it could only read.
0.2.4

Curved hairline cracks close

Fixed
  • Hole repair handles loops whose best-fit-plane projection self-intersects, via a centroid-fan fallback the planar ear clipper could not do. A freeform exhaust manifold closes fully; 6149 boundary edges disappear across NURBS parts. Deliberately kept moderate, so genuinely missing faces are not papered over.
0.2.3

Degenerate and duplicate triangles

Fixed
  • Zero-area and same-winding duplicate triangles are dropped at finalize — winding-sensitive, so zero-thickness sheets survive. Three more watertight files and 42 730 fewer non-manifold edges across the corpus.
0.2.2

Post-tessellation hole filling

Added
  • Small open boundary loops on closed shells are triangulated shut, closing hairline cracks regardless of the per-face cause, and guarded so the repair can never create a non-manifold edge. Four more watertight files, 8940 fewer boundary edges across 60-plus files.
0.2.1

Weld per shell, not globally

Fixed
  • Coincident or duplicated solids — a part stored as two separate breps, common in pipe and duct reductions — fused into a spuriously non-manifold mesh. Vertices now weld per shell and watertightness is measured per solid: nineteen more watertight files, from 235 to 257 of 403, with no regressions.
0.2.0

Watertightness and render quality

Added
  • Flip-based constraint recovery in the triangulator, plus curved seam-rim and NURBS-revolution stitching: 235 of 403 corpus files come out watertight, with no regressions.
  • Crease-aware normals — smooth within a surface, hard across sharp edges — so chamfers lose their dark shading triangles while cylinders and fillets stay smooth.
  • The regression gate: snapshot the corpus, check every later build against the golden baseline. Every release since is measured against it.
0.1.0

First tagged release

The pipeline end to end, written from scratch: parse a STEP file, resolve it into a B-rep, tessellate it into a shared-edge mesh, render it with Metal.

Added
  • An ISO 10303-21 tokenizer and parser building the raw entity graph, including complex records and references.
  • A resolved B-rep model and a loader mapping AP203, AP214 and AP242 files to geometry and topology, with unit resolution and shared edge and vertex memoization.
  • Analytic surfaces — plane, cylinder, cone, sphere, torus, surfaces of extrusion and of revolution — plus NURBS curves and surfaces with de Boor evaluation and Gauss–Newton point inversion.
  • An own constrained Delaunay triangulator and an ear-clipping triangulator with linear-time hole bridging. No third-party CAD or meshing library, at any point.
  • Watertight tessellation by construction: every topological edge is discretized once and shared, so adjacent faces meet on bit-identical points, and winding is made globally consistent and outward.
  • Metal rendering with runtime-compiled shaders (no offline toolchain), an interactive viewer, a headless PNG renderer, and the corpus inspection tool.

Still pre-1.0.

While StepKit is on 0.x the public API can change between minor versions; breaking changes are called out under Changed. If you are building something that needs native STEP, get in touch — I'll keep you posted on where it goes next.