Skip to content

fix(cadling): repair ShapeIdentityRegistry + face UV-grid on pythonocc 7.8#13

Merged
LayerDynamics merged 1 commit into
mainfrom
fix/cadling-pythonocc-7.8
Jun 10, 2026
Merged

fix(cadling): repair ShapeIdentityRegistry + face UV-grid on pythonocc 7.8#13
LayerDynamics merged 1 commit into
mainfrom
fix/cadling-pythonocc-7.8

Conversation

@LayerDynamics

@LayerDynamics LayerDynamics commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

What

Two latent bugs made core cadling B-Rep helpers silently inert on
pythonocc 7.8
. Both hid because every existing test either mocked the OCC
path or guarded its assertions with if result is not None:, so the real-OCC
failure was never exercised.

Bugs fixed

  1. ShapeIdentityRegistry registered nothinglib/topology/face_identity.py
    imported from OCC.Core import topods, which raises ImportError on
    pythonocc 7.8 (cannot import name 'topods' from 'OCC.Core'). That set
    HAS_OCC = False at import, so register_all_faces/edges/vertices returned
    empty and every face_index/edge_index was -1. Fixed to
    from OCC.Core.TopoDS import topods.

  2. Every face UV-grid failedlib/geometry/uv_grid_extractor.py built the
    trimming mask with a per-point face.visibility_status(uv) loop; passing
    numpy-float UVs raised a gp_Pnt2d SWIG overload error that the broad
    except swallowed, so FaceUVGridExtractor.extract_uv_grid returned None
    for every face. Replaced with occwl's uvgrid(method="inside") (returns
    the [U,V,1] mask directly); removed the now-dead TopAbs_IN/TopAbs_OUT
    imports.

Tests

Regression tests added to test_face_identity.py and test_uv_grid_extractor.py
using a real in-memory OCC box, with unconditional assertions (no
if result is not None: guard). Both fail before the fix and pass after:

  • registry registers 6 faces / 12 edges / 8 vertices for a box (was 0/0/0)
  • face UV-grid returns shape (10, 10, 7) with a populated trimming mask (was None)

Verified: 129/130 tests pass across tests/unit/lib/{topology,geometry} (the one
failure, test_distribution_analyzer::test_analyzers_produce_consistent_counts,
is a pre-existing, unrelated counting issue). Lint-neutral (no new ruff findings
on either file vs main).

Why it matters

These helpers are reused across cadling's B-Rep processing (coedge extraction,
graph builders, geometry analysis). Restoring them fixes silently-degraded
behavior (everything keyed on face_index was reading -1/0; all face
UV-grids were empty) on the current pythonocc 7.8 toolchain.

🤖 Generated with Claude Code

Summary by Sourcery

Restore correct OCC-backed behavior for B-Rep face identity registration and face UV-grid extraction on pythonocc 7.8.

Bug Fixes:

  • Fix ShapeIdentityRegistry OCC import so real OCC shapes are detected and faces, edges, and vertices are correctly registered on pythonocc 7.8.
  • Replace the per-point visibility-based trimming computation with occwl's inside-uv grid to prevent face UV-grid extraction from failing and returning None on real faces.

Tests:

  • Add regression test ensuring ShapeIdentityRegistry correctly populates indices from a real OCC box shape.
  • Add regression test ensuring face UV-grid extraction succeeds on a real OCC box face and returns a finite grid with a valid trimming mask.

…c 7.8

Two latent bugs made core B-Rep helpers silently inert on pythonocc 7.8;
both hid because every existing test mocked the OCC path or guarded its
assertions with `if result is not None:`.

1. face_identity.py imported `from OCC.Core import topods`, which raises
   ImportError on pythonocc 7.8 ("cannot import name 'topods' from
   'OCC.Core'"). That set HAS_OCC=False at import, so
   ShapeIdentityRegistry.register_all_* registered NOTHING and every
   face/edge index was -1. Fixed to `from OCC.Core.TopoDS import topods`.

2. uv_grid_extractor.py computed the face trimming mask with a per-point
   `face.visibility_status(uv)` loop; numpy-float UVs raised a gp_Pnt2d
   SWIG overload error that the broad except swallowed, so
   FaceUVGridExtractor.extract_uv_grid returned None for EVERY face.
   Replaced with occwl's `uvgrid(method="inside")` (returns the mask
   directly); removed the now-dead TopAbs_IN/TopAbs_OUT imports.

