Integrate slippage classification into segment_mesh_zones + zone_continuity_sweep (#109) - #111
Merged
Merged
Conversation
…inuity_sweep (#109) Repins OCCTSwiftMesh to >=1.6.0 (Mesh.slippage(forTriangles:maxSamples:), Gelfand-Guibas local slippage analysis, OCCTSwiftMesh#26/#31) and wires it into the existing zone tools: - segment_mesh_zones: each zone table entry (and ZoneRecord, optional for zones.json backward compatibility) gains a `slippage` classification (kind/axisPoint/axisDirection/pitch/confidence), reusing the SAME welded mesh + triangle-count guard adjacentZones already established for index correspondence. - zone_continuity_sweep: a zoneId-scoped sweep with no explicit axis defaults to the zone's own slippage axis when kind is cylinder/extrusion/revolution/helix (never plane, whose axis is the surface normal) and confidence >= 0.25; axisSource gains a "slippage" value alongside "explicit"/"pca". Axis selection is factored into a pure, unit-testable ZoneSweepTool.selectSweepAxis(record:explicit:). Revolve-aware angular stationing remains the #109 follow-up remainder. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ssification Review finding on #111: on a connected mesh, vertexNormals at a zone-boundary vertex blend the neighbouring zone's surface in, and the contaminated constraint rows corrupt per-zone slippage (a small extrusion panel read as helix; connected box faces read sphere/revolution/freeform, per this PR's own fixture notes). Zones are now classified from their interior triangles only (boundary = any vertex touched by another zone or an unassigned triangle), with a floor (max(24, 25% of the zone)) below which the full region is kept and the zone is NAMED in a warning rather than reported clean. Fine/coarse connected L-panel regression tests; the disjoint fixtures and the tube are unaffected by construction. 128 tests. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
serverVersion stays 1.24.0; test count reconciles to 133 (128 + #110's 5); CLAUDE.md's OCCTSwiftMesh bullet keeps the 1.6.0 slippage entry with mesh_curvature marked consumed; mesh-analysis.md intro unions both phases' additions. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #109. Integrates OCCTSwiftMesh v1.6.0's
Mesh.slippage(forTriangles:maxSamples:)(Gelfand-Guibas local slippage analysis, OCCTSwiftMesh#26/#31) into the two existing zone tools. This is an integration into existing tools, not a new tool — the server still exposes 71 tools (unchanged).OCCTSwiftMeshto>= 1.6.0(Package.swift/Package.resolved, already staged before this branch was cut).segment_mesh_zones: each zone table entry gains an optionalslippage: {kind, axisPoint?, axisDirection?, pitchPerRadianMm?, confidence}field (kind ∈ {plane, sphere, cylinder, extrusion, revolution, helix, freeform}).ZoneRecordgains the matching optionalslippage: ZoneSlippage?field (defaultnil, so azones.jsonsidecar written before this PR still decodes — no sidecar version bump).list_zonessummaries gainslippageKind: String?.zone_continuity_sweep: a zoneId-scoped sweep with no explicitaxisargument now defaults to the zone's own slippage axis when eligible;axisSourcegains a third value,"slippage", alongside"explicit"/"pca".serverVersion→1.24.0.Correspondence guard (critical detail carried over from
adjacentZones)segmented()'sMeshRegion.triangleIndicesare indexed against the unwelded input mesh, butslippageneeds the welded mesh (its classifier readsvertexNormals(), meaningless on unwelded soup).MeshZoneToolsalready solved exactly this problem foradjacentZones: weld independently, then checkwelded.triangleCount == mesh.triangleCount— if no triangle was dropped, the same index still means the same triangle in both meshes. This PR reuses that exact same welded mesh and guard forslippage, computing both inside the one guarded branch. When the guard fails,slippageis omitted per zone (alongside the pre-existing emptyadjacentZones), with its own warning in the same wording family.Axis-selection rules (
ZoneSweepTool.selectSweepAxis(record:explicit:))Factored into a pure, geometry-free function (unit-tested directly, no mesh/body needed) resolving
axisSourcein priority order:axisSourceaxisargument"explicit"(always wins, unconditionally)ZoneRecord.slippage.kind ∈ {cylinder, extrusion, revolution, helix}, non-nilaxisDirection,confidence >= 0.25"slippage"< 0.25)"pca"(unchanged pre-#109 behavior)Plane is never eligible — its slippage
axisDirectionis the surface normal, and sweeping a panel along its own normal is exactly backwards, not merely unhelpful. Sphere and freeform have no preferred axis at all. Below the confidence floor, the tool falls back to PCA and appends a warning naming the rejected kind and confidence value.axisDirection's sign is arbitrary (inherent to the underlying eigenvector recovery) and its meaning is kind-dependent — documented inZoneSlippage's doc comment and indocs/reference/mesh-analysis.md, pulling wording from upstream'sdocs/algorithms/slippage.mdrather than inventing new claims.confidenceis a spectral-gap diagnostic, not a probability — a near-symmetric body reads as low-confidence rather than confidently wrong.Out of scope, documented as the #109 follow-up remainder: revolve-aware angular stationing (stations about the axis instead of along it) for
revolution-classified zones.A fixture-design finding worth flagging for review
While building the "every zone classifies plane" test, a genuinely closed box (all 6 faces welded together, sharing edges/corners — e.g. the existing
Shape.box()fixture, or a naively hand-authored closed-box STL) turned out to be an unreliable fixture for plane classification:vertexNormals()at a shared edge/corner vertex blends across the adjacent, differently-oriented face, and at some grid resolutions this pushed a genuinely flat face's classification tosphere/revolution/freeformwith high confidence — a consumer-side characteristic ofvertexNormals()-driven classification on a connected mesh, not a bug in OCCTSwiftMesh's algorithm itself (independently verified:Shape.box()'s own faces tessellate to just 2 triangles regardless of deflection, below the algorithm's 6-sample floor, for an unrelated reason). The new plane test uses 6 disjoint (small-gap-inset, non-touching) square panels instead, sidestepping both issues; this is documented at length inSlippageIntegrationTests.swift's file header for future readers. The existing (pre-#109)Shape.box()-based tests are untouched.The mini-carbody fixture (existing, shared by
MeshZoneIntegrationTests) exhibits the same shared-edge phenomenon: its front wall is a genuine translational-symmetry-along-Z extrusion but reads ashelixhere due to boundary contamination from the adjacent top/bottom/cap faces. The "plane never uses its slippage axis" sweep test picks the roof (flat, no ramp) instead of the front wall, and documents why in-line.Test plan
OCCTMCP_FORCE_REMOTE_DEPS=1 swift build— clean.swift build -c release— clean.swift test— 126/126 passing (up from 113 onmain; 13 new top-level@Tests inSwiftTests/OCCTMCPCoreTests/SlippageIntegrationTests.swift, some parameterized across kinds):segment_mesh_zones: slippage classification (#109): a disjoint-panel cube's 6 zones all classifyplanewith axis parallel to the face normal and sensible confidence; an imported multi-ring open-tube STL's barrel zone classifiescylinderwith axis aligned to the tube's own axis.zone_continuity_sweep: slippage axis default (#109): the tube barrel's sweep with noaxisargument resolvesaxisSource == "slippage"with the axis matching the storedZoneRecord.slippage.axisDirection; an explicitaxisstill overrides (axisSource == "explicit"); the mini-carbody roof's sweep resolvesaxisSource == "pca"; a hand-strippedzones.jsonrecord (noslippagekey at all, simulating a pre-Slippage integration: zone kind/axis in segment_mesh_zones + sweep axis defaults (blocked on OCCTSwiftMesh#26) #109 sidecar) decodes cleanly and its sweep falls back to PCA without error.ZoneSweepTool.selectSweepAxis: pure axis-selection logic (#109): 9 unit tests directly on the pure function covering explicit-override, no-record, no-slippage, all 4 eligible kinds, plane/sphere/freeform exclusion, the low-confidence fallback + warning wording, and the exact-floor boundary.CLAUDE.md's test count updated 113 → 126.Deviations from the task brief (with justification)
mainis 71 (perPingTests.swift), which stays 71 — no new tool added either way, just a stale baseline number in the brief.Shape.box()fixture (used by the other, pre-existing zone-tools tests) was not reused for the new plane-classification test, for the reasons above; a purpose-built disjoint-panel-cube STL fixture is used instead.🤖 Generated with Claude Code