Releases: SecondMouseAU/OCCTSwiftInteraction
Release list
v2.0.0-rc3
Third release candidate. Everything in v2.0.0-rc2, plus a feature that landed after it and a dependency fix that main could not build without.
rc2 still resolves and builds; it simply predates everything below. Consumers pinned to 2.0.0-rc2 should move to 2.0.0-rc3 to exercise the sidecar, which is what the remaining candidate phase is for.
Added: the agent-viewport selection bridge
startSelectionSidecar(directory:) / stopSelectionSidecar() on CADViewportService, plus PresentationStyle.agentHighlight (#16, #19). Three new public declarations; the wire types stay internal, because a host reads the JSON against the documented shape rather than linking this package for them.
An out-of-process agent can now read a human's live selection and ask the viewport to highlight geometry, over a file protocol in a shared directory. The design is written down in okf/decisions/agent-viewport-selection-bridge.md (#18), and the ADR rather than this source is the contract an MCP-side client codes against.
Four things it does that a file protocol usually gets wrong:
- Writes are atomic, temp-name-then-rename, so a reader never sees half a file. A truncated
selection.jsonthat still parses would read as a shorter selection, which is worse than a parse error. - Requests are moved to
handled/, never deleted, with their outcome recorded. Deleting throws away exactly what a retry needs, and makes "applied and cleaned up" indistinguishable from "never seen". - Liveness is an exclusive
flock(2)onhost.lock, released by the kernel when the process exits or crashes. Exact, needs no timeout, and detects a second host for free. A timestamp cannot tell an idle host from a dead one. ifRevisiongives compare-and-swap (#21), new in this candidate. Without it, an agent that read a selection, decided what to highlight, and lost a race to the human still applied its request and reportedapplied, with nothing recording that the premise had moved.
Fixed on main, and it never reached a release
The sidecar needs DirectoryWatcher, which was unreleased when it landed, so it pinned OCCTSwiftIO to a branch as a stopgap. That branch was deleted when the upstream PR merged, which is correct and also the moment the stopgap stopped working:
error: could not find a branch named 'issue-42-directory-watcher'
main could not resolve for a day; no tagged release ever carried the branch pin. rc2 predates it and pins OCCTSwiftIO from: "1.7.8" normally, so nothing a consumer could have pinned was affected.
DirectoryWatcher now ships in OCCTSwiftIO 1.8.0, so this is a version pin again (#20). Worth remembering as a pattern: a branch pin is a promise to come back, and deleting the branch on merge collects that debt immediately rather than eventually.
Changed
OCCTSwiftViewport floor raised to 1.2.0, which carries a real fix rather than bookkeeping: a Swift 6 concurrency crash where unannotated MTLCommandBufferHandler closures inherited @MainActor on Xcode 16.4, giving a SIGTRAP after every test had already reported green. Real-GPU and Xcode-16.4 only, masked locally by Xcode 26.x's NS_SWIFT_SENDABLE, so the declared floor is the only thing that carries the fix to a machine that would hit it.
Verified
384 tests in 36 suites. swift-format lint --strict and swiftlint --strict clean. CI green.
Why this is still a candidate
Nothing has exercised the sidecar end to end. It is a new protocol between two processes, and the tests drive both halves inside one. ACADStudio takes it next; that exercise is what 2.0.0 final is waiting on.
The other readiness items from #13 are done: the deprecated API is gone, CADViewportService is split into per-domain files, the pick-resolver deduplication is verified by execution rather than by reading, the identity-table docs are consolidated, and a real multibody fixture exists.
Going to final also means unwinding a prerelease chain. Six consumers pin this package's rc, two of them also pin OCCTSwiftScripts 1.7.0-rc1, and ACADStudio pins OCCTMCP 1.37.0-rc1. Those move to stable in dependency order once 2.0.0 ships (ecosystem#52).
v2.0.0-rc2
Second release candidate. Everything in v2.0.0-rc1, plus the two items below. Cut so consumers test the deprecation rather than a version that predates it.
Deprecated
OCCTSwiftAIS.InteractiveObject, SubShapeRef and SubShape. These moved to OCCTSwiftTools in #2, and the aliases left behind are now marked deprecated, naming 3.0.0 as the removal point.
They were deliberately left un-deprecated through 0.x for a real reason: a same-module typealias shadows the imported type it aliases, so deprecating them warned at every one of this package's own uses rather than only at a consumer's. That is now fixed at the source. This package names the canonical OCCTSwiftTools.* types directly at all 72 sites, so the deprecation reaches only consumers.
Consumers that import OCCTSwiftAIS and name these three types unqualified will now see warnings. The fix is to import OCCTSwiftTools and name them there; the types are identical, so nothing else changes. The aliases are not being removed in 2.0.0.
Testing
A multibody fixture exists, which this package has never had. Three solids at x -2..2, 27..33 and 57..63, as a BREP compound, with the OCCTSwiftScripts script that generated it committed alongside. Three new tests cover a real multibody import end to end: one entity owning one body per solid, framing spanning every body, and each body resolving its own picks to distinct geometry.
The separation is deliberate. Framing the first body spans 4mm and framing all three spans 65mm, so an assertion about the union cannot be satisfied by a camera that frames only body 0.
366 tests in 34 suites. swift-format lint --strict and swiftlint --strict clean.
Known, and why this is still an rc
2.0.0 is a coordinated bump. Every consumer currently caps this package at from: "0.x", which is .upToNextMajor and therefore excludes 2.x. A leaf application asking for 2.x while a shared dependency demands <1.0.0 is unsatisfiable, and SwiftPM resolves it by silently selecting an older, pre-merge version of that dependency, which then collides on duplicate target names at build-graph assembly. Tracked as ecosystem#52; the fleet is being bumped against this candidate now.
Separately, OCCTSwiftIO#41: a STEP assembly loads its root as a body alongside its children, so every triangle renders twice. Found while building the fixture. It does not affect this package's own code, and it is why the fixture is a BREP compound rather than a STEP assembly.
v2.0.0-rc1
Release candidate for the first stable release of the merged package. The stable line starts at 2.0.0, not 1.0.0, and deliberately: this repo inherited 44 tags from the three repositories it was merged from, 26 of which occupy v1.0.0 through v1.6.4 and none of which contain the merged package. Starting above the highest inherited tag means nothing has to be deleted.
Breaking
Ten deprecated declarations are gone. They were deprecated through the 0.x line; a major version is when removal is free.
| Removed | Replacement |
|---|---|
loadedShape |
loadedShapes / shape(id:) |
selected, selectedFace |
selection |
selectionSummary, SelectionSummary |
selectionMeasurements, SelectionMeasurements |
loadFile(from:progress:) |
loadFile(from:id:progress:) |
loadShape(_:id:) |
load(_:id:transform:) |
loadFromData(_:filename:progress:) |
loadFromData(_:filename:id:progress:) |
CADViewportView.init x2 |
the selection: initialiser |
Nothing auto-focuses the camera any more. The removed single-shape loaders were the only ones that did. The surviving multi-entity loaders have always documented that you call focus(on:) once you have loaded what should be visible, and that is now the only path.
The only consumer in the fleet using any of this is PadCAM, which is dormant; its revival already expects the OCCTSwiftInteraction migration.
Fixed
The camera framed only the first body of a multibody import. Two paths were affected and only one had been reported: focus(on:) resolved through shape(id:), which returns an entity's first body, and loadFile(from:id:progress:) registers a multibody file as one entity owning N bodies. So the documented way to frame a multibody import was the broken one.
A failed sub-shape conversion no longer shifts every later ordinal. A compactMap where a map was needed, silently renaming every sub-shape after an unconvertible one.
Changed
Every dependency resolves from its published URL, never from a ../<name> sibling checkout (ecosystem#51). A path dependency carries no version requirement, so it compiled whatever happened to be checked out next door and dropped the pin from Package.resolved entirely. The OCCT binary is still shared, by the artifact cache that was already doing it.
Platforms narrowed to iOS 18 and macOS 15. The inherited manifests claimed visionOS and tvOS, and OCCT.xcframework ships no slice for either.
Internal
CADViewportService.swift was 2,306 lines, 30% of the package. It is now a 365-line core plus seven per-domain extension files, none over 572, split at seams the file already had. The public API is byte-identical to before the split, compared declaration by declaration.
Why this is an rc rather than 2.0.0
The four readiness items from #13 are done: the deprecated API is gone, the blob file is split, the identity-table documentation is consolidated into a cookbook page that previously did not exist despite being referenced, and the pick-resolver deduplication is now verified by execution rather than by reading the call graph.
What an rc buys is the thing none of that can provide: consumers actually resolving this tag. OCCTMCP and OCCTSwiftScripts build against the published package in CI on every push, so pointing them at this tag is the real test of a release that removes ten public declarations.
363 tests in 33 suites. swift-format lint --strict and swiftlint --strict clean.
Still open, and not in this candidate: no multibody file fixture exists, which is why both camera-framing regressions assert a bounds union rather than a real import. OCCTSwiftAIS/CompatibilityAliases.swift also survives, and whether those three shims from the merge belong in a stable release is a separate call.
v0.2.0
Consolidates identity-table construction, which phase 2 left duplicated three ways (#7, PR #8).
No API break. New stored property with a default and new defaulted parameters only.
What is new
ShapeIdentity, a public type that builds all three identity tables from a shape and an optionalBRepGraph. Previously this lived as three private helpers onCADFileLoader, reachable only through paths that tessellate.CADLoadResult.identity, keyed by body id, built during the load, behindincludeIdentity: Bool = false.
The two are complementary rather than alternatives, which is why both landed. Tables on CADLoadResult cannot serve CADViewportService.load(_:id:transform:), loadShape(_:id:), or OCCTSwiftUX's loadShape, none of which produces a CADLoadResult. A public builder alone cannot remove the shape-to-body pairing hazard.
includeIdentity defaults to false because it is not free: BRepGraph(shape:) measures 5.0ms against 9.6ms for the mesh on a 14-face solid, 3.8ms of that inside toBREPString(). OCCTDesignLoop calls load/loadFromManifest from seven sites that never pick.
Why it mattered
The three copies agreed on every success path and diverged on every failure path. Same enumerations, same uid minting, same ordinal space. What differed was behaviour on a nil graph, on the edge-polyline-only path, and above all on a shapes/bodies count mismatch, where one copy did not detect it, one wiped all identity including correctly-paired bodies, and one nil'd every body's shape.
None of those three answers was right. The mismatch is a symptom, visible only from outside, of a pairing the loader knows exactly: CADFileLoader's STL/IGES robust reload appends a shape even when that input produced no body. Building identity inside the loader keyed by body id removes the pairing rather than guarding it. CADViewportService had implemented that guard three times for the one hazard, including once against dictionaries emptied on the line above.
Also in this release
shapeToBodyAndMetadatano longer builds three identity tables and discards them, so three fewer shape-map walks per body on the path everyloadtakes.- The edge-polyline-only branch now builds an ordinary face table instead of forcing an empty one.
Verification
357 tests in 32 suites, up from 343, with zero deletions. The new pairing test was mutation-checked: deliberately mispairing identity inside the loader fails it. Worth recording that the pick test alongside it did not catch that mutation, since both fixture bodies have six faces and both mint a uid, which is why the pairing test compares bounding boxes rather than indices.
swift-format lint --strict and swiftlint lint --strict clean.
Known issue carried forward
#9: ShapeIdentity builds its face and edge tables with compactMap, so a single failed Shape.fromFace shifts every later ordinal and the table names the wrong sub-shape. This was carried identically by all three original copies, so it is not a regression, and consolidating it into one place is what makes it fixable. It is not fixed here.
For consumers
OCCTSwiftUX's own ShapeIdentity copy becomes fully removable against this release, including the shape-to-body pairing guard added in OCCTSwiftUX#30, which now has nothing left to guard. Note when migrating that OCCTSwiftUX drops the BRepGraph it builds while this type retains it.
v0.1.0
First release of OCCTSwiftInteraction, the merge of OCCTSwiftTools, OCCTSwiftAIS and OCCTSwiftCADKit into one package vending three targets (ecosystem#42).
Migrating is a manifest change and nothing else
Your import lines do not change. The module names are unchanged, because each is still its own SwiftPM target. Replace up to three dependency entries with one and update the package: label on each product:
// before
occtDep("OCCTSwiftTools", from: "1.6.4"),
occtDep("OCCTSwiftAIS", from: "1.3.2"),
occtDep("OCCTSwiftCADKit", from: "1.1.2"),
.product(name: "OCCTSwiftTools", package: "OCCTSwiftTools"),
// after
occtDep("OCCTSwiftInteraction", from: "0.1.0"),
.product(name: "OCCTSwiftTools", package: "OCCTSwiftInteraction"),Full detail, including which of the eight dependent repos uses what: docs/MIGRATION.md.
Depending on this package does not pull SwiftUI into a headless build. SwiftPM compiles only the targets reachable from the products you name, and OCCTSwiftTools imports no UI framework. That property is what made merging these three safe, and it is enforced by target dependencies rather than convention.
What is in it
Everything the three repos carried at their final versions, plus four pieces of work done since:
- OCCTSwift v3.0.0 across all three targets (ecosystem#39). The six bounding-box accessors that became Optional are unwrapped at every call site, guarding into real failure paths rather than defaulting to a zero-size box at the world origin.
- Face identity is settled as OCCT's
IsSamesemantics (#1). A face shared between two shells is one identity,faces()is the enumeration behindFaceIdentityTable, and a caller needing to know which use of a shared face was picked reads orientation off the returned shape. This also fixed five assertions that had been failing since 2026-08-10, when OCCTSwift 2.0.0 arrived transitively and nothing in CI compiled a test target to notice. - One pick resolver (#2).
OCCTSwiftTools.SubShapePickResolveris now the only place a render-path ordinal becomes aSubShapeRef, andSubShapeRef,SubShapeandInteractiveObjectmoved down toOCCTSwiftToolsaccordingly. Clip-plane awareness, geometry enrichment and the whole-body fallback deliberately stayed in their own layers. - One selection store (#3).
CADViewportServiceused to keep a selection alongside theInteractiveContextit already owned; it now drives that context and projects it.
343 tests across 30 suites, all passing, with swift build, swift build --build-tests, swift-format lint --strict and swiftlint lint --strict clean.
Breaking changes for consumers of the old CADKit
Seven items, listed in full in docs/CHANGELOG-OCCTSwiftCADKit.md. The ones most likely to reach you:
SelectionSummaryis renamedSelectionMeasurements, deprecated alias retained. It never collided with OCCTSwiftUX's same-named type, which is a different thing entirely.selectionModesisInteractiveContext.selectionModerather than a copy, initialised to[.face], overriding the context's[.body]default. The two selections are no longer independent.selectionis ordered by (body id, kind, ordinal) rather than by selection order, since the store is aSet.- Assigning
selectionModesnow clears the selection.
PadCAM's usage keeps working unchanged; its one exposure is that mode default.
Why 0.x
1.0.0 is reserved for when the picking consolidation finishes. Two phases remain, both in other repos: OCCTSwiftUX#29 and OCCTMCP#182. This tag exists so those can start.
Known and unverified: the visionOS/tvOS platform claim is inherited from the Tools and AIS manifests and is untested for the CADKit target, which previously declared only iOS and macOS. Confirm or narrow before 1.0.0.
The three old repositories are archived rather than deleted, so their tags stay resolvable and an unmigrated consumer keeps building until it chooses to move.