Regression tests (real OCC box, assertions unconditional) added to
test_face_identity.py and test_uv_grid_extractor.py; both fail before the
fix (0 faces registered; None grid). Verified: registry now 6/12/8 on a
box, face UV-grid (10,10,7); 41 topology+geometry tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sourcery-ai

sourcery-ai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Reviewer's Guide

Fixes two latent OCC integration bugs in cadling: the ShapeIdentityRegistry now correctly imports topods from OCC.Core.TopoDS so real OCC shapes are registered, and FaceUVGridExtractor now uses occwl’s uvgrid(method='inside') to build a trimming mask that works on pythonocc 7.8, with new regression tests that exercise real OCC box shapes without guarding on None results.

Sequence diagram for updated FaceUVGridExtractor UV-grid extraction

sequenceDiagram
    participant Caller
    participant FaceUVGridExtractor
    participant occwl_uvgrid as uvgrid
    participant Logger

    Caller->>FaceUVGridExtractor: extract_uv_grid(face, num_u, num_v)
    FaceUVGridExtractor->>occwl_uvgrid: uvgrid(face, num_u, num_v, method=inside)
    occwl_uvgrid-->>FaceUVGridExtractor: inside_grid
    alt inside_grid is None
        FaceUVGridExtractor->>Logger: warning("Failed to extract trimming mask from face")
        FaceUVGridExtractor-->>Caller: None
    else inside_grid is not None
        FaceUVGridExtractor->>FaceUVGridExtractor: inside_grid.astype(np.float32)
        FaceUVGridExtractor-->>Caller: uv_grid[num_u, num_v, 7]
    end
Loading

File-Level Changes

Change Details Files
Restore ShapeIdentityRegistry functionality with correct OCC topods import and add a real-OCC regression test.
  • Change topods import to use OCC.Core.TopoDS.topods so HAS_OCC is correctly set on pythonocc 7.8.
  • Ensure ShapeIdentityRegistry.register_all populates faces, edges, and vertices from real OCC shapes instead of returning empty maps when OCC is available.
  • Add a regression test that builds a real OCC box, runs ShapeIdentityRegistry.register_all, and asserts expected face/edge/vertex counts and index/id/shape round-trips without mocking.
cadling/cadling/lib/topology/face_identity.py
cadling/tests/unit/lib/topology/test_face_identity.py
Fix FaceUVGridExtractor trimming mask computation to work on real OCC faces and add a regression test using a real box face.
  • Remove unused TopAbs_IN/TopAbs_OUT imports now that visibility_status is no longer used.
  • Replace the manual per-point visibility_status(uv) loop with occwl.uvgrid(method='inside') to obtain the inside-mask directly as a [num_u, num_v, 1] grid, handling failure by logging and returning None.
  • Keep the existing normal and UV grid computations and concatenate the new trimming mask to produce the final [num_u, num_v, 7] array.
  • Add an unconditional regression test that builds a real OCC box face, calls FaceUVGridExtractor.extract_uv_grid, and asserts non-None result, expected shape, finite values, and a non-empty {0,1} trimming mask.
cadling/cadling/lib/geometry/uv_grid_extractor.py
cadling/tests/unit/lib/geometry/test_uv_grid_extractor.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've left some high level feedback:

  • In TestFaceUVGridRegression.test_box_face_uv_grid_succeeds, consider strengthening the trimming assertion to assert np.all(trimming == 1) for a planar box face (rather than just trimming.sum() > 0) to better lock in the expected fully-inside behavior.
  • In uv_grid_extractor.extract_uv_grid, you now handle a None inside_grid with a warning and None return; if there are known conditions where uvgrid(..., method="inside") can legitimately return None, it might be worth logging more context (e.g., face type or parameters) to aid debugging when this happens.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In `TestFaceUVGridRegression.test_box_face_uv_grid_succeeds`, consider strengthening the trimming assertion to `assert np.all(trimming == 1)` for a planar box face (rather than just `trimming.sum() > 0`) to better lock in the expected fully-inside behavior.
- In `uv_grid_extractor.extract_uv_grid`, you now handle a `None` `inside_grid` with a warning and `None` return; if there are known conditions where `uvgrid(..., method="inside")` can legitimately return `None`, it might be worth logging more context (e.g., face type or parameters) to aid debugging when this happens.

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@LayerDynamics LayerDynamics merged commit ec7f766 into main Jun 10, 2026
3 checks passed
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