Changelog
All notable changes to OCCTSwift.
Current: v1.7.7
| **macOS / iOS / visionOS / tvOS | OCCT 8.0.0p1** |
Release History
v1.7.7 (June 2026) — cookbook: Gordon surfaces (#229)
Documentation only — no code, API, or xcframework change. New cookbook page on Gordon surfaces — skinning a surface through a network of crossing profile + guide curves via Surface.gordon / Surface.gordonReport (GeomFill_Gordon). Covers the grid-closure requirement, build diagnostics (GordonResultStatus, allowApproximateFallback), the lower-level networkSurface (GeomFill_NetworkSurface) and its knot-alignment caveat, and a Gordon-vs-loft-vs-fill decision table. Snippets compile-checked against the shipped API; figure rendered from the same network the page shows.
v1.7.6 (June 2026) — cookbook complete: healing, meshing, XCAF, topology (#210, #228)
Documentation only — no code, API, or xcframework change. Adds the final four cookbook areas, completing the issue #210 area list (the Swift-API counterpart to OCCT’s own user guides). Every snippet was compile- and run-checked against the shipped API.
- Healing & Validity —
isValid/isValidSolid/isSelfIntersecting,analyze,signedVolume+orientedForward, the repair ops (healed/fixed/unified/upgraded), sewing, and free-boundary gap finding/closing. - Meshing & Export —
mesh(linearDeflection:)+MeshParameters, theMeshtype,mesh.toShape, a deflection table, and STL / OBJ / PLY / STEP / IGES / BREP / glTF export + import with a round-trip. - XCAF Assemblies —
Documenttrees, components & instancing, names / colors / materials, and structured STEP / GLB round-trip (with a two-colour assembly figure). - Topology Graph —
TopologyGraphnode counts, adjacency / shared edges /sameDomainFaces, durableGraphUIDs (vs ephemeralNodeRef), and history tracking through operations.
v1.7.5 (June 2026) — threadedRod from a custom profile + helical-sweeps cookbook (#225)
Additive, source-compatible. New Shape.threadedRod(customProfile:nominalDiameter:pitch:cutDepth:length:…) builds a smooth worm/screw from a custom radial tooth profile directly — composing the helicoid with the core by sewing, with no boolean — yielding a BRepCheck-valid, analytic solid (a handful of B-spline faces → a sub-MB STEP).
This addresses #225: helicalSweep + union/subtract against a coaxial cylinder produces an invalid (union) or collapsed-to-zero (subtract) result that no fuzzy value or heal pass recovers — OCCT’s BOP can’t resolve the coincident/tangent helicoid faces (consistent with #213, #181). The boolean compose path was never the way; the direct build is. The custom-profile direct build already existed under threadedShaft(spec:) with a ThreadSpec(customProfile:) — threadedRod makes it a discoverable one-liner and never silently falls back to an invalid boolean (returns nil instead).
ThreadProfile.supportsSmoothRodBuild— public predicate (real crest flat, ≤ 2 flanks) for whether a custom profile can take the direct build.Shape.helicalSweep(…)doc now warns against the boolean-compose anti-pattern and points tothreadedRod.- Cookbook: Helical Sweeps — new page (
helicalSweephelicoids vs.threadedRodworms, and why the boolean compose fails), with rendered figures.
v1.7.4 (June 2026) — docs: cookbook lofting & sweeps, context7 onboarding
Documentation only — no code, API, or xcframework change.
- Cookbook: Lofting & Sweeps (#226) — new example-rich page covering extrude, revolve, sweep-along-path, loft (square→round, ruled vs smooth, point-capped cones), and multi-section pipe shells, with a “loft vs multi-section sweep — which?” decision section. Every snippet is compile- and run-checked against the shipped API; four figures (pipe elbow, frustum, cone, vase) rendered headlessly as PNG posters + interactive
<model-viewer>GLB models. - context7 onboarding (#224) — added
context7.jsonscoping context7’s crawl to the Swift API (docs/,Sources/OCCTSwift) with usage rules, so the Swift surface becomes queryable on context7 (issue #210). - WebAssembly feasibility plan (#223) —
docs/wasm-feasibility.md: analysis + phased plan for reusing the OCCTSwift API in a SwiftWasm app (deferred; the wasi-sdk-vs-Emscripten ABI split is the central obstacle).
v1.7.3 (June 2026) — smooth fine-pitch internal threads (#219)
Bug fix. threadedHole on a fine-pitch internal thread (e.g. 3/8-16 UNC, M10×1.5) came out faceted. The ruled:false smooth helical cutter self-intersects in a degenerate band around the default ~14 sections/turn — the axial step per section is far smaller than the groove’s axial half-width, so consecutive sections overlap many-deep and the lofted B-spline pinches, making the boolean a no-op that silently fell back to the faceted cutter. The cut path now builds the smooth internal cutter at a denser, escalating section count (24→36/turn) and takes the first sound cut; the faceted cutter remains the fallback for genuinely awkward composite bodies. Fine-pitch internal threads now cut smooth (the wing-nut cookbook bore drops from ~247 faces to ~15). No API change.
v1.7.2 (June 2026) — thread envelope fix (#222)
Additive, source-compatible. Shape.threadedShaft(…) gains a build: ThreadBuild = .auto parameter. At coarse pitch / wide crest flats the smooth direct rod build (#213) bows the crest past the nominal major radius (+14–21% measured: M12×1.75 → r 6.85 vs 6.0; Tr12×3 → 7.28), which oversizes headless single-start parts (lead screws, studs, worms). build: .boolean forces the boolean cut path — cutter subtracted from a cylinder of radius exactly nominalDiameter / 2, so the crest is clamped in-envelope (≤ nominal, ~1% tessellation margin). .auto (default) and .direct keep the original smooth build. No existing call sites change.
v1.7.1 (June 2026) — p1 follow-ups + xcframework header hygiene
Additive + a packaging fix. New p1 operations and a corrected xcframework (no stale headers).
New operations
- BRepGraph durable identity —
TopologyGraphUID/RefUID/ItemUID accessors (uid(ofNodeKind:index:),node(forUID:),contains(uid:), ref/item variants,generation) overBRepGraph::UIDsView, giving persist-safe identifiers (the migration note’sUID/RefUID/ItemUID, vs the non-durable NodeId/RefId). Surface.networkSurface(profiles:guides:tolerance:)— wraps the newGeomFill_NetworkSurfacelow-level Gordon builder, with aNetworkSurfaceStatus.Surface.gordonReport(…)— exposesGeomFill_Gordon’s newStatus()/IsApproximate()and theExactOnly/approximate-fallbackApproximationMode(GordonResult+GordonResultStatus).Polygon2D.copy(),PolygonOnTriangulation.copy()/setNodes()/setParameters()— the newPoly_*copy/mutator APIs.- BRepGraph reads, now real:
faceSameDomain(of:)(derived from edge-incidence + surface equality), face/edge adjacency & shared-edges (derived from first-class reverse relations),faceIsNaturalRestriction(Tool::Face::NbWires == 0). - BRepGraph vertex-supplement:
faceAddVertex/edgeAddInternalVertex/faceRemoveVertex/faceNbVertexRefsnow back onto theBRepGraph_LayerTopoSupplementlayer (uid/shape-based; the v1.7.0 stubs were no-ops).
Packaging fix — stale headers removed from the xcframework
build-occt.sh reused the CMake install prefix across builds; cmake --install adds headers but never deletes removed ones, so 18 OCCT 8.0.0-GA headers that p1 removed/renamed (e.g. Approx_BSplineApproxInterp.hxx, BRepGraph_Builder/History/RepId/MeshCache/LayerRegularity.hxx, GeomFill_GordonBuilder.hxx) leaked into the v1.7.0 framework, where they masqueraded as current API (their symbols were never in the library). The build script now wipes the install prefixes each run, and the v1.7.1 xcframework contains only real p1 headers. (Functionally harmless in v1.7.0 — the phantom headers had no symbols — but misleading.)
Note on edge regularity/continuity: one of those phantom headers (
BRepGraph_LayerRegularity) made it look like a graph-level regularity API existed in p1. It does not (p1 shipsBRepGraph_LayerParametricinstead);TopologyGraph.edgeMaxContinuity/setEdgeRegularityremain no-ops. UseShape.maxContinuity(BRep_Tool::MaxContinuity) for edge continuity.
v1.7.0 (June 2026) — OCCT 8.0.0p1 upgrade; BRepGraph realigned to its redesigned model
MINOR — dependency upgrade with API-behaviour changes confined to the BRepGraph domain. OCCT shipped 8.0.0p1 as a hot patch on top of 8.0.0. OCCTSwift now pins it (V8_0_0_p1). Everything outside BRepGraph is a transparent upgrade; BRepGraph itself was comprehensively redesigned upstream and our wrapper has been realigned to the new model rather than shimmed back to the old one.
Upstream fix landed
Our BRepFill_CompatibleWires::SameNumberByPolarMethod() polar-iterator guard (OCCTSwift #176 — the loft/ThruSections SIGSEGV on mismatched closed profiles) shipped in 8.0.0p1. The source patch we carried (Scripts/patches/0001-…) is therefore removed; build-occt.sh pins OCCT_RC="p1".
Removed/changed OCCT classes migrated (non-BRepGraph)
Approx_BSplineApproxInterp(removed) →BSplineApproxInterpis reimplemented onGeomAPI_PointsToBSpline(the documented replacement). The C/Swift ABI is unchanged, butnbControlPointsis now advisory (the approximator chooses the pole count to meet tolerance) andinterpolatePoint(_:withKink:)is a no-op (no per-point exact-interpolation/kink control in the replacement).maxErroris computed by projecting the inputs onto the fitted curve.GeomFill_Gordon(reworked) — API remained source-compatible; no wrapper change.BRepGraph_RepIdmoved to theBRepGraphIncsubpackage (headerBRepGraphInc_RepId.hxx).
p1 crash fixes (OS-signal null-derefs that catch(...) cannot trap)
Extrema_ExtElCS(line ∥ cylinder axis) — infinite/degenerate extrema crash.ExtremaElCS.lineToCylindernow returns 0 when the line is parallel to the cylinder axis.ShapeUpgrade_WireDivide/ShapeFix_ComposeShell— p1 made theShapeBuild_ReShapecontext mandatory;Perform()null-derefs without one. Both bridges now set a context (plus WireDivide guards a wire whose edges have no pcurve on the target face).Wire.rectanglewith sub-Precision::Confusion()dimensions made degenerate edges that crashed downstream; such dimensions are now rejected (returns nil).
BRepGraph realigned to the 8.0.x model
BRepGraph is OCCT’s explicit graph-oriented topology model (see Open-Cascade-SAS/OCCT discussion #1291). 8.0.0p1 reworked it around nine separated concerns — topology definitions vs references/usages, geometry reps, mesh reps, products/occurrences, persistent UIDs, metadata layers, modification stamps (version counters, not booleans), and self-invalidating caches. The wrapper was rewritten to that model. Upstream notes the interface “will change slightly in 8.1 and in development versions after 8.0,” so expect further churn here.
Concretely:
- Shape ingestion:
BRepGraph_Builderremoved →BRepGraph::ShapesView::Add(). - History:
BRepGraph::History()removed → the registeredBRepGraph_LayerHistorylayer (LayerRegistry().FindLayer<>()/.Ensure<>()); records areEvents. - Topology queries moved across views: counts to
Topo().Geometry().NbFaceSurfaces()etc.;IsBoundary/IsManifold/FindCoEdgeIdtoBRepGraph_Tool::Edge;SameParameter/SameRangetoBRepGraph_Tool::CoEdge(per-coedge, derived). Edge→faces / vertex→edges are first-class reverse relations (FacesOf,VertexOps::Edges); face/edge adjacency and shared-edges are derived from them (no direct adjacency call survived, but the data does). - Mesh + geometry representations are handle-based: integer “rep ids” are gone. The wrapper keeps its rep-id Swift API working via a per-graph handle registry that backs the new
Mesh().Editor().Faces().SetCachedTriangulation(face, handle)/ persistent-rep setters. Mesh cache inspection readsMesh().Cache().*.Entry()(each holds a single handle + aMeshGenerationstamp). - Edge start/end vertex now resolves a
VertexRefId(a per-edge use) to its vertex definition. - Root products require explicit
AppendDocumentRoot()after creation.
Deliberately-removed concepts (now no-ops or derived-getter-only — by design, not breakage)
These reflect BRepGraph’s intent; the capability lives elsewhere in the new model:
- Flags are derived from geometry, not stored →
SameParameter/SameRange/Degenerated/IsClosedsetters are no-ops; the getters return the live derived value. - Regularity/ownership are controlled layers, not inline flags → the old
SetEdgeRegularity/EdgeMaxContinuityinline path is gone. - Natural-bound faces are normalized away (explicit topology is required below a bounded face) →
…NaturalRestrictionget/set no longer apply. - Locations live on assembly references (occurrence/child), not per-subshape → the per-vertex/edge/ wire/face/shell/solid/coedge
…RefLocalLocationsetters are gone; occurrence/child placement setters remain. - Coedges are first-class (a coedge is the edge-on-face use, carrying orientation/pcurve/seam) → the coedge-as-separate-reference setters are gone;
NbCoEdgeRefsreports the coedge count. - Vertices are references with reverse relations → face/edge vertex add/remove mutators are gone (population builds them); query via the reverse relations instead.
Test/behaviour notes
GC_MakeHyperbola(3-point) is stricter in p1: a collinearS2(zero minor radius) is rejected; the test now uses a valid off-axisS2.- Run the suite with
swift test --no-parallel— the pre-existing non-deterministic NCollection arm64 race makes the parallel run flaky (unrelated to p1).
v1.6.3 (June 2026) — buttress trued to DIN 513; Whitworth & knuckle finished
PATCH — geometry corrections, non-breaking. The last two medium-confidence thread forms are trued to their standards:
.buttress→ DIN 513 (German Sägengewinde): asymmetric 3° load / 30° clearance flanks (33° total) at depth 0.86777·P (so the bolt cored3 = d − 2·0.86777·P, verified against the DIN 513 table — e.g. S 10 × 2 → d3 = 6.528). Previously it used a reconstructed ANSI 7°/45° profile at 0.66271·P, which matched no German standard..whitworth/.bspParallelconfirmed at the correct 55° / 0.640327·P and kept as the standard BS 84 flat-truncation (crest = root flat = P/6). A fully rounded crest makes the deep tooth’sruled:falseloft spike past the nominal radius (a thin outward flap, OCCTSwift #213), so the truncation is the form that builds smooth and dimensionally exact..knucklenow routes through the faceted cut path for the external build. The previous rounded-crest direct loft was both slow (~28 s) and bulged ~6% past the nominal crest; the cut path keeps the crest exactly at the nominal radius and builds in ~1 s. (Rounded profiles — those with more than two straight flanks — are now detected and sent to the cut path generally.)
Buttress cookbook figure re-rendered with the DIN 513 profile.
v1.6.2 (June 2026) — knuckle thread trued to DIN 405
PATCH — geometry correction, non-breaking. The .knuckle form now matches DIN 405: depth 0.55·P (so the bolt minor d3 = d − 1.1·P, verified against the standard dimension table — e.g. Rd 8 × 1/10″ → d3 = 5.460) and a proper 30°-included (15° per side) flank with circular-arc rounded crest and root (the rounding radius is solved for flank tangency). Previously it used a cosine profile at 0.5·P (≈60°-included flanks). A small crest/root land is retained so the smooth direct build still applies.
v1.6.1 (June 2026) — smooth internal threads
PATCH — quality improvement, non-breaking. threadedHole now produces smooth internal threads instead of faceted ones. An interior helix is cut into a thick wall (not a thin shaft), so OCCT’s boolean subtracts a smooth (ruled=false) helical cutter robustly — verified valid across all orientations. (The external fallback is unchanged: subtracting a smooth cutter from a thin external cylinder is the unreliable case from #213, so non-cylinder/tapered external cuts stay faceted.) Cookbook nut / wing-nut / lead-screw figures re-rendered with the smooth bore threads.
v1.6.0 (June 2026) — thread forms + custom profiles
MINOR — additive, non-breaking (existing ThreadSpec/threadedShaft calls are unchanged).
The thread feature now covers the common standard forms beyond the 60° V, and can thread a cylinder with any cross-section:
- New
ThreadFormcases:.whitworth/.bspParallel(55°),.acme(29°) /.trapezoidal(metric Tr, 30°),.square,.buttress(7°/45°),.knuckle(rounded),.nptTapered/.bsptTapered(60°/55° on a 1:16 taper), and.custom. (UNF/UNC, metric-fine, and SAE remain pitch/standards variants of the existing 60° forms — no new cases needed.) ThreadProfile— a public,Codablenormalized tooth cross-section (vertices ofaxial0…1 ×depth0 = crest … 1 = root).ThreadSpec(customProfile:nominalDiameter:pitch:cutDepth:)threads a cylinder with an arbitrary shape. Built-in form profiles are exposed too (.iso60V(),.acme29,.square, …).- Geometry is now form-dependent:
ThreadSpec.cutDepth/profile/taperRatioswitch on the form. ISO/Unified compute identically to before (5H/8, P/8 crest, P/4 root, 30° flanks). - All forms work external and internal: external cylinders use the smooth, BRepCheck-valid direct build (#213) — a handful of faces; internal threads (
threadedHole), non-cylinder targets, and the tapered pipe forms use the robust faceted cut path. The OCCT bridge is unchanged (a thin wrapper); all new geometry is composed in Swift. - Parser recognises
Tr40x7[LH],1.5-4 ACME,G1/2(BSP),R…/Rc…(BSPT),W1/2/1/2 BSW(Whitworth), and1/2-14 NPT, alongside the existing metric/Unified designations.
Cookbook: the Threads page gains a forms gallery and a custom-profile example.
v1.5.3 (June 2026) — smooth, valid ISO V-threads built without booleans (closes #213)
PATCH — additive, non-breaking (same threadedShaft API; smoother/valid result).
Shape.threadedShaft(form: .iso68) produced a near-square groove (~6.6° flanks) instead of a true 60° V (30° flanks): the cutter’s flank offsets used the crest/root truncation flats and omitted the cutDepth·tan(30°) flank term. Fixing the profile, however, exposed a deeper limit — OCCT’s boolean engine cannot reliably subtract a smooth helical V-thread cutter from a cylinder (it under-cuts / no-ops on ~half of all orientations, unfixable by bleed / fuzzy / cone / extend; only the faceted screw-loft is robust, because its planar facets cross the shaft transversally).
So threadedShaft now builds the threaded rod directly, with no boolean, when the target is a plain cylinder coaxial with the axis (the common case):
- The thread region is a
ruled=falseThruSections loft of the thread’s true cross-section (“cam”: root arc → flank spiral → crest arc → flank spiral) at z-slices rotated by the helix — one BSpline face per cam edge (~9 faces, not hundreds of facets), flat caps, solid-to-axis. - Any unthreaded margin is closed by pure sewing — a single-loop shoulder face + plain cylinder + end disk — not a fuse (a fuse is robust here but 6–71 s; sewing is ~0.3 s).
Because the kernel’s BOP is never invoked, the result is orientation-robust AND BRepCheck-valid where the old cut path was faceted or failed. The boolean cut path remains the fallback for non-cylinder targets, internal threads (threadedHole), and multi-start. The whole construction is composed in Swift from already-wrapped primitives (Shape.loft(ruled:), Wire.arc/.interpolate, Shape.face(from:), Shape.sew, Shape.solidFromShell), so the OCCT bridge stays a thin wrapper — no thread-specific bridge code.
Note: the smooth thread is a BSpline solid, so its default
Bnd_Boxis the control-pole hull and overshoots the true surface by ~13% (a pole artifact, not a bulge); useboundingBoxOptimal()for the real extent (the crest sits exactly at the nominal radius).
v1.5.2 (June 2026) — reconstruction wrapping gaps: outer shell, mesh quality flag, wire arc-length adaptor (closes #211)
PATCH — additive, non-breaking. Closes the confirmed gaps from the mesh→CAD reconstruction coverage audit (#211):
Shape.outerShell→Shape?(BRepClass3d::OuterShell) — the outer body shell of a solid, distinguishing it from internal void shells.nilfor non-solids. Decomposes a part into outer-body + cavities.MeshParameters.allowQualityDecrease(IMeshTools_Parameters::AllowQualityDecrease, defaultfalse) — the one missing mesh knob. Lets a re-mesh at a different deflection actually replace an existing finer triangulation (e.g. a deviation re-measure), instead of OCCT silently keeping the coarser/finer mesh.WireCurve(BRepAdaptor_CompCurve) — treats a multi-edge wire as one arc-length curve:length,point(atAbscissa:)/tangent(atAbscissa:)(walk across edge boundaries),points(count:)/points(spacing:)for even arc-length sampling (GCPnts_UniformAbscissa), plus nativeparameterRange/point(atParameter:)/tangent(atParameter:). Replaces ad-hoc per-edge sampling when placing sections along a measured wire.EdgeCurve(BRepAdaptor_Curve) — the single-edge sibling ofWireCurve: adds the arc-length side (length,point(atAbscissa:),points(count:/spacing:)) thatEdge’s nativepoint(at parameter:)lacked.Shape.innerShells— the void/cavity shells of a solid (every shell exceptouterShell); pairs withouterShellto fully decompose a part into outer body + cavities.
Also from #211, verified and not needing changes: Shape.minDistance(to:) -> Double? already exists; and a “scattered point-cloud” GeomAPI_PointsToBSplineSurface fit is not an OCCT capability — every constructor is grid-based (Array2); a cloud fit means resampling to a grid (already wrapped via Surface.fromPointGrid) or GeomPlate / BRepOffsetAPI_MakeFilling (already wrapped). Source-only (no xcframework change).
v1.5.1 (June 2026) — Shape.isSelfIntersecting(timeout:) — bounded self-intersection check (closes #208)
PATCH — additive, non-breaking. Follow-up to #206. isValidSolid is a topology-level check (BRepCheck_Analyzer) that misses global self-intersection — a self-intersecting B-spline solid from loft(ruled: false) can report isValidSolid == true yet poison downstream booleans. New:
func isSelfIntersecting(timeout: Double = 30) -> Bool? // true / false / nil(=indeterminate)
Backed by BOPAlgo_ArgumentAnalyzer’s self-interference test (stop-on-first-faulty), wrapped in the same wall-clock watchdog as the #206 booleans so it can’t hang: returns true (self-intersects), false (clean), or nil if it couldn’t finish within timeout (indeterminate — treat as “unknown”, not “clean”). The test is expensive (seconds on B-spline solids), so it’s opt-in. Verified on the #206 operands: nurbs_env → true (the actual culprit), and the docs give the validate-at-source recipe (orientedForward() + isSelfIntersecting() == false).
Why not a cheap volume/isValidSolid guard (the issue’s other options): investigation showed the reported env operand passes BRepCheck, sits within its bounding box, and has positive volume — nothing cheap flags it. And a volume <= 0 reject would false-positive on legitimately reversed-orientation solids (a known, orientedForward()-fixable case), so it isn’t sound. isValidSolid’s doc now spells out the topology-vs-self-intersection distinction. Source-only.
v1.5.0 (June 2026) — boolean ops are time-bounded; never hang indefinitely (closes #206)
MINOR — additive param + a default-behavior change. Shape.union / subtracting / intersection could hang indefinitely on a self-intersecting / inside-out operand — e.g. a B-spline solid from loft(ruled: false) that reports isValidSolid == true yet poisons the boolean. BRepAlgoAPI_Cut on the reported operands spun for >5 min on a 66-face input.
The boolean ops now run under a wall-clock watchdog (OCCT’s Message_ProgressRange + UserBreak) and return nil at a deadline instead of spinning forever:
func union(_ other: Shape, fuzzyValue: Double = 0, glue: BooleanGlue = .off,
timeout: Double = Shape.defaultBooleanTimeout) -> Shape? // and subtracting / intersection
timeout— seconds; defaultShape.defaultBooleanTimeout(120s).0/negative = unbounded (the prior behavior). Verified to interrupt the real #206 operands (was an infinite hang → nownil).- Default-behavior change: a boolean that genuinely runs longer than 120s now returns
nilinstead of completing/blocking. Pathological hangs are bounded; raisetimeout(or pass0) for legitimately heavy booleans.
Why a timeout and not an operand pre-check: the cheap detectors don’t catch the reported env operand — BRepCheck_Analyzer reports it valid and its volume sits within its bounding box; only BOPAlgo_ArgumentAnalyzer flags it, and that itself ran >50s on the input. The watchdog is the only general, bounded guard. (The separate cav operand has negative volume, so a downstream volume > 0 && analyzeValidity(geometryChecks:) gate remains a useful cheap fast-fail and is still recommended.) Source-only (no xcframework change).
v1.4.7 (June 2026) — boolean fuzzy value + glue options (closes #202)
PATCH — additive, non-breaking. Shape.union / subtracting / intersection now expose the two BRepAlgoAPI_BooleanOperation robustness levers OCCT provides for coincident / near-tangent faces, where the default boolean can silently under-subtract or inflate volume:
func union(_ other: Shape, fuzzyValue: Double = 0, glue: BooleanGlue = .off) -> Shape?
// same trailing parameters on subtracting(_:) and intersection(_:)
fuzzyValue→SetFuzzyValue(tolerance-based fuzzy boolean;0keeps OCCT’s default, negatives ignored).glue→SetGlue— newShape.BooleanGlueenum:.off(default),.shift(BOPAlgo_GlueShift),.full(BOPAlgo_GlueFull). Gluing hardens & speeds up unions/cuts of solids known to share coincident faces (e.g. consecutive analytic loft chunks, thin-wall shells).
Defaults reproduce prior behavior exactly. Implemented via a shared templated bridge driver (OCCTShapeUnionEx/SubtractEx/IntersectEx) over the common BRepAlgoAPI_BooleanOperation base. Source-only (no xcframework change).
v1.4.6 (June 2026) — instanced-assembly STEP writer (closes #173)
PATCH — additive, non-breaking. New Exporter.writeSTEPAssembly(_ document: Document, to url:) writes an XCAF Document as a product-structured STEP assembly: each unique part label becomes one STEP product, referenced by its located component occurrences (NEXT_ASSEMBLY_USAGE_OCCURRENCE + each component’s TopLoc_Location). A part placed N times stores one MANIFOLD_SOLID_BREP, not N copies — file size scales with unique parts, and the result opens as an editable assembly in standard CAD viewers (AP214). Names/colors set on the document are preserved.
The underlying capability already existed (Document.writeSTEP transfers the XCAF doc via STEPCAFControl_Writer, and full rotation+translation placement landed in #174); this adds the named, documented, throwing convenience entry point #173 asked for, plus instancing + round-trip tests.
v1.4.5 (June 2026) — mesh→shape weld tolerance is caller-tunable (#197)
PATCH — additive, non-breaking. Mesh.toShape() sewed its triangles into a shell at a hardcoded 1e-6 weld tolerance. That tolerance must scale with the mesh’s coordinate magnitude — too small for a large-coordinate (or imprecise, imported) mesh leaves shared edges unmerged and silently yields an open shell. It now takes weldTolerance: Double = 1e-6 (the default reproduces prior output); non-positive values return nil. From the #197 hardcoded-constant sweep — the audit (see issue) found this the one remaining genuine knob; the rest of the 1e-X literals are internal correctness epsilons left as-is.
v1.4.4 (June 2026) — mesh deflection is caller-tunable on auto-meshing utilities (#197)
PATCH — additive, non-breaking. Several utility functions auto-triangulated their input at a hardcoded 0.1 mm deflection, leaving callers no control over fidelity/speed. Each now takes a deflection: Double = 0.1 parameter (the default reproduces prior output). First slice of the #197 hardcoded-constant sweep — the mesh deflection area:
Shape.writeSTLBinary(to:deflection:)/writeSTLAscii(to:deflection:)— STL export resolution.Shape.proximityFaces(with:tolerance:deflection:)— proximity triangulation.Shape.selfIntersectionPairs(tolerance:maxPairs:deflection:)— self-intersection triangulation.CoherentTriangulation.createFromMesh(_:deflection:).
(The primary STL path Exporter.writeSTL(shape:to:deflection:) already exposed this.) Source-only; remaining #197 areas — tolerances, sampling counts — tracked in the issue.
v1.4.3 (June 2026) — fast 2D drawings of threaded solids via polyhedral HLR (closes #196)
PATCH — additive + guidance. The v1.4.1 smooth analytic thread helicoid is HLR-hostile under OCCT’s exact HLR (hlrEdges / HLRBRep_Algo): projecting its BSpline faces computes analytic helical silhouettes and blows up — a downstream 2D-drawing pipeline measured ~19× slower vs the v1.4.0 faceted thread.
The fix is not to change the solid. OCCT’s polyhedral HLR (hlrPolyEdges / HLRBRep_PolyAlgo, already wrapped) projects the shape’s triangulation, so it is fast on any surface — measured ~48× faster than exact HLR on an analytic M10 thread (337 ms vs 16.4 s, side view) — while the one analytic solid stays smooth for STEP. Prefer hlrPolyEdges for 2D drawings of threaded / curved solids; reserve exact hlrEdges for analytically simple shapes.
hlrPolyEdges(direction:category:deflection:) now exposes the internal mesh deflection (mm, default 0.1) so drawing pipelines can trade fidelity (more, shorter edges) for speed. Non-breaking — the default reproduces prior output. (No GPU offload needed; the polyhedral CPU path already recovers the speed. The broader hardcoded-constant sweep this surfaced is tracked in #197.)
v1.4.2 (June 2026) — long full-length threads return a usable solid, not nil (closes #193)
PATCH — regression fix. A long full-length thread (threadedShaft over tens of turns, e.g. an ISO 4017 M10×50 full-thread shank ≈ 49 turns) came back nil. No API change.
Cause. v1.4.1’s soundness gate required Shape.isValid. For a long thread, the two cutter paths both fail that gate: the smooth analytic cutter is BRepCheck-valid but, when wound over ~40+ turns, OCCT’s boolean degenerates to a near-no-op (the result keeps ~the full blank volume — no groove cut); the faceted screw-loft fallback does cut the groove correctly but trips BRepCheck on a benign facet self-intersection (isValid == false) — exactly the #193 symptom. With both rejected, the method returned nil.
Fix. Soundness is now judged on geometry, not BRepCheck: the cut must stay inside the blank (tight/optimal envelope) and remove a sane fraction of the volume. isValid is no longer a gate. The analytic no-op is still rejected (it removes ~0 material → fails the volume check), so a long thread falls through to the faceted screw-loft and is returned — dimensionally correct and STEP-exportable, as the downstream reporter confirmed. Short/medium threads still get the smooth analytic helicoid and remain isValid == true; only the long faceted fallback is allowed to be invalid-but-usable.
v1.4.1 (June 2026) — smooth analytic thread helicoid, with screw-loft fallback (#187)
PATCH — geometry quality, no API change. threadedShaft / threadedHole now emit a smooth analytic helicoid instead of v1.4.0’s faceted ruled loft. Same signatures, same in-envelope result; the difference is surface quality and face count.
What changed. v1.4.0 swept the V-profile through ~14 screw-transformed sections per turn and ruled-lofted them — correct and in-envelope, but faceted (hundreds of flank facets) and ~1 s per thread. The cutter is now built analytically (new bridge op OCCTShapeBuildThreadCutter): the four ISO-68 V-corners each trace a single BSpline helix (GeomAPI_Interpolate), and the solid is bounded by four ruled faces between consecutive corner-helices plus two V end caps — sewn, made solid, and BRepLib::OrientClosedSolid-corrected. That’s ~6 faces, no faceting, regardless of turn count.
Automatic fallback. OCCT’s boolean chokes on the tightly-wound cutter of small, fine-pitch threads (e.g. M5×0.8 — 22.5 turns at radius 2.5): the subtraction comes back BRepCheck-“valid” but with more volume than the blank. The cut is validated (optimal/tight bounding box stays inside the blank and volume strictly decreases by a sane amount); if the analytic result fails, it silently falls back to v1.4.0’s robust screw-loft. So M6/M8/M10/M12 and coarse worm pitches get the smooth helicoid, while pathological small-fine-pitch threads still build via the faceted-but-robust path.
Why the envelope is measured on the optimal box. The smooth helicoid’s default Bnd_Box (BRepBndLib::Add) is the BSpline convex hull, which overshoots the real surface by ~0.1–0.35 mm — a control-pole artifact, not escaped material (AddOptimal returns the blank’s exact extent). Both the fallback check and the #181-C regression test now use the tight optimal box; a strict tolerance there still catches the real >1 mm balloon the guard exists for.
Migration. None required. Thread mesh/STEP geometry differs again (smoother) — byte-exact snapshot consumers must rebaseline; everything else is unchanged.
v1.4.0 (June 2026) — correct, in-envelope thread geometry (closes #187)
MINOR — BEHAVIOUR CHANGE to threadedShaft / threadedHole. The thread output geometry changes: both now produce a correct, in-envelope helicoid for every pitch, including coarse worm pitches that previously returned nil or garbage.
Why it changed. The cutter was a BRepOffsetAPI_MakePipeShell sweep of a V-profile along the helix. That sweep re-frames the section with the helix lead, so it bulged the thread outward (~1.25× cut depth for fasteners, ~3.1× for worm pitches → a self-intersecting ≈2×-radius balloon that crashed STEP export — #181-C/#185). The cutter is now built by a screw-motion sweep: the axial V-profile is transported by a pure rotate-about-axis + translate-along-axis motion (every section stays in its own axial plane), ruled-lofted, and subtracted. The result’s crest sits at the nominal radius (within ~0.1 mm tessellation), deterministically.
Migration. No API change (same signatures, still Shape?). But:
- the produced thread mesh / STEP geometry differs — snapshot/byte-exact consumers must rebaseline;
- threads that returned
nilat coarse/worm pitch now return a valid solid; - the V-form is faceted (ruled loft, ~14 sections/turn) rather than a smooth pipe surface;
- performance: ~1 s per thread (loft + boolean over the section facets). For many threads, expect it to dominate; a true analytic helical surface (future work) would remove the faceting/cost trade.
The #181-C envelope guard is retained as a thin safety net (now 1× cut depth) but effectively never trips on the in-envelope result.
v1.3.6 (June 2026) — fix: thread envelope guard rejected valid fastener threads (closes #189)
PATCH — regression fix. The #181-C envelope guard added in v1.3.4 used a tolerance (1e-3 · extent) far tighter than the bounding-box overrun of a valid threadedShaft / threadedHole result, so it returned nil for ordinary bolts/screws (M5–M10, ISO 4762/4014/…) that built in v1.3.3 — breaking 37 downstream fastener generators.
The guard’s tolerance is now 2 · cutDepth. Measured overruns (relative to the thread cut depth, which scales the corrected-Frenet sweep’s directional bulge) are ~1.25× for valid fastener threads and ~3.1× for the coarse-worm-pitch garbage the guard is meant to catch (#181-C, which balloons to ~2× radius and crashes STEP export). 2 · cutDepth sits cleanly between them — valid threads build again, the catastrophic balloon is still rejected. (The proper fix — a cutter that doesn’t bulge at all — is tracked in #187.)
v1.3.5 (June 2026) — Shape.helicalSweep worm/screw-thread helicoid (closes #185)
PATCH — additive convenience API. Adds Shape.helicalSweep(profile:axisOrigin:axisDirection:radius:pitch:turns:clockwise:solid:) (and a multi-profile overload), the turnkey form of the #180 auxiliary-spine sweep for the helical case. It builds the helix spine and a correctly-spanning central-axis auxiliary spine internally, with the orientation flags (CurvilinearEquivalence = false, no contact) that keep the swept section radial — producing a worm/screw-thread helicoid in one call:
Shape.helicalSweep(profile: rib, axisOrigin: .zero, axisDirection: SIMD3(0,0,1),
radius: 5, pitch: .pi, turns: 4.77) // crest stays radial (~Ø12), not nil
Hand-rolling this with pipeShell(mode: .auxiliary(...)) reliably returned nil because (a) Wire.helix runs toward +Z or −Z depending on handedness and (b) the auxiliary spine must span the helix’s full axial extent or the section planes never intersect it. The helper handles both. (Investigation: the correct OCCT recipe was confirmed empirically — SetMode(axisLine, CurvilinearEquivalence=false, NoContact); CurvilinearEquivalence=true and the contact modes fail to build for a helix spine.)
v1.3.4 (June 2026) — assembly/export robustness (#181 B & C)
PATCH — robustness fixes, no API change.
- STEP writer serialization (#181-B). Concurrent
writeSTEPcalls could SIGSEGV because OCCT’sSTEPCAFControl/STEPControlwriters share non-thread-safeInterface_Staticglobals with IGES. All STEP/IGES write entry points now serialize on the shared data-exchange mutex, so parallel exports queue instead of crashing. (The crash is an uncatchable signal, so internal serialization — not documentation — is the fix.) threadedShaftenvelope guard (#181-C). At coarse pitch / steep lead (and, observed here, even at bolt pitch) the helical V-cutter self-intersects and the boolean subtract returns a non-deterministic solid that BRepCheck reports “valid” yet extends well outside the blank (≈Ø22 on a Ø12 blank) — which then crashed downstream STEP export. A thread cut can only remove material, sothreadedShaftnow returnsnilwhen the result escapes the blank envelope rather than handing back garbage. Callers should fall back (e.g. a smooth-cylinder worm body).
Note on #181-A (XCAF setColor/setName on auto-created component labels): could not reproduce as an OCCT or bridge fault — XCAFDoc_ColorTool::SetColor on auto-created/reference component labels is robust in isolation, and the bridge already fails safe on unregistered labels. Left open pending a minimal reproducer.
v1.3.3 (June 2026) — multi-section pipe shell (closes #180)
PATCH — additive API. Adds Shape.pipeShellMultiSection(spine:profiles:mode:withContact:withCorrection:solid:), the multi-section form of pipeShell. Several profiles positioned along the spine are swept into a single variable cross-section solid/shell via repeated BRepOffsetAPI_MakePipeShell::Add. Supports all orientation modes including .auxiliary(spine:), so a thread rib can ramp from a runout to full crest along a helix while staying radial — the worm-thread case that single-profile pipeShellWithLaw (Frenet-only, degenerates on near-zero scaling) could not express.
Shape.pipeShellMultiSection(spine: helix, profiles: [fullRib, runoutRib], mode: .auxiliary(spine: axis))
v1.3.2 (June 2026) — fix loft (ThruSections) SIGSEGV on mismatched profiles (closes #176)
PATCH — robustness fix, no API change. Shape.loft (and any BRepOffsetAPI_ThruSections path) could SIGSEGV and abort the host process on mismatched closed profiles — e.g. machine-generated profile sets with differing vertex counts. The crash is an upstream OCCT null dereference in BRepFill_CompatibleWires::SameNumberByPolarMethod (unguarded correspondence-list iterator over-advance); because it surfaces as an OS signal, the bridge’s catch(...) could not intercept it.
Fixed by carrying a minimal source patch (Scripts/patches/0001-BRepFill_CompatibleWires-guard-polar-iterator.patch, applied by build-occt.sh) and rebuilding the xcframework. Loft now fails gracefully (nil) on such inputs. Reported and fixed upstream: OpenCASCADE/OCCT issue #1297, PR #1298.
Note: the OCC_CATCH_SIGNALS guards added in v1.2.1/v1.2.2 are inert in this build (OCCT is not compiled with OCC_CONVERT_SIGNALS) and do not provide signal safety; this patch addresses the crash at its source instead.
v1.3.1 (June 2026) — feature-aware patterning, sweep orientation, geometric edge selection (closes #169, #170, #171)
PATCH — additive helpers + one orientation fix. Three ergonomics gaps surfaced building the OCCTSwiftScripts cookbook recipes (pipe-flange, helical-spring, mounting-bracket). No C++ bridge change — everything composes existing tested primitives.
-
#169 — feature-level circular pattern.
circularPatternduplicates the body, so the bolt-circle intent (“drill one hole, repeat it around the axis”) produced overlapping flange copies with the holes filled in. NewShape.circularPatternCut(tool:axisPoint:axisDirection:count:angle:)patterns the tool and subtracts the compound in one call;circularPattern’s doc now warns it patterns the body, not features.let flange = blank.circularPatternCut(tool: hole, axisPoint: .zero, axisDirection: SIMD3(0,0,1), count: 8) -
#170 — sweep orientation.
Shape.sweep(BRepOffsetAPI_MakePipe) could yield an inward-oriented (negative-volume) solid depending on the section wire’s sense vs. the path tangent — a hazard for booleans andvolume > 0checks.sweepnow orientation-normalises its result. NewShape.orientedForward()applies the same fix explicitly, andShape.signedVolumeexposes the signedBRepGPropmass for orientation diagnostics (unlikevolume, which masks negatives asnil). -
#171 — geometric edge selection. Picking fillet edges by raw
edges()index is fragile — the index shifts with parameters. New selectors return edges that feed straight intofilleted(edges:radius:):concaveEdges()/convexEdges()(classified viaBRepOffset_Analyse),edges(where:),edges(parallelTo:tolerance:), andedges(inBounds:_:).let rounded = bracket.filleted(edges: bracket.concaveEdges(), radius: 3)
v1.3.0 (June 2026) — full 4×4 XCAF component locations (closes #174)
MINOR — additive new public API. XCAF assembly components could previously only be placed by a translation, so true instanced assemblies (shared geometry under arbitrary rigid transforms) lost their rotations. Document.addComponent(matrix:) now accepts a full 4×4 placement (row-major 12), and shape-driven instancing via Shape.located(matrix:) + addShape(makeAssembly: true) dedupes by shared TShape so each unique solid is written once with N located occurrences.
v1.2.2 (June 2026) — broaden OCC signal guards (#175)
PATCH — robustness. Extended OSD::SetSignal + OCC_CATCH_SIGNALS coverage to the validity, volume, boolean, extrude, and revolve bridge paths (on top of v1.2.1’s loft/mesh/transform guards), so more degenerate-input failures surface as caught errors rather than aborting the process. Note: OCC_CATCH_SIGNALS is a no-op unless OCC_CONVERT_SIGNALS is defined, and converting via setjmp/longjmp bypasses C++ unwinding — so this hardens, but does not fully tame, deterministic SIGSEGVs on degenerate machine-generated geometry (see #176).
v1.2.1 (June 2026) — OCC signal handling on loft/mesh/transform (#175)
PATCH — robustness. Installed OSD::SetSignal and wrapped the loft (ThruSections), mesh, and transform bridge entry points in OCC_CATCH_SIGNALS so OCCT hardware-signal faults on those paths convert to catchable failures instead of crashing the caller.
v1.2.0 (June 2026) — TopologyGraph attribute store + Codable snapshot (closes #168)
MINOR — additive new public API. TopologyGraph nodes were bare (kind, index) pairs with no payload, and the type had no serialization (it wraps an opaque C++ handle). This adds a pure Swift-side sidecar so callers can attach arbitrary typed metadata to any NodeRef and round-trip it. No C++ bridge change — the store never touches the C++ graph.
extension TopologyGraph {
public var attributes: NodeAttributeStore // per-node typed metadata
public func attribute(_ key: String, for: NodeRef) -> AttrValue?
public func setAttribute(_ key: String, _ value: AttrValue, for: NodeRef)
public func snapshot() throws -> GraphSnapshot // export attributes + source shape
public convenience init(snapshot: GraphSnapshot) throws // rebuild + reattach
}
AttrValue— closed Codable enum:bool/int/double/string/ints/doubles(intsfor mesh-region index sets,doublesfor fitted-surface params).NodeAttributeStore— Codable, keyed byNodeRef, encodes as sorted arrays so element order is deterministic; pair withGraphSnapshot.canonicalEncoder()(.sortedKeys) for byte-stable, diffable output.GraphSnapshot— Codable round-trip. The graph structure is not serialized; it is re-derived by rebuilding from the source shape’s BREP (captured at construction). Rebuild pinsparallel: false; a determinism test verifiesNodeRefindexing is stable across rebuilds.NodeKindandNodeRefgainedCodable.
Foundation for the OCCTReconstruct mesh-to-solid pipeline (per-node fit residual / confidence / provenance + session persistence) and for OCCTMCP’s planned reconstruct_* read/write graph tools (OCCTMCP #33).
v1.1.0 (May 2026) — TopologyGraph history disambiguation (closes #167)
First MINOR bump under the cohort SemVer policy. Two new methods on TopologyGraph resolve the ambiguity in findDerived’s empty-result case:
extension TopologyGraph {
/// True iff any history record names `original` as a key.
public func hasHistoryRecord(for original: NodeRef) -> Bool
/// findDerived if non-empty; else [] for explicitly-deleted nodes;
/// else [original] for untouched nodes (still at the same index).
public func findDerivedOrSelf(of original: NodeRef) -> [NodeRef]
}
findDerived returned [] for both “untouched” and “explicitly deleted” — selection-remap consumers couldn’t tell which. findDerivedOrSelf is the typical “where did this node end up?” lookup: a single deterministic call that returns derivatives, [] for deleted, or [original] for untouched. hasHistoryRecord is the lower-level disambiguator for callers that want to handle the cases differently at the call site.
Implementation is a Swift-side scan over historyRecords — O(records × originals-per-record), which is fine for typical scenes. A bridge-side accelerator can land later if profiling ever justifies it.
Downstream impact: OCCTMCP v1.3.0 currently works around this with an isIdentityPreserving flag on its HistoryRegistry for transform_body / heal_shape. Once OCCTMCP picks up this OCCTSwift bump, it can drop the flag for ops that record explicit modify/delete records and use per-node resolution.
Op count: 4,284 → 4,286 (+2). xcframework binary unchanged from v1.0.0.
v1.0.4 (May 2026) — wire applyFillet / applyChamfer through *WithFullHistory (closes #166)
Closes the explicit follow-up to v1.0.3: FeatureReconstructor.BuildResult.histories[id] now also covers FeatureSpec.Fillet and FeatureSpec.Chamfer with non-nil ids — every spec kind now resolves through OCCT’s recorded history instead of the centroid-distance heuristic on the consumer side.
Behavior changes:
applyFilletfor all threeEdgeSelectorcases (.all,.nearPoint,.onFeature) now usesShape.filletedWithFullHistory(radius:edges:)and records the returnedShapeHistoryRefinctx.histories[id].applyChamferdoes the same viaShape.chamferedWithFullHistory(distance:edges:). Chamfer’s.nearPointand.onFeatureselectors are now wired up — they were stubbed torecordSkip(.unsupported)in v1.0.3 and earlier.- Each path falls back to the index-less primitive (
filleted(radius:)/chamfered(distance:)) on builder-nil to preserve existing back-compat semantics. Specs without ids continue to land directly on the non-history path.
Internals: the per-selector helpers now return [Int]? matching-edge-index lists instead of pre-cooked Shape? results. This consolidates the resolution machinery between fillet and chamfer (chamfer used to duplicate fillet’s .all-only path because it had no shared resolver). The OCCTSwiftIO and OCCTMCP-side consumers that read BuildResult.histories[id] get fillet / chamfer coverage without any code change.
Out of scope: variable-radius fillet via FeatureSpec (the filletedWithFullHistory(edge:startRadius:endRadius:) Tier 2 variant) — FeatureSpec.Fillet only carries one radius. Variable-radius would be a new spec variant.
v1.0.3 (May 2026) — full per-input history Tier 2 & Tier 3 (issue #165)
Completes #165. Builds on the boolean-history surface in v1.0.2 by extending it to modification ops and threading history capture through FeatureReconstructor.
Tier 2 — modification ops with full history (+5 ops):
extension Shape {
func filletedWithFullHistory(radius: Double, edges: [Int])
-> (result: Shape, history: ShapeHistoryRef)?
func filletedWithFullHistory(edge: Int, startRadius: Double, endRadius: Double)
-> (result: Shape, history: ShapeHistoryRef)?
func chamferedWithFullHistory(distance: Double, edges: [Int])
-> (result: Shape, history: ShapeHistoryRef)?
func shelledWithFullHistory(facesToRemove: [Int], thickness: Double, tolerance: Double = 1e-3)
-> (result: Shape, history: ShapeHistoryRef)?
func defeaturedWithFullHistory(faces: [Int])
-> (result: Shape, history: ShapeHistoryRef)?
}
All five reuse the existing OCCTBooleanHistory opaque handle (the underlying type stores a unique_ptr<BRepBuilderAPI_MakeShape>, which is the common base of every OCCT modification builder). For consumers, the API matches Tier 1 — history.record(of: inputSubShape) returns the ShapeHistoryRecord of Modified / Generated / IsDeleted lookups.
Tier 3 — FeatureReconstructor.BuildResult.histories:
public struct BuildResult: Sendable {
// … existing fields …
public let histories: [String: ShapeHistoryRef]
}
Per-feature ShapeHistoryRef keyed by the feature id. Populated when:
- A boolean spec (
FeatureSpec.Boolean) with non-nil id resolves successfully — captured fromunionWithFullHistory/subtractedWithFullHistory/intersectionWithFullHistory - A hole spec (
FeatureSpec.Hole) with non-nil id — captured from the underlying subtract - An additive feature (revolve/extrude/sheet-metal) with non-nil id whose
absorbAdditivestep fuses into a non-emptycurrent— captured from the union
Features without an id aren’t keyed, and the existing applyFillet / applyChamfer paths still go through the non-history primitives (those cases need edge/face index computation that’s tracked as a separate refinement).
This unblocks OCCTMCP’s remap_selection for the apply_feature tool: instead of falling back to centroid-distance heuristics on splits / merges / deletions, the consumer can now walk BuildResult.histories[feature_id].record(of: subshape) for the exact OCCT-recorded mapping.
Op count: 4,279 → 4,284 (+5 Tier 2 entry points). xcframework binary unchanged from v1.0.0; SPM consumers continue to resolve against the v1.0.0 asset.
v1.0.2 (May 2026) — per-input boolean history (issue #165 Tier 1)
Additive feature for selection-remapping consumers (#165). Adds a per-input-subshape history lookup surface to the four BRepAlgoAPI boolean ops, addressing OCCTMCP’s remap_selection need to walk selection IDs across boolean / split mutations exactly (instead of the centroid-distance heuristic that loses on splits / merges / deletions):
extension Shape {
func unionWithFullHistory(_ other: Shape) -> (result: Shape, history: ShapeHistoryRef)?
func subtractedWithFullHistory(_ tool: Shape) -> (result: Shape, history: ShapeHistoryRef)?
func intersectionWithFullHistory(_ other: Shape) -> (result: Shape, history: ShapeHistoryRef)?
func splitWithFullHistory(by tool: Shape) -> (pieces: [Shape], history: ShapeHistoryRef)?
}
public final class ShapeHistoryRef: @unchecked Sendable {
func record(of inputSubShape: Shape) -> ShapeHistoryRecord // .modified / .generated / .isDeleted
}
The ShapeHistoryRef retains the OCCT builder so Modified / Generated / IsDeleted stay queryable after the operation completes. Existing BooleanResult / BooleanHistoryResult callers are unchanged — pure additive surface.
Bug fix on the way. While building the history-handle plumbing I found that the new probe-then-fill helpers returned 0 when called with maxCount=0 (or outRefs=null), breaking the Swift-side count-then-allocate idiom. Fixed: the new bridge functions now always return the full count and only stop writing when count >= maxCount. Existing callers were unaffected (none used the probe path).
xcframework binary unchanged from v1.0.0 (no OCCT version change). SPM consumers continue to resolve against the v1.0.0 asset.
Out of scope for this release (will land in follow-ups under #165 Tiers 2 / 3): filletedWithFullHistory / chamferedWithFullHistory / shelledWithFullHistory / defeaturedWithFullHistory, and FeatureReconstructor.BuildResult.history.
v1.0.1 (May 2026) — TopologyGraph.rootNodes fix + test repair
Bug fix. TopologyGraph.NodeKind was missing product = 10 and occurrence = 11 cases, so rootNodes silently returned [] even when products were present (compactMap { NodeKind(rawValue: 10) } filtered every entry out as nil). After OCCT 8.0.0 beta1 reshaped root iteration to “Products only”, every rootNodes consumer hit this. Fixed by extending the enum to cover the full BRepGraph_NodeId::Kind range (topology 0–8, assembly 10–11; slot 9 reserved upstream).
Tests. The four pre-existing failures shipped with v1.0.0 are repaired:
hasRootsandchildExplorernow wrap the box’s solid in a Product vialinkProductToTopologybefore queryingrootNodes(matches OCCT 8.0 GA assembly semantics).edgeVertexDistanceswitched from low-levelBRepExtrema_DistanceSS(which deliberately skips edge-vertex pairs whose closest point is at an endpoint, expecting the caller to also pair vertices-with-vertices) to high-levelShape.distance(to:)backed byBRepExtrema_DistShapeShape, which orchestrates all subshape combinations including endpoint cases.edgeSelectorFeatureUnsupporteddeleted — it assertedFillet.onFeaturewas unsupported, contradicting the newerfilletOnFeaturetest that asserts the opposite..onFeatureis wired up inFeatureReconstructor.
xcframework binary is unchanged from v1.0.0; SPM consumers continue resolving against the v1.0.0 asset.
v1.0.0 (May 2026) — OCCT 8.0.0 GA — SemVer-stable
OCCTSwift reaches SemVer-stable v1.0.0, pinned to OpenCASCADE Technology 8.0.0 GA (released 2026-05-07, commit d3056ef8 on Open-Cascade-SAS/OCCT). After eight months of pre-1.0 development across 170+ point releases — wrapping ~4,275 OCCT operations across 1,160+ test suites — the public Swift API is stable from this point on. Pin to from: "1.0.0" in Package.swift.
OCCT 8.0.0 GA highlights since rc5 (per OCCT discussion #1275):
- BRepGraph (graph-based topology) and Gordon Surfaces shipped in their final shape
- TKHelix toolkit (geometric helix with B-spline approximation)
- ExtremaPC specialized point-to-curve extrema with variant dispatching
- STEP read/write thread safety: “safe under the contract of one reader or writer per thread”
- Multiple SEGV fixes in chamfer, fillet, and pipe-shell operations
- BSpline evaluation bugs corrected; geometry hashing implementations completed
- C++17 minimum (already required by Swift 6);
Standard_Failureinheritsstd::exception
Beta2 → GA breaking changes absorbed in this release:
PointSetLibremoved. OCCT introducedPointSetLib_Props/PointSetLib_Equationin 8.0.0 beta1 (rc5/PCA point-cloud analysis) and removed them before GA. The SwiftPointSetLibenum and bridge wrappers were deleted to follow upstream. If you depended onPointSetLib.properties / barycentre / inertiaMatrix / equation, port to your own NumPy/Accelerate implementation; the OCCT primitives are no longer available at any layer.- CoEdge continuity setters consolidated into
setEdgeRegularity. OCCT 8.0.0 GA moved continuity from per-coedge to per-(edge, face1, face2)(inBRepGraph_LayerRegularity). The pre-GAsetCoEdgeContinuity/setCoEdgeSeamContinuity/setCoEdgeSeamPairIdare replaced by a singleTopologyGraph.setEdgeRegularity(_:face1:face2:continuity:) -> Bool. For seam continuity, pass the same face index asface1andface2. Explicit seam-pair-id is gone — seam-pair-id is structural in GA (two coedges on the same edge/face with opposite orientations); query via the existingcoedgeSeamPairaccessor.
Removed deprecated:
TopologyGraph.occurrenceParentOccurrence(_:)— deprecated in v0.157.0 when OCCT 8.0.0 beta1 reshaped assembly topology toProduct → Occurrence → Product. UseoccurrenceParentProduct(_:).
Looking ahead: OCCTSwift now moves to a work-on-branch strategy for upstream OCCT changes; main stays release-quality. Future OCCT releases land in feature branches and graduate to a tagged OCCTSwift release only when the upstream is GA.
v0.171.0 (May 2026) — ML-export hoist to OCCTSwiftIO
Breaking change. The consumption-side ML repacking layer added in v0.136.0 (TopologyGraph.GraphExport, exportForML(), exportJSON()) has been removed and lifted to OCCTSwiftIO v0.2.0 per OCCTSwiftIO#1 (supersedes OCCTSwift#71). It’s pure batch / headless workflow with no Viewport dependency — fits the OCCTSwiftIO charter, doesn’t need to live in the kernel.
What stays in the kernel (and why): FaceGridSample, sampleFaceUVGrid(faceIndex:uSamples:vSamples:), and sampleEdgeCurve(edgeIndex:count:). Their implementations call C bridge functions on TopologyGraph.handle, which is internal to this module. Lifting them would require widening visibility — explicitly out of scope per the partial-lift decision recorded on the issue.
Consumer migration: direct callers of exportForML / exportJSON must add import OCCTSwiftIO alongside import OCCTSwift. Symbol resolution otherwise unchanged. Known external callers swept: OCCTSwiftScripts/Sources/occtkit/Commands/GraphML.swift, OCCTSwiftScripts/Sources/GraphML/main.swift.
Net deltas: −124 LOC in BRepGraph.swift, −76 LOC in ShapeTests.swift. xcframework binary unchanged (no bridge changes).
v0.170.1 (May 2026) — ShapeMeasurements kernel hoist + OCCTBridge.mm split complete
ShapeMeasurements moved to kernel (#100, PR #163). ShapeMeasurements (per-face areas / centroids / perimeters + per-edge lengths) and Shape.measure(linearTolerance:) are now part of OCCTSwift itself, no longer requiring a dependency on OCCTSwiftTools. Pure Swift relocation — no bridge changes. Existing OCCTSwiftTools.ShapeMeasurements callers should re-target to import OCCTSwift once OCCTSwiftTools ships its dep bump (tracked in OCCTSwiftTools#13).
OCCTBridge.mm split — DONE (#99, PRs #160-#162). The monolithic OCCTBridge.mm is now 393 lines of pure foundation (header includes, global mutex, OCCTSewing struct, Internal.h import) — down from 58,168 lines pre-split (−99.3%). All 4,281 operations live in 15 per-OCCT-module translation units (OCCTBridge_Modeling.mm, OCCTBridge_Topology.mm, OCCTBridge_Healing.mm, OCCTBridge_Properties.mm, OCCTBridge_Geom2d.mm, OCCTBridge_Surface.mm, OCCTBridge_Curve3D.mm, OCCTBridge_Document.mm, OCCTBridge_IO.mm, OCCTBridge_Mesh.mm, OCCTBridge_Spatial.mm, OCCTBridge_BRepGraph.mm, OCCTBridge_AIS.mm, OCCTBridge_Visualization.mm, OCCTBridge_ProjLib_NLPlate.mm). Net-zero behavior change throughout; public C surface unchanged. The xcframework binary is identical to v0.170.0 (no OCCT changes), so SPM consumers can continue using the v0.170.0 binary URL.
v0.170.0 (May 2026) — OCCT 8.0.0-beta2 ingest
xcframework rebuilt against V8_0_0_beta2. No public API changes — beta2 is a small follow-up to beta1 with no API breakage. Final 8.0.0 release remains targeted for May 7, 2026.
Upstream changes that landed in beta2:
- Thread-safe STEP write + STEP/IGES read (OCCT #1259) — fixes
libmallocdouble-free under concurrentSTEPControl_Writer::Transferand intermittent crashes in concurrent STEP/IGES readers. Contract: one reader/writer per thread; STEP read + write safe under that contract; IGES read still requires explicit serialization. OCCTSwift already serializes IGES viaigesMutex()and STEP viaocctGlobalMutex(), so the upstream fix is a net safety improvement without requiring bridge changes. - CPU grid path restored (OCCT #1252) — the classical
Graphic3d_Structure-based grid removed in beta1 is back as a coexisting backend. Doesn’t surface in OCCTSwift (no grid API exposed). - Documentation refresh + samples directory + CI warning cleanup — internal to upstream; no impact on consumers.
OCCTSwift surface unchanged: 4,281 wrapped operations, 3,393 tests, 1,178 suites, identical Swift OCCTSwift.* API.
v0.169.0 (May 2026) — Mesh + export progress (issue #98 follow-up)
Extends the ImportProgress channel from v0.168 to two more long-running OCCT operations called out as out-of-scope in the original issue: BRepMesh_IncrementalMesh::Perform and the STEP / IGES writers. Same protocol, same cancellation contract.
New Swift API:
extension Shape {
/// Run BRepMesh_IncrementalMesh with progress + cooperative cancellation.
/// Throws ImportError.cancelled if cancelled.
@discardableResult
public func meshWithProgress(
linearDeflection: Double = 0.1,
angularDeflection: Double = 0.5,
progress: ImportProgress? = nil
) throws -> Shape
}
extension Exporter {
/// Export a shape to STEP with progress + cancellation.
/// Throws ExportError.cancelled if cancelled.
public static func writeSTEP(shape: Shape, to url: URL, progress: ImportProgress?) throws
/// Export a shape to IGES with progress + cancellation.
public static func writeIGES(shape: Shape, to url: URL, progress: ImportProgress?) throws
}
extension Document {
/// Write the document to a STEP file with progress + cancellation.
/// Throws ImportError.cancelled if cancelled.
public func writeSTEP(to url: URL, progress: ImportProgress?) throws
}
extension ExportError {
case cancelled
}
Bridge plumbing: 5 new entry points (OCCTShapeIncrementalMeshProgress, OCCTExportSTEPProgress, OCCTExportSTEPWithModeProgress, OCCTExportIGESProgress, OCCTDocumentWriteSTEPProgress) reusing the existing BridgeProgressIndicator from v0.168. BRepMesh_IncrementalMesh::Perform(Message_ProgressRange&), STEPControl_Writer::Transfer(...range), IGESControl_Writer::AddShape(...range), and STEPCAFControl_Writer::Transfer(...range) all accept the indicator’s progress range.
Why ImportProgress is the type for export too: it’s the same channel — progress + cancel. Adding parallel ExportProgress/MeshProgress protocols would multiply types without functional benefit. The protocol name reads slightly oddly in export contexts; pre-1.0 we accept that, and v1.0 will likely rename to OperationProgress.
6 new tests cover meshing progress + cancellation, STEP/IGES export with progress: nil (back-compat), STEP export progress fires, and Document.writeSTEP(to:progress:) round-trip.
v0.168.0 (May 2026) — STEP/IGES import progress + cancellation (issue #98)
Wraps OCCT’s Message_ProgressIndicator so callers of Shape.loadSTEP / loadIGES / loadIGESRobust and Document.load / loadSTEP can observe progress and cooperatively cancel long-running imports.
New Swift API:
public protocol ImportProgress: AnyObject, Sendable {
func progress(fraction: Double, step: String)
func shouldCancel() -> Bool // default: false
}
extension ImportError {
case cancelled
}
extension Shape {
public static func loadSTEP(from url: URL, progress: ImportProgress? = nil) throws -> Shape
public static func loadSTEP(from url: URL, unitInMeters: Double, progress: ImportProgress? = nil) throws -> Shape
public static func loadIGES(from url: URL, progress: ImportProgress? = nil) throws -> Shape
public static func loadIGESRobust(from url: URL, progress: ImportProgress? = nil) throws -> Shape
}
extension Document {
public static func load(from url: URL, progress: ImportProgress? = nil) throws -> Document
public static func loadSTEP(from url: URL, progress: ImportProgress? = nil) throws -> Document
public static func loadSTEP(from url: URL, modes: STEPReaderModes, progress: ImportProgress?) throws -> Document
}
progress: nil (the default) keeps existing call sites source-compatible — no behavioural change for callers that haven’t opted in.
Bridge plumbing: 7 new *Progress C entry points in OCCTBridge plus an internal BridgeProgressIndicator subclass of Message_ProgressIndicator that forwards Show() to a Swift callback (via opaque userData + @convention(c) trampoline) and reports UserBreak() == true when the Swift shouldCancel() returns true. STEPControl_Reader::TransferRoots, IGESControl_Reader::TransferRoots, and STEPCAFControl_Reader::Transfer all accept the indicator’s progress range.
Cancellation contract: shouldCancel() is polled at OCCT’s progress checkpoints (typically once per transferred entity). Returning true causes the loader to throw ImportError.cancelled at the next boundary. The shape / document is not partially constructed.
4 new tests cover (1) progress callback fires for a round-tripped STEP file, (2) progress: nil back-compat path still works, (3) cancellation flag honored, (4) Document.load progress.
Driver: unblocks OCCTSwiftTools v0.4.0 — its CADFileLoader.load(from:format:) async API can now pass progress straight through, giving OCCTSwiftAIS’ file-open dialog a real progress bar and cancel button “for free”.
v0.167.0 (May 2026) — visionOS + tvOS support
OCCT.xcframework now ships seven slices:
| Platform | Slice |
|---|---|
| macOS 12+ arm64 | macos-arm64 |
| iOS 15+ device arm64 | ios-arm64 |
| iOS 15+ Simulator arm64 | ios-arm64-simulator |
| visionOS 1+ device arm64 | xros-arm64 (new) |
| visionOS 1+ Simulator arm64 | xros-arm64-simulator (new) |
| tvOS 15+ device arm64 | tvos-arm64 (new) |
| tvOS 15+ Simulator arm64 | tvos-arm64-simulator (new) |
Package.swift declares .visionOS(.v1) and .tvOS(.v15) alongside the existing .iOS(.v15) / .macOS(.v12). The xcframework asset attached to this release is ~341 MB (up from 148 MB at v0.165.0; quadruples the slice count).
Build script changes (Scripts/build-occt.sh) — required to make OCCT 8 cross-compile cleanly to visionOS and tvOS SDKs:
- Added four new build blocks (
visionOS device,visionOS Simulator,tvOS device,tvOS Simulator). - Each new block sets
-DCMAKE_SIZEOF_VOID_P=8to bypass OCCT’sOCCT_MAKE_COMPILER_BITNESScmake macro, which couldn’t autodetect pointer size on the visionOS SDK (32 + 32*(/8)syntax error from an emptyCMAKE_C_SIZEOF_DATA_PTR). - Removed explicit
-mtargetos=/-m*-version-min=flags from the C/CXX flags — clang rejects them when CMake already sets--target=arm64-apple-xros1.0from the SDK + deployment target. Letting CMake derive the target is the correct path. - xcframework creation step now conditionally includes each platform slice: if a slice fails to build (empty
.a), the xcframework is built without it instead of aborting the whole script.
OCCT.xcframework.zip checksum: 5147b7d65cd9af5a6c3af1b38a1492365e645ed5c76a663bf9311c2f54043d87.
v0.166.1 (May 2026) — Platform plan refinement
Metadata-only patch revising the v1.0.0 platform expansion plan:
- Dropped Intel Mac (
macOS x86_64). Apple is winding down Intel macOS support; not worth the build slot. - visionOS confirmed for v1.0.0. Device + simulator slices.
- tvOS reduced to “if cheap”. Will only add if it falls out of the visionOS work without extra effort.
- Linux / Windows / Android — moved to “under review” with a full analysis in docs/platform-expansion.md. Headline: Linux is the strongest non-Apple candidate (~2 weeks of focused work), Windows is medium-risk, Android should wait for Swift-on-Android packaging to stabilize. The prerequisite for any non-Apple port is the OCCTBridge
.mm→.cppaudit, which is independently useful.
v0.166.0 (May 2026) — Swift Package Index readiness
Preparation for a public listing on Swift Package Index alongside v1.0.0. No code changes; metadata only.
Added:
.spi.yml— SPI build matrix declaration:- macOS via SPM on Swift 6.0, 6.1, 6.2, 6.3
- iOS on Swift 6.3
- DocC documentation target:
OCCTSwift
CODE_OF_CONDUCT.md— short pointer to Contributor Covenant 2.1 with reports email.- README:
- SPI shields.io badges (Swift versions, platforms) — activate once the package is added to SPI.
- Updated install snippet from stale
from: "0.128.0"to currentfrom: "0.165.0". - “Supported Platforms” table covering current support and v1.0.0 expansion plan (Intel Mac, visionOS).
- Documented Swift 6.1+ verified clean against 6.1 / 6.2 / 6.3 toolchains.
Submission gating: waiting until v1.0.0 ships (May 7, 2026, alongside OCCT 8.0.0 GA) before submitting to SPI. v0.166 makes the repo submission-ready.
v0.165.0 (May 2026) — Fix SPM xcframework URL (issue #97)
Package.swift had its remote binaryTarget(url:) hardcoded to the v0.131.0 xcframework — predating OCCT 8 by months. SPM consumers pinning from: "0.157.0" resolved the version correctly but the build failed at compile-time with 'BRepGraph_MeshView.hxx' file not found because the v0.131.0 binary was built against rc-era OCCT and didn’t ship the beta1 headers that the v0.157+ wrappers reference. Local-path consumers were unaffected (the auto-detect picks up Libraries/OCCT.xcframework).
This release:
- Attaches the current beta1 xcframework as a release asset (
OCCT.xcframework.zip, ~148 MB). - Updates
Package.swift’s remote URL to point at the v0.165.0 release and bumps the SPM checksum to99bba63c0e686195512cfaa4f3f46f9f11c8b6cd89e8fe5b8aed872a48978003.
After this release, from: "0.165.0" resolves cleanly for remote-pin consumers and the v0.157.0 → v0.164.0 wrapper surface (MeshView, MeshCache, EditorView mutation, ProductOps, RepOps + cache inspection) becomes usable downstream. Downstream Package.swift consumers should bump their pin to from: "0.165.0".
No new ops; this is purely a packaging fix.
v0.164.0 (May 2026) — RepOps non-guard setters & cache entry inspection (21 ops)
Final wrapping pass for OCCT 8.0.0 beta1 BRepGraph surface. After this release, the public surface of BRepGraph::EditorView and BRepGraph::MeshView is exhaustively wrapped on TopologyGraph.
RepOps non-guard setters — swap geometry / mesh content bound to an existing rep id without recreating the rep:
graph.repSetSurface(repId, surface: newSurface)
graph.repSetCurve3D(repId, curve: newCurve3D)
graph.repSetCurve2D(repId, curve: newCurve2D)
graph.repSetTriangulation(repId, triangulation: newTri)
graph.repSetPolygon3D(repId, polygon: newPoly3D)
graph.repSetPolygon2D(repId, polygon: newPoly2D)
graph.repSetPolygonOnTri(repId, polygon: newPolyOnTri)
graph.repSetPolygonOnTriTriangulationId(polyOnTriRepId, triRepId: newTriRepId)
Cache entry inspection — detailed access to the algorithm-derived cache tier for diagnostics and non-destructive mesh tooling:
graph.cachedFaceMeshIsPresent(0) // Bool
graph.cachedFaceMeshTriRepCount(0) // Int
graph.cachedFaceMeshActiveIndex(0) // Int (-1 if absent)
graph.cachedFaceMeshStoredOwnGen(0) // UInt32 (cache freshness gen)
graph.cachedFaceMeshTriRepId(0, repIndex: 0) // Int? (active or specific entry)
graph.cachedEdgeMeshIsPresent(0)
graph.cachedEdgeMeshPolygon3DRepId(0)
graph.cachedEdgeMeshStoredOwnGen(0)
graph.cachedCoEdgeMeshIsPresent(0)
graph.cachedCoEdgeMeshPolygon2DRepId(0)
graph.cachedCoEdgeMeshPolygonOnTriRepCount(0)
graph.cachedCoEdgeMeshPolygonOnTriRepId(0, repIndex: 0)
graph.cachedCoEdgeMeshStoredOwnGen(0)
The StoredOwnGen accessors expose the cache freshness generation — pair with the entity’s current OwnGen (via existing readers) to detect stale cache entries.
3 new tests cover fresh-graph absence, post-appendCachedTriangulation state readback, and edge/coedge cache absence.
v0.163.0 (May 2026) — EditorView ProductOps assembly building (5 ops)
Closes the EditorView mutation surface. With v0.163.0 the public mutation API of BRepGraph::EditorView is fully wrapped on TopologyGraph.
let parent = graph.createEmptyProduct()!
let child = graph.linkProductToTopology(
shapeRootKind: 0, shapeRootIndex: 0,
placement: TopologyGraph.identityLocationMatrix)!
let linked = graph.linkProducts(
parentProductIndex: parent,
referencedProductIndex: child,
placement: TopologyGraph.identityLocationMatrix)!
// linked.occurrenceIndex, linked.occurrenceRefIndex
graph.productRemoveOccurrence(parent, occurrenceRefIndex: linked.occurrenceRefIndex)
graph.productRemoveShapeRoot(child)
linkProductToTopology accepts placement: nil for an identity placement. linkProducts takes a parentOccurrenceIndex: Int? (nil for unparented).
2 new tests cover the create/link path and remove-with-bogus-ids no-crash safety.
v0.162.0 (May 2026) — EditorView geometric setters, location setters, PCurve API (16 ops)
Closes the EditorView wrapping started in v0.159.0. With v0.162.0 the public mutation surface of BRepGraph::EditorView is fully wrapped on TopologyGraph.
CoEdge geometric setters:
setCoEdgeUVBox(_:u1:v1:u2:v2:)setCoEdgeContinuity/setCoEdgeSeamContinuity(GeomAbs_Shape: 0=C0, 1=C1, 2=C2, 3=C3, 4=CN)setCoEdgeSeamPairId
Face geometric setter:
setFaceTriangulationRep(_:triRepId:)— bind the active triangulation to a face’s persistent tier (vsappendCachedTriangulationfor the cache tier)
CoEdge PCurve API (uses existing Curve2D Swift type):
coEdgeCreateCurve2DRep(_ curve2D:)→ rep idcoEdgeSetPCurve(_ coedgeIndex:curve2D:)(pass nil to clear)coEdgeAddPCurve(edgeIndex:faceIndex:curve2D:first:last:orientation:)
Location setters via 12-double 3x4 matrix (gp_Trsf::SetValues row-major convention):
setVertexRefLocalLocation,setCoEdgeRefLocalLocation,setWireRefLocalLocationsetFaceRefLocalLocation,setShellRefLocalLocation,setSolidRefLocalLocationsetOccurrenceRefLocalLocation,setChildRefLocalLocation- Convenience:
TopologyGraph.identityLocationMatrixreturns the 3x4 identity
3 new tests cover CoEdge geometric setters on real coedges, identity-matrix location setters on real refs, and face-triangulation binding with MeshView readback.
v0.161.0 (May 2026) — EditorView Add / Remove / Ref setters (41 ops)
Continues the EditorView wrapping started in v0.159.0 with the structural-mutation surface:
Add operations (return ref id or nil):
edgeAddInternalVertex(_:vertexIndex:orientation:)faceAddVertex(_:vertexIndex:orientation:)shellAddChild(_:childKind:childIndex:orientation:)solidAddChild(_:childKind:childIndex:orientation:)compoundAddChild(_:childKind:childIndex:orientation:)compSolidAddSolid(_:solidIndex:orientation:)
Remove operations (return Bool indicating active-usage removal):
edgeRemoveVertex,edgeReplaceVertex(returns new ref id)wireRemoveCoEdge,faceRemoveVertex,faceRemoveWireshellRemoveFace,shellRemoveChildsolidRemoveShell,solidRemoveChildcompoundRemoveChild,compSolidRemoveSolidremoveRep(repKind:repIndex:)— generic representation removal
Ref setters (entity-ref → entity-def rebinding, orientation, rep-id binding):
- Vertex:
setVertexRefOrientation,setVertexRefVertexDefId - Edge:
setEdgeStartVertexRefId,setEdgeEndVertexRefId,setEdgeCurve3DRepId,setEdgePolygon3DRepId - CoEdge:
setCoEdgeRefCoEdgeDefId,setCoEdgeEdgeDefId,setCoEdgeFaceDefId,setCoEdgeCurve2DRepId,setCoEdgePolygon2DRepId,setCoEdgePolygonOnTriRepId,clearCoEdgePCurveBinding - Wire:
setWireRefIsOuter,setWireRefOrientation,setWireRefWireDefId - Face:
setFaceSurfaceRepId,setFaceRefOrientation,setFaceRefFaceDefId - Shell:
setShellRefOrientation,setShellRefShellDefId - Solid:
setSolidRefOrientation,setSolidRefSolidDefId - Occurrence:
setOccurrenceChildDefId,setOccurrenceRefOccurrenceDefId - Generic:
setChildRefOrientation,setChildRefChildDefId
Setters that need TopLoc_Location or Bnd_Box2d (e.g. *RefLocalLocation, CoEdge.SetUVBox, CoEdge.SetContinuity) are deferred until a 12-double / 4-double calling convention lands in the bridge.
3 new tests cover Add no-crash safety, Remove returning false on bogus ref ids, and Ref setters operating on real box ids without crashing.
v0.160.0 (May 2026) — MeshCache write API + new Triangulation type
Completes the OCCT 8.0.0 beta1 two-tier mesh storage wrapping started in v0.158.0. The cache write side — BRepGraph_Tool::Mesh static helpers — is now exposed on TopologyGraph, and a new Triangulation Swift class wraps Handle<Poly_Triangulation> for input.
New Triangulation class (mirrors the existing Polygon3D / PolygonOnTriangulation pattern):
let tri = Triangulation.create(
nodes: [SIMD3(0,0,0), SIMD3(1,0,0), SIMD3(0,1,0), SIMD3(1,1,0)],
triangles: [0,1,2, 1,3,2]
)!
tri.nodeCount // 4
tri.triangleCount // 2
tri.node(at: 0) // SIMD3(0, 0, 0)
tri.triangle(at: 0) // (0, 1, 2)
tri.deflection = 0.01
Vertex indices are 0-based on the Swift boundary; the bridge handles OCCT’s 1-based convention internally.
MeshCache write API on TopologyGraph:
let triRepId = graph.createTriangulationRep(tri)!
graph.appendCachedTriangulation(faceIndex: 0, triRepId: triRepId)
graph.setCachedActiveIndex(faceIndex: 0, activeIndex: 0)
let polyRepId = graph.createPolygon3DRep(polygon3d)!
graph.setCachedPolygon3D(edgeIndex: 0, polyRepId: polyRepId)
let polyOnTriRepId = graph.createPolygonOnTriRep(polygonOnTri, triRepId: triRepId)!
graph.appendCachedPolygonOnTri(coedgeIndex: 0, polyRepId: polyOnTriRepId)
graph.setCachedPolygon2D(coedgeIndex: 0, poly2DRepId: ...)
This unblocks downstream tooling (OCCTMCP, OCCTSwiftScripts) that wants to populate algorithm-derived mesh data on a graph without touching the persistent (STEP-imported) tier — important for non-destructive meshing workflows.
4 new tests cover Triangulation construction round-trip, malformed-input rejection, and rep-creation + face/edge binding with subsequent MeshView readback.
v0.159.0 (May 2026) — EditorView field setters
OCCT 8.0.0 beta1’s BRepGraph::EditorView exposes per-entity Ops classes with Set* methods that mutate field-level data on existing graph entities (without requiring a full topology rebuild). v0.159.0 wraps the simple-value subset (scalars, bools, orientations) on the TopologyGraph Swift type:
VertexOps — setVertexPoint(_:x:y:z:), setVertexTolerance(_:tolerance:)
EdgeOps — setEdgeTolerance, setEdgeParamRange(_:first:last:), setEdgeSameParameter, setEdgeSameRange, setEdgeDegenerate, setEdgeIsClosed
CoEdgeOps — setCoEdgeParamRange, setCoEdgeOrientation (Forward/Reversed/Internal/External as Int 0–3)
WireOps — setWireIsClosed
FaceOps — setFaceTolerance, setFaceNaturalRestriction
ShellOps — setShellIsClosed
All 14 setters are pass-through to the corresponding g.Editor().<Entity>().Set*(...) on the OCCT side. Invalid ids are no-ops (try/catch in bridge). Setters that require new opaque types — SetPCurve, SetSurfaceRepId, SetTriangulationRep, Mut* RAII guards — are deferred. Same with Add* / Remove* mutation methods that aren’t already wrapped via the Builder bridge functions.
Driver: lets headless tooling (OCCTMCP, OCCTSwiftScripts) tweak field-level data after constructing a graph (e.g. relax a tolerance, mark an edge degenerate) without round-tripping through TopoDS_Shape rebuilds.
4 new tests cover set-then-read-back where a getter exists, plus no-crash safety on the readback-less setters.
v0.158.0 (May 2026) — MeshView two-tier mesh storage (read API)
OCCT 8.0.0 beta1 introduced a two-tier mesh storage model: an algorithm-derived cache (populated by BRepGraphMesh) and the persistent tier (mesh data imported from STEP, stored in topology definitions). v0.158.0 wraps the read-side of this model — BRepGraph::MeshView queries — exposing it on the existing TopologyGraph Swift type:
- Counts:
polygon2DCount,polygonOnTriCount,activeTriangulationCount,activePolygon3DCount,activePolygon2DCount,activePolygonOnTriCount. Pairs with the existingtriangulationCount/polygon3DCountfrom v0.133.0. - Per-entity cache-first queries:
meshFaceActiveTriangulationRepId(_ faceIndex:)→ optional rep id (cache-first, persistent fallback)meshEdgePolygon3DRepId(_ edgeIndex:)→ optional rep id (cache-first, persistent fallback)meshCoEdgeHasMesh(_ coedgeIndex:)→ bool (cache-only)
The Swift API is unchanged for existing call sites. Driver: prep for future BRepGraphMesh-driven workflows in OCCTMCP / OCCTSwiftScripts that want to introspect mesh state without invalidating the persistent tier.
The mesh write API (BRepGraph_Tool::Mesh::CreateTriangulationRep etc.) is intentionally not yet wrapped — it requires marshaling Handle<Poly_Triangulation> from Swift, which is a larger lift. Targeted for v0.159 or v1.0.
v0.157.0 (May 2026) — OCCT 8.0.0 beta1 support (final pre-1.0 release)
xcframework rebuilt against V8_0_0_beta1. v1.0.0 will follow on May 7, 2026 pinned to the OCCT 8.0.0 GA tag.
Bridge migrations driven by upstream API churn since rc5:
BRepGraph_BuilderViewremoved (OCCT #1237) → migrated all 22 mutation entry points toBRepGraph_EditorView. Old:g.Builder().AddVertex(p, t); new:g.Editor().Vertices().Add(p, t). Swift API surface unchanged.NCollection_Vectordeprecated (OCCT #1230) → switched 4 internal sites toNCollection_DynamicArray, including theBRepGraph_History::Recordmapping container.Builder().AppendFlattenedShape/AppendFullShapeconsolidated → both now route through the staticBRepGraph_Builder::Add(graph, shape, options). TheFlattenandCreateAutoProductoptions preserve the pre-beta1 distinction.Builder().ClearFaceMesh/ClearEdgePolygon3Dmoved → nowBRepGraph_Tool::Mesh::ClearFaceCache/ClearEdgeCache. Semantic shift: clears only the new cached-mesh tier, not persistent (STEP-imported) mesh data.graph.Build(shape, parallel)removed → wrapper now calls the staticBRepGraph_Builder::Add(graph, shape, opts)withCreateAutoProduct = falseto preserve the historical “no auto Product wrap” behaviour.graph.RootNodeIds()→graph.RootProductIds()— root iteration is now Products only.BRepGraph_Copy::CopyFace→CopyNode— single-node deep copy now takes any NodeId kind.Topo().Occurrences().ParentOccurrenceremoved — beta1 model isProduct → Occurrence → Product; an occurrence has no parent occurrence. Wrapper retained as-1sentinel for ABI; will be removed in v1.0.BRepGraph_ChildExplorer::Current()returnsBRepGraphInc::NodeInstance(wasNodeUsage); field accessor unchanged.BRepGraph_Tool::Edge::StartVertex/EndVertexrenamed toStartVertexId/EndVertexId; return type simplified from aVertexRefstruct toBRepGraph_VertexId.Topo().Poly().Nb*moved toMesh().Poly().Nb*— triangulation/polygon counts live on the new MeshView, paired with the two-tier mesh storage.
New beta1 surface (BRepGraph_MeshCache, BRepGraph_MeshView read-side, EditorView per-entity Ops methods, BRepGraph_Tool::Mesh cache-write API) is deferred to v0.158 / v1.0 — kept v0.157 minimal to preserve the soak window.
The 1300+ existing tests continue to pass under serial execution (OCCT_SERIAL=1 with --num-workers 1); the pre-existing parallel-execution NCollection arm64 race remains the same as v0.156.
v0.156.3 (Apr 2026) — Document.node(at:) warms up the labelId registry (issue #95)
The Document.node(at:) lookup added in v0.156.1 returned nil on a freshly-loaded STEP document if rootNodes hadn’t been walked first. Cause: the bridge’s labelId-to-TDF_Label registry is populated lazily via registerLabel(...) calls — OCCTDocumentLabelIsNull(0) reports null because labels[0] doesn’t exist yet. rootNodes warms it up because OCCTDocumentGetRootLabelId(handle, i) calls registerLabel, but OCCTDocumentGetRootCount alone doesn’t.
node(at:) now eagerly iterates root indices to register top-level labels before the IsNull check:
public func node(at labelId: Int64) -> AssemblyNode? {
let rootCount = OCCTDocumentGetRootCount(handle)
for i in 0..<rootCount { _ = OCCTDocumentGetRootLabelId(handle, i) }
guard !OCCTDocumentLabelIsNull(handle, labelId) else { return nil }
return AssemblyNode(document: self, labelId: labelId)
}
Deep-child labelIds aren’t registered by this warmup — those are expected to have been registered earlier by an explicit traversal (e.g. via node.children). The contract docstring spells this out.
mainLabel was checked for the same lazy-init quirk and is fine as-is — OCCTDocumentGetMainLabel calls registerLabel(main) itself.
Driver: OCCTSwiftScripts#23’s set-metadata verb. The downstream workaround (_ = document.rootNodes.count before node(at:)) can be removed.
One new regression test: load a STEP doc, look up node(at: 0) without touching rootNodes first, expect a non-nil node with labelId == 0.
v0.156.2 (Apr 2026) — Public Mesh(vertices:normals:indices:) constructor (issue #94)
Mesh had internal init(handle:) and no public way to construct from raw vertex/index arrays. This blocked sibling packages (notably OCCTSwiftMesh) from returning Mesh instances produced by mesh-domain algorithms (decimation, smoothing, repair, remeshing) that operate purely on vertex/index buffers and have no B-Rep state.
let mesh = Mesh(
vertices: [SIMD3(0, 0, 0), SIMD3(1, 0, 0), SIMD3(0, 1, 0)],
indices: [0, 1, 2]
)
Optional normals: [SIMD3<Float>]? parameter — when nil, per-vertex normals are computed by averaging the face normals of adjacent triangles (smooth shading default). Per-triangle normals are always computed from the geometry. faceIndices is set to -1 for every triangle (no B-Rep source).
Failable initializer rejects: empty inputs, index count not divisible by 3, indices out of range, mismatched normals count.
Bridge: one new symbol OCCTMeshCreateFromArrays(vertices, vertexCount, normals, indices, indexCount) -> OCCTMeshRef? — caller releases via the existing OCCTMeshRelease. Unblocks OCCTSwiftMesh#1 (v0.1.0 — Mesh.simplified(_:) via vendored meshoptimizer).
7 new tests covering round-trip, computed-normals correctness, supplied-normals preservation, and all four invalid-input rejection paths.
v0.156.1 (Apr 2026) — Public AssemblyNode.labelId + Document.node(at:) lookup (issue #93)
AssemblyNode.labelId was internal even though every other Document API works in terms of Int64 labelIds (removeShape(labelId:), componentLabelId(...), expandShape(labelId:), etc.). Consumers walking the assembly via Document.rootNodes → AssemblyNode.children couldn’t read each node’s labelId to identify it across calls. Driver: OCCTSwiftScripts#23 (occtkit inspect-assembly / set-metadata) needs stable IDs that round-trip.
Two tiny additive changes:
// 1. labelId is now public
public let labelId: Int64
// 2. New lookup on Document
public func node(at labelId: Int64) -> AssemblyNode?
node(at:) validates the labelId via OCCTDocumentLabelIsNull (O(1), consistent with the rest of the int64-based Document API) and returns nil for unknown labelIds. LabelIds are stable within a single Document instance — round-trips with rootNodes traversal in the same session.
No bridge changes. Two new tests covering the round-trip and rejection of nonexistent labelIds.
v0.156.0 (Apr 2026) — Quality release: drop deprecated GCE2d_* symbols
OCCT 8.0.0 deprecated the entire GCE2d_Make* family of 2D geometry constructors in favour of the canonical GC_Make*2d names — each old class is now literally a using GCE2d_X = GC_X2d typedef alias. This release migrates all internal C++ uses inside OCCTBridge.mm to the canonical names so we’re no longer building against deprecated identifiers.
GCE2d_MakeArcOfCircle → GC_MakeArcOfCircle2d
GCE2d_MakeArcOfEllipse → GC_MakeArcOfEllipse2d
GCE2d_MakeArcOfHyperbola → GC_MakeArcOfHyperbola2d
GCE2d_MakeArcOfParabola → GC_MakeArcOfParabola2d
GCE2d_MakeCircle → GC_MakeCircle2d
GCE2d_MakeEllipse → GC_MakeEllipse2d
GCE2d_MakeHyperbola → GC_MakeHyperbola2d
GCE2d_MakeLine → GC_MakeLine2d
GCE2d_MakeMirror → GC_MakeMirror2d
GCE2d_MakeParabola → GC_MakeParabola2d
GCE2d_MakeRotation → GC_MakeRotation2d
GCE2d_MakeScale → GC_MakeScale2d
GCE2d_MakeSegment → GC_MakeSegment2d
GCE2d_MakeTranslation → GC_MakeTranslation2d
14 #include directives + ~30 internal symbol uses migrated. Bridge ABI unchanged: the bridge’s own C function names (OCTGCE2dMake*) are preserved so Swift wrappers continue to call them by their existing names — this is a non-breaking internal hygiene release.
Operation count, test count, and suite count are unchanged — same OCCT objects, just constructed via canonical names. The @Suite("GCE2d_MakeLine") test label was renamed to @Suite("GC_MakeLine2d") for consistency. Source comments and // MARK: headers in Sources/OCCTSwift/Curve2D.swift and Sources/OCCTSwift/Document.swift were updated similarly.
This was the cleanup-half of a rescoped v0.156.0 plan. The OCAF/Message data introspection scope originally pencilled in for v0.156.0 was abandoned after a full audit revealed the project is at the asymptote of useful OCCT public surface — most flagged “missing” classes were already wrapped via the established OCCTDocumentRef + int64_t labelId pattern, and the genuinely unwrapped classes (~25 ops total: gp_Vec2f/3f, GeomConvert_FuncCone/Cylinder/SphereLSDist) are too small to justify a 100-op release on their own.
v0.155.1 (Apr 2026) — Wire(_:Shape) convenience initializer (issue #91)
Completes the v0.154.0 trio. Recovers a typed Wire from a generic Shape that wraps a TopoDS_Wire, returning nil on type mismatch. Mirrors Face(_:Shape) and Edge(_:Shape).
let box = Shape.box(width: 10, height: 10, depth: 10)!
let wireShapes = box.subShapes(ofType: .wire)
if let wire = Wire(wireShapes[0]) {
// typed Wire recovered from a wire-typed Shape
}
Unblocks face-rebuild flows where existing inner wires (returned as [Shape] from Shape.wires or subShapes(ofType: .wire)) need to be passed back into Shape.face(outer:holes:) — previously those wires were stuck as Shape because the Wire(handle:) initializer was internal. Concrete motivating case: preserving both bore and chamfer outlines on the same mid-face when extracting countersink mid-surfaces in UnfoldEngine.
Bridge: one new symbol OCCTWireFromShape(OCCTShapeRef) -> OCCTWireRef?.
v0.155.0 (Apr 2026) — SheetMetal.Builder: convex bends (issue #89)
The v0.151–v0.153 builder only supported concave bends (L-bracket-style, where the two flanges’ bodies overlap in volume around the seam). Convex bends — Z-section middle bends, offset brackets, gusseted brackets where one flange folds back on the opposite side — failed with BuildError.filletFailed because the seam edge is non-manifold (a kiss point with four boundary faces meeting at one line, which BRepFilletAPI_MakeFillet rejects).
v0.155 adds first-class convex bend support:
-
Auto-detected direction. Each bend is classified concave or convex from the relative position of the two flanges’ body centroids. No caller change needed; the existing v0.151–v0.153 fixtures (L, U, stepped Z) continue to build identically because they’re all concave.
-
Convex bend material. Convex bends build a curved-triangle prism that bridges the two flanges’ outer-corner edges with a cylindrical fillet on the outside surface, then boolean-fuses with the flanges. The “kiss point” stays sharp on the inside (which is the natural CAD interpretation when the user’s flange placements don’t leave room for an inside cylinder); the outside is rounded to the bend radius.
Bendstruct expanded with optional explicit controls:angle: Double?— bend angle in radians, signed (positive = concave, negative = convex). Nil means auto-infer from flange positions. Sign convention follows OCCT’s right-hand rule: angles are CCW-positive about the bend axis derived fromcross(fromFlange.normal, toFlange.normal), with concave-positive matching how a CAD designer thinks about bends.insideRadius: Double— replaces the legacy singleradius(which still works as a convenience init).outsideRadius: Double?— independent control of the outside fillet radius. Defaults to nil = match insideRadius for convex builds.materialThicknessAtBend: Double?— allow thinner material in the bend region than the flange thickness, common in etched parts where a thinned bend line allows tighter folds without cracking.direction: BendDirection—.auto(default),.concave, or.convexfor explicit override.
- The legacy
Bend(from:to:radius:)initializer is unchanged. All v0.151–v0.153 callers continue to work without modification.
The 93-face inside-corner-reinforcing-bracket from #89 (Z-section with both same-direction and convex bends) now builds cleanly. Test fixtures from the issue: symmetric Z, offset L with very short web, channel-with-flange, all pass.
Bridge: one new symbol OCCTWireCreateArcThroughPoints(s, m, e) for 3-point arc-wire construction (avoids the gp_Ax2 X-direction ambiguity of the angle-based arc API). Exposed as Wire.arc(start:midpoint:end:).
v0.154.0 (Apr 2026) — Face(_:Shape) and Edge(_:Shape) convenience initializers
Two tiny additive bridge symbols and their Swift conveniences. Recovers a typed Face or Edge from a generic Shape that wraps a TopoDS_Face / TopoDS_Edge (returns nil on type mismatch). Useful when a method gives back a Shape (e.g. subShapes(ofType: .face)) and you want the typed wrapper to call methods like area(), outerWire, length, etc., directly.
let box = Shape.box(width: 10, height: 10, depth: 10)!
let faceShapes = box.subShapes(ofType: .face)
if let face = Face(faceShapes[0]) {
print(face.area()) // 100
}
Bridge: OCCTFaceFromShape(OCCTShapeRef) -> OCCTFaceRef? and OCCTEdgeFromShape(OCCTShapeRef) -> OCCTEdgeRef?. Both return NULL when the shape’s ShapeType() doesn’t match. Unblocks the upcoming UnfoldEngine package, which builds on these.
v0.153.0 (Apr 2026) — SheetMetal.Builder step-aware bends (issue #86)
The v0.151 SheetMetal.Builder implementation extruded each flange at its full profile, fused them, then filleted the seam edge. That works when both flanges have matching extents along the seam direction, but fails on stepped seams — flanges that meet along less than their full extent (a narrow tab on a wider base, a U-channel with sides narrower than the spine). OCCT can’t cleanly fillet an edge that terminates at a free-face boundary, so the v0.151 builder reported BuildError.filletFailed and the downstream OCCTDesignLoop pipeline padded the narrower flange to match — both expensive and incorrect.
v0.153 lifts that limitation:
SheetMetal.Builder.build(flanges:bends:)now computes the seam intersection between each pair of flanges in a bend and splits the wider flange at the intersection endpoints before extruding. The matched-extent middle piece carries the bend; the outer pieces stay flat. The fillet machinery from v0.151 runs unchanged on the matched-extent piece, where it’s always well-formed.- For matched-extent inputs (where v0.151 already worked), the result is identical: the splitting step is a no-op.
- Two new error cases:
BuildError.seamsDoNotOverlap(fromID:toID:)if the two flanges’ seam edges don’t actually intersect along the seam line;BuildError.nonRectangularStepFlange(id:)if a flange would need to be split but its profile isn’t axis-aligned-rectangular (rectangular profiles cover the issue’s three test fixtures and the common cases; non-rectangular stepped seams are deferred).
The three reference fixtures from issue #86 all build cleanly:
- L-bracket with 80×40 base + 20×30 centred mounting tab.
- Z-bracket with 50×30 base + full-seam mid + 20×30 stepped top tab.
- U-channel with 100×40 spine + 80×15 stepped side flanges (narrower than the spine in the seam direction).
OCCTDesignLoop’s eval/describer_to_features.py can drop its seam-padding workaround and emit actual described flange dimensions; the existing typed SheetMetal.Flange / SheetMetal.Bend API and the JSON envelope are unchanged.
The unrelated v0.151 limitation about the bend axis being on the outside corner (sharp inner corner, filleted outer corner) still applies — that’s a different construction (real inside-radius + outside-radius bend) and is filed separately.
v0.152.1 (Apr 2026) — FeatureReconstructor.buildJSON decodes boolean (issue #88)
FeatureSpec.boolean (with op ∈ union | subtract | intersect, leftID, rightID) has been wired through applyBoolean since the typed Swift API landed, and v0.152’s inputBody makes it useful for cuts that reference the seeded body via @input. But the JSON decoder never picked it up — FeatureEntry.init(from:) had no case "boolean": branch, so JSON entries with "kind": "boolean" fell into the default: clause and were silently dropped.
- Adds the
case "boolean":decoder branch. Readsop(string),left,right, optionalid. Coding keys for these were already declared. - Bad
oprawValue surfaces as a recordable skip with reasonunsupported("boolean(op:smush)")rather than throwing — matches the rest of the reconstructor’s “graceful degradation” policy. - Unknown
kindstrings now also surface asSkippedentries when the JSON entry carries anid. Reason:unsupported("unknown JSON kind: …"). Stage:additive. Without this, typos inkindand version-drift schemas were silently swallowed; now they’re visible. Entries without an id continue to be silently ignored, matching the rest ofFeatureReconstructor(the kernel only records skips when there’s an id to attach them to).
Together these mean the inputBody → boolean(@input, slot) chain that v0.152 implies should work, actually does work end-to-end from JSON.
v0.152.0 (Apr 2026) — FeatureReconstructor.inputBody for chained composition (issue #87)
FeatureReconstructor.build(from:) previously always started from an empty BuildContext.current, with the in-progress shape grown purely from additive feature entries. That blocks chaining — composing a body via one kernel API (e.g. SheetMetal.Builder from v0.151) and then cutting / finishing into it via the reconstructor. v0.152 makes the kernel itself accept a starting body.
- Optional
inputBodyparameter on both build entry points:FeatureReconstructor.build(from: specs, inputBody: Shape? = nil)andFeatureReconstructor.buildJSON(_:inputBody:). When non-nil,BuildContext.currentis seeded with the input and the input is registered innamedShapesunder the sentinel id@input. When nil, behaviour is byte-for-byte identical to v0.151. FeatureReconstructor.inputBodySentinel— the literal string@input, exposed as a public constant so JSON envelopes and Swift callers share one source of truth. BooleanleftID/rightID,Fillet.edgeSelector.onFeature, andChamfer.edgeSelector.onFeatureall resolve@inputvia the standardnamedShapeslookup — no separate code path. Last-write-wins semantics: a feature withid == "@input"shadows the seed, which is the obvious behaviour.- No JSON schema change. Downstream callers using
buildJSONpassinputBody:from Swift; the JSON envelope itself is unchanged. Within the envelope, references to@inputare just regular id strings. - Stage ordering preserved. Additive features still union onto whatever
currentis at the start of stage 1 (input or empty). Subtractive / finishing / annotation stages run with the same dispatch as v0.151. The existingSkippedreporting (under-determined / OCCT failure / unresolved-ref / unsupported) is unchanged.
The immediate driver is the sheet-metal → reconstructor chain referenced by OCCTSwiftScripts#13: build a bent bracket via SheetMetal.Builder, then drill mounting holes into it with the reconstructor’s hole-placement and Skipped machinery. The verb-side wiring downstream is one line — FeatureReconstructor.buildJSON(envelope, inputBody: try GraphIO.loadBREP(at: path)).
This is also the primitive the planned Skipped resume-from-last-good-shape behaviour will need: “given a partially-built shape, continue applying remaining specs” reduces to an inputBody-aware build.
Out of scope: multi-body input lists (use Shape.compound upstream), round-tripping face / edge tags from prior history (gone after BREP serialisation), reverse decomposition (Shape → [FeatureSpec]).
v0.151.0 (Apr 2026) — Sheet-metal composition API (issue #85)
OCCT has no sheet-metal bend primitive and is not expected to grow one — CATIA / SolidWorks / FreeCAD all compose bends from extrude + union + fillet. v0.151 adds the canonical Swift-level composition so downstream consumers (OCCTDesignLoop’s VLM reconstructor, scripts, MCP tooling) do not each reinvent it.
SheetMetal.Flange— a closed 2D profile positioned in world space by explicit(origin, uAxis, vAxis, normal). All three axes are independent so left-handed world placements (e.g. a flange normal along +Y with the profile reading +X / +Z) are expressible without handedness surprises.vAxisdefaults tocross(normal, uAxis)when omitted.SheetMetal.Bend— names two flanges + an inside radius. No geometric data; the builder resolves the seam edge from the flange placements.SheetMetal.Builder.build(flanges:bends:)— extrudes each flange along its normal bythickness, fuses the bodies in order, then for each bend finds the seam edge(s) and appliesShape.filleted(edges:radius:). Seam finding walks the fused shape’s edges, keeps only those parallel tocross(nA, nB), and selects the one whose midpoint lies on each flange’s face that points toward the other flange — which uniquely identifies the bend and rejects the coincidental convex back corner.SheetMetal.BuildError— named cases for invalid thickness, empty flange list, duplicate/unknown IDs, invalid profile, extrusion/union/fillet failures, parallel flanges (no seam direction), and missing seam edge.CustomStringConvertiblefor direct logging.
Known limitation: stepped seams (flanges meeting along less than their full seam-direction extent, e.g. a narrow upright on a wider base) surface as BuildError.filletFailed. OCCT cannot cleanly round an edge that terminates at a free-face boundary; downstream callers should match flange widths along the seam or split the wider flange. Reverse-direction unwrap (bent BRep → flat cutting pattern) is the planned next addition to this namespace.
v0.150.0 (Apr 2026) — Pure-Swift PDF + SVG export + BOM + balloons
Second half of the v0.149 → v0.150 drawing-automation arc. Drawings now have three readable output formats (DXF for engineering tools, PDF for humans, SVG for the web) plus the assembly-drawing primitives that make BOM-driven output a one-call operation.
PDFWriter+Exporter.writePDF(drawing:to:pageSize:)/writePDF(sheet:body:to:)— pure-Swift PDF 1.4 writer. No UIKit / AppKit / Core Graphics dependency; works on macOS, iOS, and Linux. Helvetica font, one page per file, content stream installs a mm→pts CTM so staged geometry stays in drawing units. Per-layer ISO 128-20 stroke weights (0.5 mm VISIBLE / OUTLINE, 0.25 mm HIDDEN / CENTER / DIMENSION / TEXT, 0.18 mm HATCH) with dashed / chain patterns on HIDDEN / CENTER. Circles rendered as four cubic Bézier segments; arcs split into ≤90° Bézier chunks.SVGWriter+Exporter.writeSVG(drawing:to:)/writeSVG(sheet:body:to:)— pure-Swift SVG 1.1 writer. One<g>group per layer with stroke / stroke-width / stroke-dasharray attributes. Arcs emitted as native SVG<path d="M… A …"/>. ViewBox explicit or computed from content bounds. Drawing’s mathematical Y (up) mapped to SVG’s screen Y (down) via a group-levelscale(1,-1); each<text>carries its own counter-transform so glyphs read right-side up.DrawingAnnotation.balloon(Balloon)— new case carryingitemNumber+centre+radius+ optionalleaderTo. Rendered in every writer (DXF / PDF / SVG) as a circle + number text + optional leader line that exits the circle at the point nearest the target.Drawing.addBalloon(itemNumber:at:leaderTo:radius:id:)is the convenience entry point.BillOfMaterials— pure-SwiftCodablevalue type. Seven-column table (ITEM / PART NO / DESCRIPTION / QTY / MAT / MASS / NOTES) with per-column default widths; caller populates[Item]and callsrender(into: DXFWriter, at:). Origin is the bottom-right anchor so the table grows up and to the left (idiomatic placement above a title block).Sheet.renderBOM(_:into:at:)convenience places the BOM right-aligned to the inner frame’s top edge.DrawingDispatch.swift— shared internal annotation + dimension dispatcher used byPDFWriterandSVGWriter.DrawingPrimitiveOpsstruct bundles the five drawing primitives (addLine / addPolyline / addCircle / addArc / addText) as closures; a single dispatch path handles everyDrawingAnnotationcase (centreline, centermark, textLabel, hatch, cuttingPlaneLine, balloon) and everyDrawingDimensioncase including tolerance rendering.DXFWritercontinues to use its own inline logic — not because it couldn’t be ported, but to keep its test coverage load-bearing and avoid regression risk.Exporter.pdfA3Landscape/pdfA4Landscape— named pts-space page-size constants. AlsoPDFWriter.addDimension(_:)/SVGWriter.addDimension(_:)mirror the DXF-side method added in v0.149 for ad-hoc dimension staging without aDrawing.
After v0.150, the only substantive drawing-layer gap is native DXF DIMENSION entities (still exploded LINE+TEXT), which remains demand-gated.
v0.149.0 (Apr 2026) — Sheet automation + tolerance + ordinate dimensioning
First of a two-release arc closing the last substantive drawing-automation gaps: one-call multi-view layout, typed tolerance data on every dimension, and ISO 129-1 §9.3 ordinate dimensioning.
Sheet.standardLayout(of:scale:margin:includeIso:)— composes front / top / side / optional isometric views of aShapeonto the sheet’s inner frame as a 2x2 grid. Arrangement follows the sheet’sProjectionAngle: first-angle places top below front, third-angle places top above. Uniform scale is computed to fit the widest projected view; callers can pass a smallerDrawingScaleto override. Returns aStandardLayoutwhosePlacedViews hold the original Drawings (attach dimensions per view before callingrender(into:)).Drawing.addAutoDimensions(from:viewDirection:minRadius:dimensionOffset:bounds:)— heuristic dimensioner: adds a linear dimension for the projected X and Y extents of the shape’s bounding box, plus a diameter dimension on every visible circular edge. Edge-on circles are skipped (mirrors theaddAutoCentermarksdetection);minRadiusfilters noise holes.DrawingTolerance— typed,Codableenum carried astolerance: DrawingToleranceon everyDrawingDimensionpayload (Linear, Radial, Diameter, Angular, Ordinate). Cases:.none,.symmetric(Double),.bilateral(plus:minus:),.unilateral(Double),.fitClass(String),.limits(lower:upper:). Inline cases fold into the nominal label; multi-value cases render as stacked upper/lower TEXT in DXF at ~55% height, placed perpendicular to each dimension’s text baseline.DrawingDimension.ordinate(Ordinate)— shared-origin X+Y dimensioning for CNC reference-datum workflows. Each feature carries its own position plus optional custom label; a singletoleranceapplies across all features. DXF emit draws a small origin cross, per-feature extension lines with ticks at the origin baseline, and offset labels perpendicular to each line.Drawing.addOrdinateDimensions(origin:features:tolerance:id:)is the convenience entry point.DrawingDimension.Ordinate+FeatureareCodablefor JSON-driven pipelines.DXFWriter.addDimension(_:)— public single-entity dispatch over everyDrawingDimensioncase; useful for tests and for scripts that compose DXFs from dimension values without going through aDrawing.
v0.148.0 (Apr 2026) — Drawing.append(_:) unified dispatcher
Small release closing #83 and #84 — both asked for the same thing: a public Drawing.append(_:) that dispatches every DrawingAnnotation case without the consumer-side switch blind spot.
Drawing.append(_ annotation: DrawingAnnotation)— appends anyDrawingAnnotationcase (centreline, centermark, textLabel, hatch, cuttingPlaneLine). When new cases land, the dispatcher updates in one place, not in every consumer.Drawing.append(contentsOf: [DrawingAnnotation])— for factory output likeDrawingAnnotation.surfaceFinish(...),.featureControlFrame(...),.datumFeature(...),.breakLine(...),.cosmeticThreadSideView(...)which all return arrays.Drawing.append(_ dimension: DrawingDimension)/append(contentsOf: [DrawingDimension])— symmetric for dimensions.
Downstream replay(...) helpers (OCCTSwiftScripts, OCCTSwiftPartsAgent) collapse to one-line drawing.append(contentsOf: DrawingAnnotation.surfaceFinish(...)). The existing addCentreLine / addCentermark / addTextLabel / addHatch / addCuttingPlaneLine typed factories continue to work unchanged; they’re now a thin convenience over append(_:) conceptually (though the storage path is identical either way).
v0.147.0 (Apr 2026) — Drawing + FeatureSpec consumer polish
Closes four small follow-up issues (#79, #80, #81, #82) that downstream consumers (OCCTSwiftScripts, OCCTDesignLoop, MCP tooling) asked for to remove boilerplate and unblock JSON-driven workflows.
- #80
Edge.curve3D: DirectEdge → Curve3Dbridge. Ensures the 3D curve is built viaBRepLib::BuildCurves3dfor pcurve-only edges. Returns the rawGeom_Curveso consumers can callcurve.circleProperties/lineProperties/ etc. without DownCast gymnastics. - #79
Drawing.addAutoCentermarks(from:viewDirection:extent:minRadius:bounds:): symmetric toaddAutoCentrelines. Walks circular edges, projects each centre into the view plane, adds.centermarkannotations. Skips edges whose circle plane is parallel to the view (edge-on).minRadiusfilters small holes;boundsfilters centermarks outside the view. - #81
DrawingAnnotation.CuttingPlaneLine+Drawing.addCuttingPlaneLine: typed ISO 128-40 cutting-plane line. Computes trace in view 2D from cutting plane normal × view direction. DXFWriter renders heavy-chain ends, thin-chain middle, perpendicular arrows, and label letters at both ends. - #82
FeatureSpecCodable conformance:FeatureSpec+ all nested types (Revolve,Extrude,Hole,Thread,EdgeSelector,Fillet,Chamfer,Boolean) nowCodable. UnblocksFeatureReconstructor.buildJSON+ Python / MCP driven reconstruction pipelines without each consumer mirroring the types in their own schema.
v0.146.0 (Apr 2026) — ISO drawings III: cosmetic threads, surface finish, GD&T symbols, detail views
Closes the ISO drawings arc (#78). Final release ships cosmetic threads (#77), ISO 1302 surface finish, ISO 1101 GD&T symbols, and compressed-view conventions (detail + break lines).
- #77
DrawingAnnotation.cosmeticThreadSideView/cosmeticThreadEndView: ISO 6410 cosmetic thread representation. Side view: two parallel lines at minor diameter spanning the thread length, optional callout text. End view: 3/4 broken arc set (0–90° / 90–180° / 180–315° with a 45° gap).Drawing.addCosmeticThreadSide(...)andDXFWriter.addCosmeticThreadEndView(...)convenience wrappers. - ISO 1302 surface finish:
SurfaceFinishSymbolenum (.any/.machiningRequired/.machiningProhibited).DrawingAnnotation.surfaceFinish(at:leaderTo:ra:symbol:method:)produces the check-mark geometry with Ra value label, horizontal bar for machiningRequired, optional production-method text, and leader line to the target feature. - ISO 1101 GD&T symbols:
GDTSymbolenum covering all 15 ASME/ISO geometric characteristics (straightness, flatness, circularity, cylindricity, profile of line/surface, perpendicularity, parallelism, angularity, position, concentricity, symmetry, coaxiality, circular runout, total runout).DrawingAnnotation.featureControlFrame(at:symbol:tolerance:datums:leaderTo:)emits the classic[⌖] [0.1] [A] [B] [C]rectangular frame.DrawingAnnotation.datumFeature(label:at:pointingTo:)emits the boxed letter + triangle pointer. - Detail views:
Drawing.detailView(at:scale:)returns aTransformedDrawingsuitable for placing a scaled-up region of the parent drawing at a specific sheet location. - Break lines:
DrawingAnnotation.breakLine(from:to:amplitude:)emits ISO 128-30 compressed-length zigzag marker as 5 line segments.
v0.145.0 (Apr 2026) — ISO drawings II: sheet templates, title blocks, projection symbols
Second release in the ISO drawings arc (#78). Closes #76 — adds ISO 5457 trimmed-sheet templates, ISO 7200 title blocks, and ISO 5456-2 projection symbols as first-class OCCTSwift API.
PaperSize:A0/A1/A2/A3/A4with.size(in: .landscape)/.portraitreturning ISO 5457 trimmed dimensions in mm.Orientation:.landscape/.portrait.ProjectionAngle:.first(ISO / Europe) /.third(ANSI / USA).TitleBlock: ISO 7200 mandatory + optional fields (title, drawingNumber, owner, creator, approver, documentType, dateOfIssue, revision, sheetNumber, language, material, weight, scale).Sheet: ties PaperSize + Orientation + ProjectionAngle + TitleBlock together.render(into: DXFWriter)emits border + ISO 5457 inner frame with correct margins (20 mm binding left, 10 mm other edges on A0–A3), centring marks at edge midpoints, and the title block in the bottom-right.innerFrameproperty exposes the drawable rectangle for layout.ProjectionSymbol:ProjectionSymbol.render(.first, at:, into:)emits the ISO 5456-2 truncated-cone + circle pair at the correct relative position for first / third angle.- DXFWriter gets two new layers:
BORDERandTITLE.
v0.144.0 (Apr 2026) — ISO drawings I: section views, hatch, multi-view, style foundations
First of a three-release ISO-drawings arc (tracked in #78). Closes #73, #74, #75 and adds the ISO 128-20 / 3098 / 5455 style primitives every downstream sheet producer needs.
- #75
Drawing.transformed(translate:scale:)+Drawing.bounds: newTransformedDrawingwrapper andDXFWriter.collectFromDrawing(_ transformed:)overload.Drawing.bounds(deflection:includeAnnotations:)returns the drawing’s 2D axis-aligned bounding box. Unblocks multi-view sheet composition:writer.collectFromDrawing(view.transformed(translate: offset, scale: 0.5)). - #73
Shape.section2D(planeOrigin:planeNormal:planeU:deflection:)+Shape.section2DView(...): slice a shape with a plane, return aDrawingin the plane’s own 2D frame (not world space).section2DViewwraps the contour with automatic ISO 128-40 hatching at 45° and an optional “A-A” label. - #74
Drawing.addHatch(boundary:angle:spacing:islands:): ISO 128-50 sectional-view fill. DXFWriter tessellates into line segments at the specified angle and spacing with island (hole) subtraction via even-odd rule scanlines. AddsHATCH+SECTIONXCAF layers. - G1 ISO 128-20 line widths + ISO 128-21 arrows + ISO 3098 text heights:
DrawingLineWidthenum (w013 → w200, ISO 1:1.4 series),DrawingTextHeightenum (h25 → h200) with.recommended(forPaper:)and.snap(_:),DrawingArrowStyle(filledClosed / openClosed90 / openClosed30 / tick),DrawingLineStyle.defaultWidth/.boldWidthper style. - G2 ISO 5455
DrawingScale: enum cases.one/.reduction(Int)/.enlargement(Int)/.custom(Double)with.factorand.labelaccessors.DrawingScale.preferredreturns the ISO-standard scale series (50:1 down to 1:1000).
v0.143.0 (Apr 2026) — Measurement ergonomics + clearing v0.142 deferrals
Small-but-broad release that sands the measurement papercuts surfaced by the v0.143 audit and retires every deferral the v0.142 release notes flagged. Roughly 40 ops: 4 measurement additions, 5 deferral clearings.
Measurement ergonomics (M1–M4):
Shape.volume/Shape.surfaceArea— verified already wrapped as optional properties (audit had missed them); no new code, just confirmation.Curve3D.distance(to: SIMD3)/Edge.distance(to: SIMD3)— one-liner point-to-curve distance when you don’t need the projected point / parameter.- Angle helpers:
Edge.angle(to:),Edge.isParallel(to:tolerance:),Edge.isPerpendicular(to:tolerance:),Face.angle(to:),Face.isParallel(to:),Face.isPerpendicular(to:),Face.isCoplanar(with:tolerance:). PlusConstructionAxis.angle(to:in:),ConstructionPlane.angle(to:in:).unsignedAngle(between:and:)free function for SIMD3 pairs. - Circle / revolution property extraction:
Edge.circlePropertiesreturns(center, radius, axis, isFullCircle, startAngle, endAngle)?for circular edges (three-point circle fit).Face.revolutionPropertiesreturns(axis, radius)?for cylindrical / conical / spherical / toroidal / surface-of-revolution faces.
Deferral clearings (from v0.142 release notes):
- Constructionspeak persistence (D1):
Document.addConstructionShape(_:)tags a shape with theCONSTRUCTIONXCAF layer;Document.constructionShapeLabelsenumerates on reload.ConstructionContext.materialize(in:graph:options:)resolves every plane/axis/point recipe and creates a finite representative shape (rectangular face for planes, bounded edge for axes, vertex for points) on the layer. STEP export preserves layer tags; import produces layer-marked shapes but not the typed recipes. Matches FreeCAD’s long-standing ceiling. - Arc / circle tessellation in
Sketch.buildProfile(D2):SketchElement.CurveKind.tessellate2D(segmentsPerRadian:)for all four curve kinds (line / polyline / arc / circle).Sketch.buildProfilenow lifts tessellated samples through the host plane’s frame. D-shaped and circular profiles now produce wires. - Named-shape registry for
FeatureSpec.Boolean(D3): Each feature with a non-nilidregisters its produced shape in an internal dict;Boolean.leftID/rightIDlook up by id..union/.subtract/.intersectall supported. Missing-id cases report.unresolvedRef. - Multi-leaf
.createdBydisambiguation (D4): newleafOccurrence: Int? = 0parameter onTopologyRef.createdBy— pick the Nth leaf when a creation has split into multiple live descendants.TopologyGraph.currentForms(of:)returns all leaves.leafOccurrence: nildisables forward-walk. - FeatureReconstructor ↔ TopologyGraph coupling for
EdgeSelector(D5):.nearPoint(point, tolerance)resolves edges by midpoint-distance within the target shape..onFeature(featureID)looks up the source feature’s shape via the named-shape registry and heuristically matches target edges whose midpoints coincide with the source’s edges..allfor uniform fillet/chamfer still works. (v1 heuristic; full graph-history dispatch remains available if consumers need per-op edge identity.)
Scope cuts: chamfer per-edge selector still requires a per-edge distance array the bridge doesn’t yet expose — falls through to .unsupported for .nearPoint / .onFeature on chamfer specifically. Uniform chamfer (.all) works. Flagged as a v0.144 candidate.
v0.142.0 (Apr 2026) — Construction geometry, sketches, FeatureReconstructor
Second release in the v0.141 → v0.143 arc — ships Phases 2–6 from #72 plus #62 in one go. With this release, OCCTSwift has the full construction-geometry vocabulary that agentic modelling needs: recipe-based references (v0.141) → typed construction entities → document context → sketches → declarative feature reconstruction.
ConstructionPlane/ConstructionAxis/ConstructionPoint(#72 Phase 2): Fusion-style recipe enums carryingTopologyRefs. 7 plane variants (absolute, offsetFromFace, throughAxis, tangentToFace, midPlane, byThreePoints, normalToEdge), 5 axis variants (absolute, alongEdge, normalToFace, throughPoints, intersectionOfPlanes), 6 point variants (absolute, atVertex, midpointOfEdge, centroidOfFace, atEdgeParameter, intersectionOfAxisAndPlane). Resolvers computePlacement/(origin, direction)/SIMD3<Double>against aTopologyGraph. TypedConstructionResolutionError.TopologyRef.containedInnow resolves (#72 Phase 2 unblock): newOCCTBRepGraphChildIndicesbridge +TopologyGraph.childIndices(rootKind:rootIndex:targetKind:)Swift wrapper.ConstructionContext(#72 Phase 3): Document-level collection with typed opaque IDs (PlaneID/AxisID/PointID), named entities, per-entity resolution against a graph, andallBroken(in:)diagnostic returning every entity that fails to resolve.Document.constructionContextis a lazy per-document property.Sketch+SketchElement(#72 Phase 4):Sketchis hosted on aConstructionPlaneID, carries an array ofSketchElements with per-elementisConstructionflag.buildProfile(in:graph:)is the single filter site (FreeCAD-inspired) — construction elements are excluded when assembling the profile wire. Elements:.line,.polyline,.arc,.circle(arcs/circles tessellation comes later).FeatureReconstructor(#62): DeclarativeFeatureSpectagged union (revolve / extrude / hole / thread / fillet / chamfer / boolean).FeatureReconstructor.build(from:)with staged additive → subtractive → finishing → annotation dispatch.EdgeSelectorenum with.all,.nearPoint,.onFeature—.onFeaturecurrently reports.unsupportedpending full TopologyGraph-integrated dispatcher;.allworks today for uniform fillet/chamfer.FeatureReconstructor.buildJSON(_:)front end parses the OCCTDesignLoop-compatible schema.Placementshared value type (origin + orthonormal basis) with ergonomicinit(origin:normal:)that picks deterministic x/y axes.
Scope of what the v1 implementation deliberately does not do (deferred to later iterations as concrete consumers surface):
- Constraint solving in
Sketch— explicit non-goal (see #72). - Named-shape registry for
FeatureSpec.Booleanwith id-based left/right selection. .onFeature/.nearPointedge resolution in fillet/chamfer dispatch — requires couplingFeatureReconstructorto a liveTopologyGraph, which is the natural next iteration once agents drive it.- XCAF
CONSTRUCTIONlayer persistence — recipes live in-memory; STEP round-trip drops them (matches FreeCAD’s 20-year limitation documented in #72). - Multi-leaf
.createdBydisambiguation when a single creation splits into many live descendants.
v0.141.0 (Apr 2026) — Construction-geometry foundation: BRepGraph history readback + TopologyRef
First release in the v0.141 → v0.143 “Construction Geometry” arc (tracked in #72). Builds the substrate for recipe-based topology references that survive mutations — the prerequisite for agent-driven CAD where construction planes / axes / points stay attached to model features through edits.
- BRepGraph history record readback (#72 Phase 0): Exposes the old→new node mappings that the OCCT kernel was already recording.
TopologyGraph.historyRecord(at:),.historyRecords,.findOriginal(of:),.findDerived(of:),.recordHistory(operationName:original:replacements:). NewTopologyGraph.NodeRefvalue type (kind + index) andHistoryRecordwith full mapping. TopologyRefrecipe type (#72 Phase 1): Indirect enum expressing topology references as recipes evaluated against the current graph, not as indices (Onshape FeatureScript-inspired). Cases:.literal(NodeRef),.createdBy(operationName:kind:occurrence:),.containedIn(parent:kind:occurrence:),.splitOf(original:occurrence:). TypedTopologyResolutionErrorenum for failure modes.TopologyGraph.resolve(_:): Evaluates recipes by walking history records, returnsResult<NodeRef, TopologyResolutionError>..createdBypicks up newly-introduced replacements by operation name and walks forward to the current form;.splitOfpicks the Nth replacement of a split original; ancestor-resolution failures surface as.ancestorMissing.
Scope: .containedIn returns .noCurrentDescendant until Phase 2 adds child-at-index accessors. .createdBy current-form walk picks the first leaf in deterministic order; multi-leaf disambiguation (useful when a single creation splits into many live descendants) comes in later phases.
v0.140.0 (Apr 2026) — GD&T write path + typed dimension/tolerance enums
Completes the read-only GD&T support shipped in v0.21.0 with a write path. Downstream callers can now author XCAFDoc_Dimension / XCAFDoc_GeomTolerance / XCAFDoc_Datum attributes, attach them to shape labels, and round-trip through STEP AP242. Typed Swift enums replace the raw Int32 type codes from v0.21.0 for the full list of XCAFDimTolObjects types.
- Typed enums:
Document.DimensionType(all 32XCAFDimTolObjects_DimensionTypecases — Location_Linear, Size_Diameter, Size_Radius, toroidal variants, etc.) andDocument.GeomToleranceType(all 16 — flatness, perpendicularity, position, profileOfLine, etc.). - Typed value types:
Document.Dimension,Document.GeomTolerance,Document.Datum. Accessors:typedDimension(at:),typedGeomTolerance(at:),typedDatum(at:),typedDimensions,typedGeomTolerances,typedDatums. - Write path:
Document.createDimension(on:type:value:lowerTolerance:upperTolerance:),createGeomTolerance(on:type:value:),createDatum(name:),setDimensionTolerance(at:lower:upper:). Returns the new attribute’s index or nil on failure. - Bridge additions:
OCCTDocumentCreateDimension,OCCTDocumentCreateGeomTolerance,OCCTDocumentCreateDatum,OCCTDocumentSetDimensionTolerance.
Scope: full modifier / qualifier / grade sequences (XCAFDimTolObjects_DimensionModif, GeomToleranceModif, DatumSingleModif etc.) remain partial wrapping — added on demand. This release covers the 90%-case authoring path.
v0.139.0 (Apr 2026) — Thread Form v2 + cleanup
Replaces v0.138’s circular-sweep thread placeholder with a real truncated V-profile following ISO-68 / UN conventions. Also folds in two quality-of-life cleanups (#68 boolean arg labels, #69 versioned MARK headers).
Behaviour change: callers of v0.138’s Shape.threadedHole / threadedShaft will now receive geometry that actually looks like a thread in HLR reprojection (alternating diagonal edges at pitch spacing) rather than a helical groove. API signatures unchanged; new default parameters (starts: 1, runout: .none) preserve single-start no-runout behaviour.
- Thread Form v2 (#66 follow-up):
ThreadCutterProfilebuilds a truncated trapezoidal cross-section with 30° flanks (60° included), H/8 crest flat, H/4 root flat. Swept along a helical spine withBRepOffsetAPI_MakePipeShell(correctedFrenet mode) and boolean-cut against the target. NewcrestFlat/rootFlat/minorDiameteraccessors onThreadSpec. NewRunoutStyleenum (.none/.filleted(radius:)/.tapered(turns:)). Newstarts: Intparameter onthreadedHole/threadedShaftfor multi-start threads. - Boolean op labels (#68):
Shape.union(_:),Shape.intersection(_:),Shape.section(_:)now matchShape.subtracting(_:)— all unlabelled, consistent withSet.union(_:)/Set.intersection(_:). Deprecatedwith:-labelled shims kept for backwards compatibility. - MARK header refactor (#69): 32 versioned grab-bag MARK headers (
// MARK: - v0.X.Y: A, B, C) renamed to feature-first format (// MARK: - A, B, C (v0.X.Y)). Xcode jump-to-section and grep-for-feature now work; OCCTMCP’s MARK-based API-reference generator can categorise without a regex fallback.
Tapered-runout law-based pipe-shell is tracked as a follow-up — the .tapered case falls back to .filleted until BRepOffsetAPI_MakePipeShell::SetLaw is wrapped.
v0.138.0 (Apr 2026) — Engineering Drawings II: DXF export + thread features
Second release in the v0.137 → v0.139 arc. Closes #63 (DXF export) and #66 (ISO thread features). ~50 ops.
- DXF 2D writer (#63): Custom pure-Swift DXF R12 ASCII writer (OCCT ships no DXF support — confirmed by audit).
Exporter.writeDXF(drawing:to:deflection:)walks aDrawing’s visible / hidden / outline edges throughShape.allEdgePolylinesand emits LINE / LWPOLYLINE / CIRCLE / ARC / TEXT entities. Layers: VISIBLE / HIDDEN / OUTLINE / CENTER / DIMENSION / TEXT, with appropriate linetypes (CONTINUOUS / DASHED / CHAIN). Dimensions from v0.137’sDrawingDimensionare emitted as exploded LINE+TEXT geometry (universally readable).Exporter.writeDXF(shape:to:viewDirection:)convenience combines projection and write. PublicDXFWriterfor callers composing DXF manually. - Thread features (#66):
ThreadFormenum (iso68 / unified);ThreadSpecstruct withparse("M5x0.8"),parse("1/4-20 UNC"), metric-coarse-pitch table, theoretical and cut depth accessors, minor-diameter computation.Shape.threadedHole(axisOrigin:axisDirection:spec:depth:)andShape.threadedShaft(axisOrigin:axisDirection:spec:length:)produce helical cut / boss geometry viaBRepOffsetAPI_MakePipeShellsweep of a circular profile. Integrates with #62’sFeatureReconstructor—FeatureSpec.Threadcan now route through real geometry instead of annotation-only.
Scope decisions: v1 threads use a circular sweep cross-section rather than full 60° flank triangle — produces correct handedness, pitch, diameter, and depth for reprojection diff and visualisation; manufacturing-accurate flanks land in a follow-up release. Multi-start threads, ACME / BSP / NPT forms, and full BRepOffsetAPI_MakePipeShell option wrapping (SetForceApproxC1, multi-profile Add()) deferred. GLTF Shape-level export, PLY import, STEP/IGES option completeness dropped from v0.138 — Document-level GLTF already ships, and the remaining gaps are low priority vs. closed-loop pipeline needs.
v0.137.0 (Apr 2026) — Engineering Drawings I: axes, dimensions, centrelines
Keystone release for the v0.137 → v0.139 “Engineering Drawings” series (tracked in #67). Adds axis extraction from shapes (#65), a pure-Swift value-type dimensioning API on Drawing (#64), and auto-centreline generation bridging the two. ~60 ops.
- Axis extraction (#65):
Face.primaryAxis,Shape.revolutionAxes(tolerance:),Shape.symmetryAxes(fractionalTolerance:),Surface.torusAxis,Surface.revolutionAxis. NewShapeAxisvalue type with.cylinder/.cone/.sphere/.torus/.revolution/.extrusion/.symmetrykinds. Bridge:OCCTSurfaceTorusAxis,OCCTSurfaceRevolutionAxis,OCCTSurfaceRevolutionLocation,OCCTFaceGetPrimaryAxis,OCCTShapeRevolutionAxes,OCCTShapeSymmetryAxes. - Surface introspection completeness: typed
Surface.SurfaceType+Surface.surfaceKind;Surface.Continuity+Surface.continuityClass; type-predicate conveniencesisPlane/isCylinder/isCone/isSphere/isTorus/isBezier/isBSpline/isSurfaceOfRevolution/isSurfaceOfExtrusion/isOffsetSurface. - Drawing dimensioning API (#64):
DrawingDimensiontagged union (linear / radial / diameter / angular) +DrawingAnnotationtagged union (centreline / centremark / text label).DrawingLineStyleenum. Methods onDrawing:addLinearDimension,addRadialDimension,addDiameterDimension,addAngularDimension,addCentreLine,addCentermark,addTextLabel,clearAnnotations, plusdimensions/annotationsaccessors. Pure-Swift value types — XDE round-trip deferred to v0.139 (#67). - Auto-centreline generation (#64 ↔ #65):
Drawing.addAutoCentrelines(from:viewDirection:overshoot:tolerance:bounds:)projects a shape’s revolution axes into the drawing’s view plane and emits chain-pattern centrelines; axes parallel to the view direction are returned in.skipped.
Scope decisions (see #67 for rationale): Full PrsDim display-dimension completeness (MaxRadius / MinRadius / Chamf2d / Chamf3d) and PrsDim geometric-relation wrapping (Concentric / Parallel / etc.) were cut from v0.137 — they are AIS display objects with low marginal value compared to the Swift value-type API that drives the closed-loop drawing workflow.
v0.132.0 - v0.136.0 (Apr 2026) — BRepGraph Topology Graph
Wraps OCCT’s new BRepGraph API — graph-based B-Rep topology with cache-friendly traversal, O(1) upward navigation, and parallel geometry extraction. 163 operations across 5 releases.
- v0.136.0: ML-friendly graph export (COO adjacency, node features, JSON), UV-grid face sampling (positions/normals/curvatures), edge curve sampling — for GNN/UV-Net/BRepNet pipelines
- v0.135.0: Builder mutations — AddVertex/Shell/Solid, AddFaceToShell/ShellToSolid, AddCompound, RemoveNode/Subgraph, AppendShape, deferred invalidation, SplitEdge, ReplaceEdgeInWire
- v0.134.0: Product/Occurrence assembly queries, RefsView per-kind counts and entry access, edge start/end vertices, shell closure, compound hierarchy
- v0.133.0: Shape reconstruction from graph nodes, BRepGraph_Tool vertex/edge/face geometry access, CoEdge half-edge queries, history tracking, graph copy/transform, poly counts
- v0.132.0: Core graph — build from shape, topology/geometry counts, face adjacency, shared edges, edge boundary/manifold, child/parent explorers, validate, compact, deduplicate, stats
v0.129.0 - v0.131.0 (Apr 2026) — RC5 New APIs
- v0.131.0: Approx_BSplineApproxInterp, GeomEval TBezier/AHTBezier curves+surfaces, GeomAdaptor_TransformedCurve
- v0.130.0: GeomEval analytical curves (helix, sine wave), analytical surfaces (ellipsoid, hyperboloid, paraboloid, helicoid), Geom2dEval spirals, GeomFill_Gordon, PointSetLib, ExtremaPC
- v0.129.0: IGES mutex serialization (thread safety fix per OCCT #1179)
v0.120.0 - v0.128.0 (Apr 2026) — Completion & Polish
Final method-level coverage of all user-facing OCCT classes.
- v0.128.0: v0.128.0 release (3333 ops total)
- v0.125.0: BSplineSurface deep (20), Geom2d_BSpline (20), BezierCurve (8), BezierSurface (12)
- v0.124.0: ChamferBuilder (20), FilletBuilder (16), WireAnalyzer (18)
- v0.123.0: ThruSections/CellsBuilder/PipeShell/UnifySameDomain/Section extensions
- v0.122.0: WireFixer, ShapeFix_Edge, BRepTools/BRepLib statics, History, Sewing extensions
- v0.121.0: GLTF import/export (xcframework rebuilt with RapidJSON), FilletBuilder, ChamferBuilder
- v0.120.0: IsCN, ReversedParameter, ParametricTransformation, gp extras, surface reversed copies
v0.110.0 - v0.119.0 (Mar-Apr 2026) — Constraint Solvers & Serialization
- v0.119.0: BREP serialization, gp_Pln/gp_Lin distance/contains, BezierSurface queries
- v0.118.0: BRepBndLib, ShapeAnalysis tolerance, BRepAlgoAPI_Check/Defeaturing
- v0.116.0: Helix construction, gp_Ax3/GTrsf2d/Mat2d, quaternion interpolation
- v0.115.0: Interpolation expansion, ThruSections builder, Triangulation queries
- v0.114.0: TopoDS_Builder, ShapeContents, FreeBoundsProperties, WireBuilder
- v0.113.0: MakeEdge completions, multi-result projections, DistShapeShape full results
- v0.112.0: RWMesh iterators, Intf_Tool, BRepAlgo_AsDes, BiTgte, wire/shell construction
- v0.111.0: PSO, GlobOptMin, FunctionRoots, GaussIntegration, BRepLProp
- v0.110.0: Constraint solver infrastructure — C callback adapters for OCCT math solvers
v0.100.0 - v0.109.0 (Mar 2026) — Geometry Factories & Extrema
- v0.109.0: Extrema elementary distances, TrigRoots, IntAna2d, BRepAlgo_NormalProjection
- v0.108.0: Complete Geom_ and Geom2d_ method coverage — all conic/surface property methods
- v0.107.0: BSpline manipulation (3D/2D/surface), Bezier methods, BRepTools, Sewing, Hatch
- v0.106.0: GC surface factories, ShapeAnalysis_Wire/Edge, BRepLib_MakeEdge2d
- v0.105.0: GC/GCE2d geometry factories, GCPnts uniform sampling, CompCurveToBSpline (90 ops)
- v0.104.0: BndLib analytic bounding, OSD_Host/PerfMeter, IntAna_IntQuadQuad
- v0.103.0: gce transform factories, GProp element properties, Plate constraints
- v0.102.0: TopExp adjacency, Poly_Connect mesh adjacency, BRepOffset_Analyse
- v0.101.0: Geom_TrimmedCurve, BRepLib_FindSurface, ShapeAnalysis_Surface, Resource_Manager
- v0.100.0: RWStl I/O, ShapeAnalysis_Curve statics, BRepExtrema_SelfIntersection
v0.90.0 - v0.99.0 (Mar 2026) — OCAF Extensions & Math
- v0.99.0: Convert_CompBezierCurves, Geom_OffsetSurface, OSD_File, ShapeFix_Wireframe
- v0.98.0: IntAna analytic intersections, OSD_Chronometer/Process, Draft_Modification
- v0.97.0: BRepAlgo_Loop, Bnd_BoundSortBox, BRepGProp_Domain, TNaming_Naming, Precision
- v0.96.0: XCAFDoc_AssemblyItemRef, BRepAlgo_Image, OSD_Path, BRepClass_FClassifier
- v0.95.0: Convert ellipse/hyperbola/parabola/cylinder/cone/torus to BSpline
- v0.94.0: math_Matrix/Gauss/SVD/PolynomialRoots/Jacobi, Convert circle/sphere to BSpline
- v0.93.0: OSD_MemInfo, ShapeFix_EdgeProjAux, Geom2dAPI_Interpolate, BRepAlgo_FaceRestrictor
- v0.92.0: Bnd_OBB, Bnd_Range, BRepClass3d point-in-solid, TDataXtd_Constraint
- v0.91.0: ElCLib curve evaluation, ElSLib surface evaluation, gp_Quaternion, OSD_Timer
- v0.90.0: TDF_ChildIDIterator, TDocStd_PathParser, TFunction_DriverTable, TNaming extensions
v0.80.0 - v0.89.0 (Mar 2026) — Extrema, Color Science & OCAF Deep
- v0.89.0: TDF_Transaction/Delta, TDF_ComparisonTool, TDocStd_XLinkTool
- v0.88.0: TNaming extensions, TDataStd_IntPackedMap, TDataStd_NoteBook
- v0.87.0: TDataStd_Tick/Current, ShapeAnalysis_Shell, CanonicalRecognition
- v0.86.0: TDataStd extended attributes (BooleanArray, ByteArray, IntegerList, etc.)
- v0.85.0: UnitsAPI, BinTools binary I/O, Message_Messenger/Report
- v0.84.0: VrmlAPI_Writer, TDataStd_Directory/Variable, TDocStd_XLink
- v0.83.0: XCAFDoc attributes, Notes, ClippingPlaneTool, AssemblyGraph (97 ops)
- v0.82.0: Quantity_Period/Date, Font_FontMgr, Image_AlienPixMap (39 ops)
- v0.81.0: Quantity_Color, Quantity_ColorRGBA, Graphic3d materials (24 ops)
- v0.80.0: Extrema 3D/2D, GeomTools persistence, ProjLib, gce factories (35 ops)
v0.70.0 - v0.79.0 (Mar 2026) — TKBool, TKFillet, TKHlr & Geometry Deep
- v0.79.0: Poly_CoherentTriangulation, BRepFill_Evolved, BRepExtrema_DistanceSS, GeomFill
- v0.78.0: BRepTools modifications, ShapeUpgrade_SplitSurface, GeomConvert, Poly_Polygon
- v0.77.0: GeomLib utilities, GccAna circle/line solvers, Approx_SameParameter
- v0.76.0: Geom_CartesianPoint, Geom_Direction, Axis1/2Placement, ShapeConstruct_Curve (41 ops)
- v0.75.0: BiTgte_Blend, GeomConvert_ApproxCurve/Surface, GCPnts, BRepGProp
- v0.74.0: TKMesh/TKOffset/TKPrim/TKShHealing/TKTopAlgo gap closure
- v0.73.0: Extended HLR edges, HLRAppli_ReflectLines, Intrv_Interval (29 ops)
- v0.72.0: LocOpe_Gluer, ChFi2d_Builder/ChamferAPI/FilletAPI, FilletSurf_Builder
- v0.71.0: IntTools_BeanFaceIntersector, BOPAlgo_WireSplitter, BRepFeat_SplitShape
- v0.70.0: IntTools EdgeEdge/EdgeFace/FaceFace, BOPAlgo BuilderFace/BuilderSolid
v0.60.0 - v0.69.0 (Mar 2026) — Data Exchange & TKGeomAlgo
- v0.69.0: NLPlate G2/G3, Plate_Plate solver, GeomPlate, GeomFill Generator (20 ops)
- v0.68.0: TopTrans_CurveTransition, GeomFill trihedrons, GccAna_Circ2d3Tan (18 ops)
- v0.67.0: FairCurve, LocalAnalysis, TopTrans SurfaceTransition (8 ops)
- v0.66.0: Full TkG2d — Point2D, Transform2D, AxisPlacement2D, Vector2D (44 ops)
- v0.65.0: BOPAlgo RemoveFeatures/Section, ShapeBuild, ShapeExtend, ShapeUpgrade (24 ops)
- v0.64.0: ProjLib, BRepOffset_Offset, Adaptor3d_IsoCurve (9 ops)
- v0.63.0: GeomLProp, BRepOffset_SimpleOffset, GeomInt_IntSS, Contap_Contour (17 ops)
- v0.62.0: BRepLib topology, MakeEdge2d, ShapeCustom, LocOpe, CPnts (22 ops)
- v0.61.0: Approx, Contap, BOPAlgo, IntCurvesFace, BRepMesh, GeomPlate (19 ops)
- v0.60.0: XDE/XCAF Full Coverage (42 ops)
v0.50.0 - v0.59.0 (Feb-Mar 2026) — OCAF & Data Exchange
- v0.59.0: IGES/OBJ/PLY Full Coverage (23 ops)
- v0.58.0: STEP Full Coverage (25 ops)
- v0.57.0: OCAF Persistence (17 ops)
- v0.56.0: TDataXtd + TFunction (29 ops)
- v0.55.0: TDataStd Attributes (25 ops)
- v0.54.0: TDF Core + TDocStd (31 ops)
- v0.50.0-v0.53.0: Various additions
v0.38.0 - v0.49.0 (Feb 2026) — Audit & Gap Closure
Systematic OCCT test suite audit rounds (7 rounds total), closing gaps in primitives, sweeps, booleans, modifications, healing, measurement, and topology.
v0.27.0 - v0.37.0 (Feb 2026) — RC4 Upgrade & Feature Expansion
- OCCT 8.0.0-rc3 → rc4 upgrade
- Feature-based modeling, pattern operations, shape editing
- Topological naming (TNaming), OCAF framework
- TDataStd/TDataXtd attributes, TFunction framework
v0.16.0 - v0.26.0 (Feb 2026) — Parametric Geometry
- 2D/3D parametric curves (Geom2d, Geom) with Metal draw methods
- Parametric surfaces with curvature analysis
- Law functions for variable-section sweeps
- Medial axis transform
- Camera, selection, presentation mesh
- Color science, materials
v0.6.0 - v0.15.0 (Jan 2026) — XDE & Annotations
- XDE document support (assembly, colors, materials, GD&T)
- Annotations (dimensions, text labels, point clouds)
- KD-tree spatial queries
- Polynomial solver, hatch patterns
v0.1.0 - v0.5.0 (Dec 2025 - Jan 2026) — Foundation
- Basic primitives, booleans, transforms
- Wire creation, sweep operations
- Mesh generation, STL/STEP import/export
- Shape validation and healing
- STEP optimization