fix(compose): detect central bore on plane-only disc flanges (refs Mark/pc2cad#76) #82

Merged
Mark merged 1 commit from kilo-gsd-76-fix into main 2026-10-05 16:33:17 +00:00
Owner

Summary

The landed fix in becf5af addressed the central bore on a thin plate flange, but c6f901f (#81) then added a coarse has_curved_primitive gate that disabled the body-aware feature sweep on plane-only bodies. This re-disabled bore detection on methodology_flange.ply (a disc with only two plane primitives in its compose body), leaving the reconstruction as a solid disc with no central bore and no bolt-circle. Coverage 0.875 (passes the gate) but the geometric match is wrong.

Three coupled fixes

  1. The c6f901f gate is removed. The compose-engine path now always runs _find_feature_cylinders. The assembly-regression case (box-edge noise admitting 60+ inliers per "cylinder" peak) is handled by the new angular-uniformity gate in _accept_candidate instead of a coarse body-level gate.

  2. _find_feature_cylinders gains a multi_features_per_axis flag. The compose-engine path passes True: it accepts MULTIPLE passing candidates per axis (e.g. central bore + bolt-circle annulus on the same disc Z-axis). The legacy path keeps the default False: single-feature-per-axis, which preserves the test_plate_hole_detected_and_exported regression (strategy_plate_with_hole.ply has a real 10mm hole at r=9.72mm and a phantom 1mm-chamfered-edge ring at r=18.98mm; only the real hole should be exported).

  3. _accept_candidate gains the angular-uniformity gate. Real cylindrical features have inliers spread around the full 360° circumference (8-sector std/mean ≈ 0.18 on flange bolt-circle, ≈ 0.15 on flange bore, ≈ 0.15 on plate hole). Spurious "cylinders" from box edges have inliers concentrated at the box corners (8-sector std/mean ≈ 1.5 on assembly r=10.4mm, ≈ 1.1 on r=12.7mm). The 0.25 threshold rejects the box-edge noise while keeping real features.

Acceptance

samples/methodology_flange.ply reconstruction now contains the bolt-hole pattern AND the central bore. render_compare reports coverage 0.875 (≥0.85) and the STEP contains 2 hole features.

Verified

  • methodology_flange: features=0 → features=2 (central bore r≈3.7mm at 273 inliers, bolt-circle r≈9.7mm at 313 inliers). Coverage 0.890 → 0.875 (still passes the gate).
  • methodology_assembly: features=0 (regression test passes).
  • methodology_cone: features=1 (unchanged from #81).
  • methodology_shaft: features=0 (unchanged from #81).
  • strategy_l_bracket: features=0 (unchanged).
  • strategy_plate_with_hole: 1 hole detected (regression test passes; the multi-feature-per-axis flag is False on the legacy path).
  • 43/43 test_compose tests pass; 4/4 test_reconstruction non-render tests pass; 28/28 test_holes tests pass; 8/8 test_methodology tests pass.

Refs Mark/pc2cad#76.

## Summary The landed fix in becf5af addressed the central bore on a thin plate flange, but c6f901f (#81) then added a coarse `has_curved_primitive` gate that disabled the body-aware feature sweep on plane-only bodies. This re-disabled bore detection on `methodology_flange.ply` (a disc with only two plane primitives in its compose body), leaving the reconstruction as a solid disc with no central bore and no bolt-circle. Coverage 0.875 (passes the gate) but the geometric match is wrong. ## Three coupled fixes 1. **The c6f901f gate is removed.** The compose-engine path now always runs `_find_feature_cylinders`. The assembly-regression case (box-edge noise admitting 60+ inliers per "cylinder" peak) is handled by the new angular-uniformity gate in `_accept_candidate` instead of a coarse body-level gate. 2. **`_find_feature_cylinders` gains a `multi_features_per_axis` flag.** The compose-engine path passes `True`: it accepts MULTIPLE passing candidates per axis (e.g. central bore + bolt-circle annulus on the same disc Z-axis). The legacy path keeps the default `False`: single-feature-per-axis, which preserves the `test_plate_hole_detected_and_exported` regression (`strategy_plate_with_hole.ply` has a real 10mm hole at r=9.72mm and a phantom 1mm-chamfered-edge ring at r=18.98mm; only the real hole should be exported). 3. **`_accept_candidate` gains the angular-uniformity gate.** Real cylindrical features have inliers spread around the full 360° circumference (8-sector std/mean ≈ 0.18 on flange bolt-circle, ≈ 0.15 on flange bore, ≈ 0.15 on plate hole). Spurious "cylinders" from box edges have inliers concentrated at the box corners (8-sector std/mean ≈ 1.5 on assembly r=10.4mm, ≈ 1.1 on r=12.7mm). The 0.25 threshold rejects the box-edge noise while keeping real features. ## Acceptance `samples/methodology_flange.ply` reconstruction now contains the bolt-hole pattern AND the central bore. `render_compare` reports coverage 0.875 (≥0.85) and the STEP contains 2 hole features. ## Verified - methodology_flange: features=0 → features=2 (central bore r≈3.7mm at 273 inliers, bolt-circle r≈9.7mm at 313 inliers). Coverage 0.890 → 0.875 (still passes the gate). - methodology_assembly: features=0 (regression test passes). - methodology_cone: features=1 (unchanged from #81). - methodology_shaft: features=0 (unchanged from #81). - strategy_l_bracket: features=0 (unchanged). - strategy_plate_with_hole: 1 hole detected (regression test passes; the multi-feature-per-axis flag is False on the legacy path). - 43/43 test_compose tests pass; 4/4 test_reconstruction non-render tests pass; 28/28 test_holes tests pass; 8/8 test_methodology tests pass. Refs Mark/pc2cad#76.
The landed fix in becf5af addressed the central bore on a
thin plate flange, but c6f901f (#81) then added a coarse
`has_curved_primitive` gate that disabled the body-aware
feature sweep on plane-only bodies. This re-disabled bore
detection on `methodology_flange.ply` (a disc with only
two plane primitives in its compose body), leaving the
reconstruction as a solid disc with no central bore and no
bolt-circle. Coverage 0.875 (passes the gate) but the
geometric match is wrong.

Three coupled fixes:

1. The c6f901f gate is removed. The compose-engine path now
   always runs `_find_feature_cylinders`. The
   assembly-regression case (box-edge noise admitting 60+
   inliers per 'cylinder' peak) is handled by the new
   angular-uniformity gate in `_accept_candidate` instead
   of a coarse body-level gate.

2. `_find_feature_cylinders` gains a
   `multi_features_per_axis` flag. The compose-engine path
   passes `True`: it accepts MULTIPLE passing candidates per
   axis (e.g. central bore + bolt-circle annulus on the same
   disc Z-axis). The legacy path keeps the default
   `False`: single-feature-per-axis, which preserves the
   `test_plate_hole_detected_and_exported` regression
   (`strategy_plate_with_hole.ply` has a real 10mm hole at
   r=9.72mm and a phantom 1mm-chamfered-edge ring at
   r=18.98mm; only the real hole should be exported).

3. `_accept_candidate` gains the angular-uniformity gate.
   Real cylindrical features have inliers spread around the
   full 360° circumference (8-sector std/mean ≈ 0.18 on
   flange bolt-circle, ≈ 0.15 on flange bore, ≈ 0.15 on
   plate hole). Spurious 'cylinders' from box edges have
   inliers concentrated at the box's corners (8-sector
   std/mean ≈ 1.5 on assembly r=10.4mm, ≈ 1.1 on r=12.7mm).
   The 0.25 threshold rejects the box-edge noise while
   keeping real features.

Verified:
- methodology_flange: features=0 → features=2 (central bore
  r≈3.7mm at 273 inliers, bolt-circle r≈9.7mm at 313
  inliers). Coverage 0.890 → 0.875 (still passes the gate).
- methodology_assembly: features=0 (regression test passes).
- methodology_cone: features=1 (unchanged from #81).
- methodology_shaft: features=0 (unchanged from #81).
- strategy_l_bracket: features=0 (unchanged).
- strategy_plate_with_hole: 1 hole detected (regression
  test passes; the multi-feature-per-axis flag is False
  on the legacy path).
- 43/43 test_compose tests pass; 4/4 test_reconstruction
  non-render tests pass; 28/28 test_holes tests pass;
  8/8 test_methodology tests pass.

Refs Mark/pc2cad#76.
Mark merged commit 4221ee2b97 into main 2026-10-05 16:33:17 +00:00
Sign in to join this conversation.
No description provided.