FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Bonsai: reduce CPU cost of the proximity snap path by carlopav · Pull Request #9340 · IfcOpenShell/IfcOpenShell · GitHub

Bonsai: reduce CPU cost of the proximity snap path - #9340

Draft
carlopav wants to merge 3 commits into
IfcOpenShell:v0.8.0from
carlopav:bonsai-cad-intersect-perf
Draft

Bonsai: reduce CPU cost of the proximity snap path#9340
carlopav wants to merge 3 commits into
IfcOpenShell:v0.8.0from
carlopav:bonsai-cad-intersect-perf

Conversation

carlopav commented Aug 20, 2026
edited
Loading

Copy link
Copy Markdown
Contributor

Context

The GPU snap detection that landed in 4cb6ce703..2b429dc60 replaces object discovery. Raycast.ray_cast_by_proximity survives it and is still the only CPU path that turns a detected object into Vertex / Edge Center / Edge snaps: detect_snapping_points calls it for the object under the cursor, and in plan/section views once per visible element carrying cut or fill decorator data.

These three changes reduce the cost of that function and of Cad.intersect_edges_v2, which it calls once per edge. They are independent of how candidates are found, so they apply equally before and after the GPU work.

Changes

One commit each, each independently revertible:

1. Speed up Cad.intersect_edges_v2. Replace np.cross / np.linalg.norm / np.dot with explicit scalar arithmetic. On 3-element operands numpy spends far more time in its dispatch machinery (normalize_axis_tuple, moveaxis) than on the arithmetic itself. Float64 throughout, so results are unchanged. Also used by wall junctions in tool/model.py.

2. Hoist matrix_world out of the proximity loops. It was copied once per vertex and twice per edge, and cannot change while the loops run. Pure code motion: the copy is still taken, and the obj=None path used by the custom_bmesh callers is preserved.

3. Skip the line-line intersection for edges that cannot snap. Every point of an edge lies within half its length of its midpoint, whose distance to the ray the loop already computes for the Edge Center snap. By the triangle inequality no point can be nearer to the ray than midpoint_distance - edge_length / 2; once that lower bound reaches the snap threshold, intersect_edges_v2 cannot produce a hit. The predicate never rejects an edge that could snap, so snapping results are unchanged.

Measurements

Being explicit about what is and isn't measured:

  • 3.8x on intersect_edges_v2 (68.95µs → 18.16µs) is a direct microbenchmark of the function and holds.
  • End-to-end figures I collected earlier (per-event call count 52,113 → 1,869; ~370ms → ~70ms) were measured before the GPU rewrite, when ray_cast_by_proximity ran against every candidate object. They no longer describe the current architecture, so I am deliberately not claiming them here. What remains true regardless is that both changes strictly remove work while preserving behaviour.

Where I would expect them to still matter most is plan and section views: the cut/fill block in detect_snapping_points came through the rewrite unchanged and still iterates every visible mesh element with cache data, rebuilding a bmesh per object per mouse move.

Tests

  • test/tool/test_cad.py — 6 characterization tests for intersect_edges_v2 (skew, crossing, parallel, collinear, 2D input, unnormalised directions), written against the previous implementation before the refactor, so it is verified behaviour-preserving rather than assumed to be.
  • test/tool/test_raycast.py (new) — 5 tests for the early-out, including a randomised search over 3000 edges that samples 21 points along every rejected edge and asserts none lies within the threshold, with a guard so the test cannot pass vacuously.

test/tool/test_cad.py test/tool/test_raycast.py — 23 passed. black --line-length 120 clean.

Related

Supersedes #9004, which fixed the object-origin sort in ray_cast_and_get_closest_to_camera_snaps. The GPU rewrite left that function in the file but nothing calls it any more, so the heuristic it corrected is no longer on a live path. I'll close #9004.

🤖 Generated with Claude Code

Real work comparison

Pre fix

After fix

Copy link
Copy Markdown
Contributor Author

@brunoperdigao the recent work on GPU snap candidate selection is great, but there are still some perfomance issues in section view. I opened a PR instead of an issue to offer some possible performance improvements, in the hope it could be helpful. The result is not yet fluid, but at least workable for some specific usecase.
I also noticed I'm not able to snap 2d representation of objects when placed over a solid.
Happy to share privately the model i'm working on at the moment if it could help.

carlopav and others added 3 commits August 20, 2026 22:51
intersect_edges_v2 runs thousands of times per mouse move under the
snapping tools and is also used for wall junctions in tool/model.py. On
3-element operands, numpy spends far more time in its dispatch machinery
(normalize_axis_tuple, moveaxis) than on the arithmetic itself, so
np.cross / np.linalg.norm / np.dot dominate the profile.

Replace them with explicit scalar forms in this function only. They keep
float64 precision, so results are unchanged: 3.8x faster on the function
(68.95us -> 18.16us measured).

Characterization tests were written against the previous implementation
first and pin the closest-points results for skew, crossing, parallel,
collinear, 2D and unnormalised-direction inputs, so the refactor is
verified to be behaviour-preserving.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ray_cast_by_proximity copied obj.matrix_world once per vertex and twice
per edge. The matrix cannot change while the loops run, so copy it once
before them instead.

Pure code motion: the copy is still taken (the loops must not alias the
object's live matrix) and the None case is preserved for the custom_bmesh
callers that pass obj=None.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The per-edge loop in ray_cast_by_proximity calls Cad.intersect_edges_v2
for every edge of the object under the cursor, and that call dominates
the loop. Most edges are nowhere near the cursor and cannot pass its
threshold test.

Add a conservative bound: every point of an edge lies within half the
edge length of its midpoint, whose distance to the ray the loop already
computes for the Edge Center snap, so by the triangle inequality no point
can be nearer than midpoint_distance - edge_length / 2. When that lower
bound reaches the snap threshold the intersection cannot produce a hit.

The predicate never rejects an edge that could snap, so snapping results
are unchanged. Tested with a randomised search that samples points along
every rejected edge and asserts none lies within the threshold.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
carlopav force-pushed the bonsai-cad-intersect-perf branch from 07c8449 to 219c4d1 Compare August 20, 2026 20:52

Copy link
Copy Markdown
Contributor

@carlopav Thanks for pointing that out. I'll take a look

Copy link
Copy Markdown
Contributor

@carlopav I went through another path. I realized I forgot to integrate the cut geometry snaps to the new GPU system, and it seems to solve the issue. Could you please test and see how it goes. I've committed to the v0.8.0 branch

Copy link
Copy Markdown
Contributor Author

@brunoperdigao I cannot find your new commits anywhere, are you sure you pushed them?

Copy link
Copy Markdown
Contributor

@brunoperdigao I cannot find your new commits anywhere, are you sure you pushed them?

Sorry, don't know what happened. Here it is now: https://github.com/IfcOpenShell/IfcOpenShell/commits/v0.8.0/

Copy link
Copy Markdown
Contributor Author

@brunoperdigao now that's fantastic! snap in section works like a charm on sectioned elements. Still not able to snap 2d geometry (a bed with a Curve2d representation over a floor) but that's another topic. I'd close the PR if you agree: the only possible commit is the first (71cfa36), but it doesn't probably makes much sense if the snap already works good...

Copy link
Copy Markdown
Contributor

@carlopav Let me take a closer look at the commits later before you close. Since I was working with another approach I didn't actually take the time to see what parts can be implemented as well.
About the 2d geometry, it should work, but maybe I missed something. Could you provide me with a sample file where it doesn't work?

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants


Back | FazBrowse Home | New Git URL