Skip to content

Add detect_mesh_features: crease-ring feature outlines (#108) - #113

Merged
gsdali merged 3 commits into
mainfrom
feat/108-detect-mesh-features
Jul 23, 2026
Merged

Add detect_mesh_features: crease-ring feature outlines (#108)#113
gsdali merged 3 commits into
mainfrom
feat/108-detect-mesh-features

Conversation

@gsdali

@gsdali gsdali commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds detect_mesh_features: crease-ring feature outlines (doors, panels, window returns, recesses) on raw scan meshes, wrapping OCCTSwiftMesh.Mesh.creaseEdges(minAngleDegrees:) (OCCTSwiftMesh#28, v1.7.0) — the mesh-domain complement to recognize_features, which needs BREP/AAG topology a scanned/STL body doesn't have.
  • Pipeline: load -> mesh -> weld (mandatory precondition, mirroring MeshCurvatureTools) -> creaseEdges. Detection, stats, and render all live on the same welded mesh, so there's no triangle/vertex-index correspondence problem to guard.
  • Rings and open paths (a crease running off an open boundary) share one rings array, largest-first; junction-aware chaining (upstream) splits Y/T intersections cleanly; unchained leftovers are counted, never dropped.
  • Zone interplay: when segment_mesh_zones has already run for the body (matching MeshSignature, welded triangle-count-survival guard), each ring reports containingZones — majority-first zone ids incident to the ring's vertices. Omitted with a warning when the zone table is stale or the guard fails; omitted silently when no zones exist at all.
  • Optional render: the body surface as a neutral translucent grey mesh, plus one edges-only ViewportBody per ring (no mesh triangles, so OffscreenRenderer draws its wireframe unconditionally) in a categorical color, composited with a legend.
  • Repins OCCTSwiftMesh 1.6.0 -> 1.7.0 (verified Package.resolved pins 1.7.0 under OCCTMCP_FORCE_REMOTE_DEPS=1).
  • Registers as tool docs: bring reference site current (#62/#63/#66/#69/#70) + harden scriptsPin #73 (Server.swift schema + dispatch, dispatch-level minAngleDegrees guard per the Add align_bodies: GOM-style point-to-plane ICP registration (#104) #106 convention); serverVersion -> 1.26.0.
  • Docs: new detect_mesh_features section in docs/reference/mesh-analysis.md (args, welded-internally note, junction semantics, zone interplay, the recognize_features distinction), Phase 3 backlog updated to mark detect_mesh_features: crease-ring feature outlines (blocked on OCCTSwiftMesh#28) #108/OCCTSwiftMesh#28 shipped, README tool table + tool-count references (72 -> 73) across README/CLAUDE.md/docs/guides/getting-started.md/docs/reference/README.md.

Coordination note

A sibling PR (fit_primitives, #107, also OCCTSwiftMesh v1.7.0) is landing in parallel. Per the pre-assigned coordination plan, this branch is standalone-testable against current main at 73 tools; once fit_primitives lands first, a follow-up rebase on this PR (or a fresh PR) will bump the tool count to 74. The Package.swift repin comment's leading clause was written to be textually identical between both branches so that rebase conflict is trivial to resolve.

Test plan

  • OCCTMCP_FORCE_REMOTE_DEPS=1 swift package resolve — confirms Package.resolved pins occtswiftmesh at 1.7.0
  • OCCTMCP_FORCE_REMOTE_DEPS=1 swift build — green
  • swift build -c release — green
  • swift test141/141 passing (133 baseline + 8 new MeshFeatureToolsTests cases: closed-ring detection on a round two-tier-cylinder fixture with ~90° fold angles / largest-first / stable ids, zone-interplay containingZones correctness, a genuinely flat zero-crease body, an unweldable far-apart-triangle soup, the maxRings truncation warning, call-to-call determinism (byte-identical JSON), non-trivial PNG render output, and a dispatch-level invalid-minAngleDegrees guard)
  • PingTests.toolCount updated 72 -> 73; IntegrationTests' expected-tool-names list includes detect_mesh_features

Fixture design note

The originally-planned square "mesa" (raised platform) fixture turned out to be a poor choice for exercising closed rings: a rectangular mesa's own vertical corners are themselves additional 90° creases (adjacent walls meeting at 90°), which turns every corner into a degree-3 junction and fragments what should be one clean ring into several short open paths. Switched to a round two-tier-cylinder fixture (no corners), matching OCCTSwiftMesh's own docs/algorithms/crease-detection.md test-fixture guidance (coarseCappedCylinderMesh). Documented in the fixture's own doc comment and in CLAUDE.md's MeshFeatureTools.swift bullet.

🤖 Generated with Claude Code

Wraps OCCTSwiftMesh v1.7.0's Mesh.creaseEdges(minAngleDegrees:) to find
dihedral-fold-edge feature outlines (doors, panels, window returns,
recesses) on raw scan meshes where recognize_features (BREP/AAG) has no
face/edge structure to work against. Welds the mesh (mandatory precondition,
mirroring MeshCurvatureTools), chains fold edges into closed rings and open
paths largest-first, and reports each ring's containingZones when
segment_mesh_zones has already run for the body. Optional render: the
surface as a neutral translucent mesh plus one categorically-colored
edges-only wireframe ViewportBody per ring, composited with a legend.

Repins OCCTSwiftMesh 1.6.0 -> 1.7.0. Tool count 72 -> 73; test count
133 -> 141 (8 new cases: closed-ring detection, zone interplay, a flat
zero-crease body, an unweldable soup, the maxRings cap, determinism,
render output, and a dispatch-level argument guard).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@gsdali

gsdali commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator Author

Review: approved

Verified against the brief and the v1.7.0 upstream surface; no blocking findings. 141/141 green.

  • The fixture pivot was the right call, for the right reason: a square mesa's vertical corners are themselves 90-degree creases, so every corner is a degree-3 junction and the upstream chaining (correctly) fragments the "ring" into open paths — the same junction behavior the upstream welded-box test pins. The round two-tier cylinder isolates closed-ring behavior cleanly, and the test asserts the full contract: 4 closed rings, ~90-degree fold angles, perimeter lengths matched against analytic values, largest-first order, stable ids. The plain-box test then pins the junction case ON PURPOSE (12 open paths) instead of avoiding it.
  • The edges-only render is verified, not assumed: the file header cites OffscreenRenderer's actual wireframe condition (edges draw unconditionally when a body has no mesh triangles), the vertices array is populated so ring bodies participate in camera framing, and an integration test requires the PNG to exist with non-trivial size.
  • Zone interplay guard chain is correct and honestly ordered: signature staleness first, then the weld-count correspondence guard, then the vertex-to-zones map; majority-first containingZones with a deterministic tie-break; nil-silent when no zones exist (optional context), warned when zones exist but can't be trusted.
  • Coordination discipline held: 73 tools standalone-testable, serverVersion 1.26.0, identical repin clause, rebase note in the PR body.

One cosmetic nit, foldable into the landing-train rebase: the neutral surface body takes its normals from welded.normals, which is empty (welding rebuilds the Mesh without normals), so the fallback constant (0,0,1) normals give the backdrop flat lighting — welded.vertexNormals() would shade it properly for one extra line. The ring overlays (the actual point of the render) are unaffected.

Lands second in the train: rebase over fit_primitives (#112), counts 73 -> 74, then v1.26.0.

gsdali and others added 2 commits July 23, 2026 11:25
… to 74; fold in both review nits

Union-resolves the parallel-development conflicts (registrations,
dispatch, docs sections, count lines), reconciles tool count to 74 and
test count to 146, keeps serverVersion 1.26.0, lists both new tools in
the reference index row. Review nits: fit_primitives' empty-report
message no longer blames minSupportTriangles when maxPrimitives emptied
the list; detect_mesh_features' render backdrop shades with computed
vertex normals instead of a constant fallback (welded meshes carry no
normals).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… merge

The conflict blocks split both tools' Tool() registration and dispatch
case mid-expression; rebuilt as two complete registrations and two
complete cases. 74 tools, 146 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant