Releases: SecondMouseAU/OCCTMCP
v1.9.0 — surface deviation + invalid-shape loading (inspection/measurement)
Measurement & inspection improvements, with Node↔Swift parity restored via OCCTSwiftScripts v1.4.0.
New
measure_deviation— directed + symmetric surface deviation (one-sided / symmetric Hausdorff) between two bodies. The metric for certifying a reconstruction against its source mesh;measure_distanceis minimum-only. Reports{max, rms, mean, worstPoint}per direction +symmetricHausdorff. (#41, #46)compute_metrics→boundingBoxOptimal— opt-in tight extent (BRepBndLib::AddOptimal); the defaultBnd_Boxover-reports curved B-spline geometry. (#44, #45)read_brep/import_file→allowInvalid— load a topologically invalid / loose-face shape (an in-progress reconstruction) into the scene so the analysis tools can measure it. (#41 Gap 2, #48)
Fixed
execute_scriptpinned a pre-1.0 OCCTSwiftScripts → stale OCCTSwift; now tracks the server cohort. (#42, #43)
Node parity
measure_deviation,compute_metricsboundingBoxOptimal, andread_brep/import_fileallowInvalidare now in both servers (Node wraps the new occtkit verbs). Node tool count 36 → 37. (#47, #48)
Cohort
OCCTSwift 1.8.0, OCCTSwiftScripts 1.4.0.
v1.8.0 — graph_select + graph_ml convexity
Adds the B-rep graph selection primitives (OCCTMCP#38).
- graph_select — local adjacency/selection on a BREP path: face-neighbors (+ convexity + shared-edge count via the kernel AAG), edge-faces, vertex-edges, face-adjacency (full gAAG), edges-class (boundary|non-manifold|seam|degenerate). The selection/pointer primitive without dumping the whole graph.
- graph_ml — now augments its output with a convexity-attributed faceAdjacency block (kernel-direct AAG), completing the graph_ml half of #38.
Bumps the OCCTSwiftScripts pin to v1.2.0. Builds against OCCTSwift 1.7.1 (OCCT 8.0.0p1). (#39, closes #38)
v1.7.1 — re-pin OCCT 8.0.0p1 cohort
Re-pin to the OCCT 8.0.0p1 cohort: OCCTSwift 1.7.1, OCCTSwiftMesh 1.1.1, OCCTSwiftTools 1.1.2, OCCTSwiftAIS 1.0.3, OCCTSwiftScripts 1.0.5, OCCTSwiftViewport 1.1.20. No API changes; 36 tests pass.
v1.7.0 — General-arrangement assembly drawings
General-arrangement / assembly drawings
Bumps OCCTSwiftScripts 1.0.3 → 1.0.4 (DrawingComposer GA overloads, OCCTSwiftScripts#50) and surfaces them through generate_drawing.
generate_drawingacceptsbodyIds(2+) → a general-arrangement assembly sheet: shared views, a parts list, and a numbered balloon per body (viaComposer.render(spec:components:)). A singlebodyIdstill gives a standard part drawing (sections / dimensions honoured).bodyIdis now optional;bodyIdstakes precedence.DrawingReportgainscomponentCount+partCount.- New integration test (two-body GA sheet). 36 tests green.
⚠️ Cohort dependency bump
OCCTSwiftScripts 1.0.4 raised its OCCTSwift floor to 1.3.1, so this forces OCCTSwift 1.2.0 → 1.4.0 (OCCT.xcframework 1.3.2) across the cohort. Verified with a clean build — no OCCTMCP API breakage. Declared OCCTSwift floor bumped to 1.3.1.
🤖 Generated with Claude Code
v1.6.0 — pick_surface_point
New: pick_surface_point
Repins OCCTSwiftViewport 1.0.4 → 1.1.20 (tap-to-measure, SecondMouseAU/OCCTSwiftViewport#68) and adds an MCP tool that surfaces the viewport's ray→surface-point pick math headlessly.
Cast a ray through pixel (screenX, screenY) of a render_preview-framed view (pass the same options you render with) and get back the nearest world-space surface point on a body: hit, bodyId, point [x,y,z], distance, and a selectionId.
The selectionId is a valid add_dimension anchor, so you can pick two arbitrary surface points and dimension between them — not just topology centroids. Implemented with Ray.fromCamera + SceneRaycast.cast.
Details
- New
RayPickTool;SelectionRegistry.recordPointSnapshotfor free-point anchors. - Dispatch uses
numberValueso integer pixels survive the JSON int/double round-trip. - New integration test (pick ×2 → add_dimension). 35 tests pass.
- README: 57 tools.
🤖 Generated with Claude Code
v1.5.0 — reconstruct_* tool group (consume OCCTSwift v1.2.0)
OCCTMCP v1.5.0 — reconstruct_* tool group (consume OCCTSwift v1.2.0)
MINOR — additive new tool surface. No breaking changes.
OCCTSwift v1.2.0 closes #168 with a TopologyGraph per-node attribute store + Codable GraphSnapshot round-trip. This release consumes that to ship the reconstruct_* group (#33) — LLM read/write over an attributed reconstruction graph, the missing piece for driving the OCCTReconstruct mesh-to-solid pipeline from a model.
New tools (6 → 56 total)
| Tool | Direction | Purpose |
|---|---|---|
reconstruct_get_graph |
read | Topology counts + annotated nodes (with reconstruct.* attrs) + instance clusters |
reconstruct_set_decision |
write | decidedBy (geometric/ml/human) + accept/reject |
reconstruct_force_fit |
write | Record a forced surface-type override |
reconstruct_confirm_instances |
write | Confirm/reject a congruence cluster |
reconstruct_export_session |
read | Byte-stable GraphSnapshot to disk |
reconstruct_import_session |
write | Reload a snapshot into a session |
Design
ReconstructRegistry— actor-backedsessionId → TopologyGraph, mirroringSelectionRegistry/HistoryRegistry. All graph access is actor-isolated.- Nodes addressed by the self-describing
<kind>:<index>string (e.g.face:3), parseable both ways. reconstruct.*namespaced attribute keys; engine-written keys (residual/confidence/…) round-trip throughget_graph/export_sessionuntouched.- Scope boundary: the reconstruction engine (surface fitting, congruence detection) lives in OCCTReconstruct.
reconstruct_force_fitrecords the override for the engine to honour — it does not re-fit here. This resolves the issue's "override and re-fit" wording toward its own out-of-scope note. - Swift-only, consistent with selection / remap / annotations / history (Node remains the portable 36-tool subset).
Dependency
OCCTSwiftpin1.1.0 → 1.2.0. No other cohort members moved.
Tests
34 swift-testing cases (was 28). New ReconstructToolsTests.swift covers node addressing, each write path, the export→import round-trip, and byte-stability of the canonical snapshot. swift build + swift test green.
v1.4.2 — floor + lock the crash-fixed Viewport cohort
OCCTMCP was locked to OCCTSwiftViewport 1.0.2 — the version with the uncatchable NormalSmoothing.quantize() crash on body load (Viewport #30). render-preview rasterizes through Viewport, so the server could trap on large or ill-bounded models.
Floors + lock raised to the crash-fixed cohort: Viewport 1.0.2 → 1.0.4, Tools 1.1.0 → 1.1.1, AIS 1.0.1 → 1.0.2, Scripts 1.0.2 → 1.0.3. Build clean; all 28 tests pass.
🤖 Generated with Claude Code
v1.4.1 — drop swift-sdk fork branch (official tagged SDK)
Dependency hygiene — drop the swift-sdk fork branch
OCCTMCP depended on the gsdali/swift-sdk fork branch add-value-numbervalue — a moving, non-reproducible reference. This release switches to the official modelcontextprotocol/swift-sdk at a tagged version (from: "0.11.0" → resolves to 0.12.1).
The fork's only delta from upstream was a single Value.numberValue accessor (proposed upstream in modelcontextprotocol/swift-sdk#225, PR #226, still open). It is now back-ported verbatim in Sources/OCCTMCPCore/Value+NumberValue.swift and will be removed once the SDK ships the property.
Impact
No behavioral change — numberValue is identical. The dependency graph is now reproducible (version-locked at 0.12.1 instead of a branch that could move or disappear). Build clean; all 28 tests pass.
🤖 Generated with Claude Code
v1.4.0 — consume OCCTSwift v1.1.0 + drop identity-flag workaround
OCCTMCP v1.4.0 — consume OCCTSwift v1.1.0 + drop identity-flag workaround
OCCTSwift v1.1.0 closes #167 with TopologyGraph.findDerivedOrSelf(of:) and hasHistoryRecord(for:) — unambiguous answers to "where did this node end up?":
- non-empty derivatives → modified
[]→ explicitly deleted[self]→ no record at all (untouched)
This release consumes those and removes the v1.3 workaround.
What changed
HistoryRegistry'sEntry { graph, isIdentityPreserving }reverts to a plain graph store.recordIdentityHistory*keep their topology-count guards but no longer write self-records or set a flag.RemapTools.remapViaHistoryswitches fromfindDerived+ flag plumbing tofindDerivedOrSelf. Empty result is now a definitivefate=lost. Cleaner control flow.recordKindinsiderecordBoolean/recordSingleInputHistoryskips writing identity records (input mapped to its own index in the post graph). Without this skip, OCCT-reported "modified to same index" cases would get conflated with explicit deletes byfindDerivedOrSelf— the fillet integration test exposed this on the cutover.
History opt-in matrix as of v1.4
| Tool | Path | Notes |
|---|---|---|
transform_body |
implicit identity (no records written) | every node maps 1:1; findDerivedOrSelf returns [self] |
heal_shape |
implicit identity if guards pass | falls back to heuristic if shape repair changed topology |
boolean_op |
per-input via *WithFullHistory |
identity-mapping records skipped at write time |
apply_feature |
per-feature via BuildResult.histories[id] |
every spec kind covered (OCCTSwift v1.0.4) |
mirror_or_pattern |
use find_correspondences instead |
different contract |
Tests
25 swift-testing cases — same surface as v1.3.0; no new tests, no removed tests. All four history-asserting integration tests still pass; the fillet one passes only after the recordKind identity-skip fix landed, which proves we're now exercising the findDerivedOrSelf semantics correctly.
v1.3.0 — find_correspondences (closes #24) + history-fix
OCCTMCP v1.3.0 — find_correspondences (closes #24) + history-fix
Adds the cross-body selection-mapping tool that closes the last gap in the remap matrix, and fixes two latent bugs that turned out to hide each other.
find_correspondences — new tool (50 total)
Maps selectionIds from a source body onto a target body that's a known transform of the source — typically a mirror_or_pattern output. Different contract from remap_selection: not "X mutated into Y on the same body" but "X on body A corresponds to Y on body B under transform T".
find_correspondences({
sourceSelectionIds: ["sel:src#face[3]"],
targetBodyId: "mirror-src",
transform: {
kind: "mirror",
planeOrigin: [0, 0, 0],
planeNormal: [1, 0, 0]
}
}) → { correspondences: [{ sourceSelectionId, targetSelectionId, fate, confidenceMm }] }
v1 supports translate / mirror / rotate hints. Compound transforms, bbox-alignment inference, and reading mirror_or_pattern manifest metadata as transform defaults are tracked as follow-ups in #24.
Fixed: identity history was a placebo
OCCT's TopologyGraph.findDerived returns empty for identity records (original == replacement). The recordIdentityHistory loop in HistoryRegistry was writing exactly such no-op records — so findDerived always returned empty and remap_selection fell back to the centroid heuristic. Every prior test asserting fate=preserved + confidenceMm=0 was passing because the centroid heuristic also returned 0 (non-overlapping unions, holes on opposite-side faces, etc.).
HistoryRegistry now stores an Entry { graph, isIdentityPreserving }. Identity-preserving recorders set the flag and skip the self-record loop. RemapTools treats findDerived empty + flag set as preserved-at-same-index. transform_body's history path is real for the first time.
Fixed: JSON ints failing in tool dispatch
When a client encodes Value.double(0), the SDK's JSONEncoder writes 0 (no decimal). The decoder picks .int(0). Tool dispatches reading coordinate arrays via arr[N].doubleValue got nil. LLMs sending [0, 0, 1] for unit vectors hit this constantly.
New Value.asDouble extension accepts either .int or .double; 11 array sites + 3 scalar sites converted across transform_body / mirror_or_pattern / select_topology / find_correspondences etc.
Updated history opt-in matrix
| Tool | Path | Notes |
|---|---|---|
transform_body |
identity flag | every node maps 1:1; flag short-circuits findDerived empty to preserved |
heal_shape |
identity flag if guards pass | falls back to heuristic if topology counts changed |
boolean_op |
per-input via *WithFullHistory |
recorded under output + both inputs |
apply_feature |
per-feature via BuildResult.histories[id] |
every spec kind covered (OCCTSwift v1.0.4) |
mirror_or_pattern |
use find_correspondences instead |
different contract — pattern instances aren't OCCT-derivatives |
Tests
25 swift-testing cases (was 24). New findCorrespondencesAcrossMirror builds a 20³ box at +X, mirrors about YZ, picks a face on the source, calls find_correspondences, asserts fate=matched + targetSelectionId.hasPrefix("sel:mirror-src#face[") + confidenceMm < 0.01. The existing historyRemapPreservesAcrossTransform now actually exercises the history path (was a placebo before this release).