Skip to content

fix: arc-sense face rebuilds and id-keyed viewer selection#218

Merged
Krande merged 2 commits into
mainfrom
fix/cone-fidelity-and-id-selection
Jun 11, 2026
Merged

fix: arc-sense face rebuilds and id-keyed viewer selection#218
Krande merged 2 commits into
mainfrom
fix/cone-fidelity-and-id-selection

Conversation

@Krande

@Krande Krande commented Jun 10, 2026

Copy link
Copy Markdown
Owner

Problem

Two residuals after #217, both confirmed on a large converted assembly model:

  1. Phantom cones/cylinders obfuscating the model. The param-extent face repair over-covered: circular arcs were sampled (and OCC edges built) in the circle's positive parametric direction from the oriented edge's endpoints, so reversed-traversal arcs became their complements — a small wedge's parameter extent snapped to the full period and rebuilt as a full revolution band. Hole bounds were also silently filled.
  2. Tree selection mis-resolved on duplicate names. Real models carry thousands of nodes sharing one display name, and four frontend lookups keyed by name returned the first match.

Fixes

Geometry (ada/occ/geom)

  • The occupied arc is determined by the EDGE_CURVE alone (its own endpoints, positive direction iff same_sense; orientation only reverses traversal and is reader-convention-dependent). Both _sample_edge_points and make_edge_from_edge now consult it — the latter was the root cause of many failing wires (reversed arcs built as 300° complements).
  • _make_face_from_param_extent re-adds non-closure hole bounds (tried in both windings, pcurves restored via ShapeFix_Face — safe on bounded faces — accepted only when area shrinks), and a last-resort area gate drops any rebuild exceeding 4× the squared bbox diagonal of its own boundary samples.
  • Face-repair counters ride back from tessellation workers into the conversion stats ("rebuilds") and summary log.

Viewer selection (frontend)

  • New O(1) reverse index (model_key, rangeId) → tree node in treeViewStore, rebuilt on tree set/clear; both click handlers rekeyed to it (also removes a worker round-trip + full-tree scan per selected range).
  • crossModelSelect resolves member names against the target model's hierarchy in one batched worker call (all matches, off the main thread) instead of scanning the active model's userdata first-match-wins.
  • Deleted findNodeById and getDrawRangeByName (name scans); removed the display-name mesh fallback in tree-click handling.

Results on the reference model (~7k solids)

before after
GLB size 800 MB 486 MB (−39%)
model extent −40.5 … 46.6 m −10.9 … 45.3 m (phantom bands gone)
out-of-extent nodes (60 m) 0 0
solids meshed 7000 7001
repair accounting none 49,923 faces rebuilt, 1,267 holes restored, 0 gate drops

pythonocc 120 tests / adacpp 112 tests green; frontend typecheck + 33 tests green (new: arc-sense triple-authoring, wedge containment, closed-cylinder survival, hole re-add, gate unit, range-index uniqueness).

🤖 Generated with Claude Code

Krande and others added 2 commits June 10, 2026 21:34
Converted models showed spurious full cones/cylinders obfuscating the view.
The param-extent repair (for faces whose wire cannot trim an infinite
revolution surface) over-covered three ways, all fixed:

1. Arc sense. _sample_edge_points and make_edge_from_edge both walked
   circular arcs in the circle's positive parametric direction from the
   ORIENTED_EDGE's endpoints — sampling/building the complement arc
   whenever the traversal was reversed, which widened the parameter extent
   to the full period (a small wedge rebuilt as a full band). The occupied
   arc is determined by the EDGE_CURVE alone: its own endpoints, positive
   direction iff same_sense. Orientation only reverses traversal, and
   readers differ on whether they pre-swap oriented-edge endpoints, so
   both sites now consult the EdgeCurve.

2. Inner bounds. The rebuild filled cutout holes. Non-closure bounds
   (a closure bound = full circles concentric with the revolution axis,
   e.g. a closed cylinder's rims) are now re-added as hole wires —
   tried in both windings, pcurves restored via ShapeFix_Face (safe
   here: the face is bounded, unlike the known unbounded-face segfacts)
   and accepted only when the area genuinely shrinks.

3. Area gate. A rebuilt face whose area exceeds 4x the squared bbox
   diagonal of its own boundary samples is over-covering its evidence
   and is dropped — a hole beats a phantom cone. Legitimate closed
   revolution faces peak at ~1.2x, so the gate has >3x margin.

Face-repair counters (rebuilds, holes re-added/dropped, gate drops) ride
back from the tessellation workers and surface in the conversion stats
("rebuilds") and summary log, so a conversion now quantifies its
fidelity loss.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Real CAD models carry thousands of nodes sharing one display name, and
every loaded GLB numbers its nodes from 0 — so the only globally unique
identity for a picked range is (model_key, rangeId). Four lookups keyed
by display name and returned the first match, highlighting/selecting the
wrong element:

- findNodeById scanned the whole tree comparing display names (used to
  sync the tree after a viewport click). Replaced by an O(1) reverse
  index in treeViewStore (buildRangeIndex: "model_key|rangeId" -> tree
  node, rebuilt on setTreeData/clearTreeData) — also drops a worker
  round-trip plus a full-tree recursive scan PER SELECTED RANGE.
- getDrawRangeByName linearly scanned id_hierarchy by name AND read the
  ACTIVE model's userdata even when the cross-model link targeted
  another file. crossModelSelect now recovers the target model's key
  from its own meshes and resolves all member names in one batched
  worker call (getDrawRangesByMemberNames — existing, previously
  unused), off the main thread, all matches instead of first-match.
- handleClickedNode's display-name mesh fallback could only ever bind a
  wrong same-named mesh; node_name (the merged-mesh name) is
  authoritative and a null simply means a pure group row.
- AnimationController's name lookup is left with a TODO (FEA animation
  meshes have unique names in practice; the GLB node index is the
  future key).

Unit test: duplicate names across two overlaid models resolve per
(model_key, rangeId). Frontend typecheck clean, 33 tests green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

🚀 Profiling Results (Top 20 most expensive calls)

Function Calls Duration (s)
<built-in method builtins.exec> (~:0) 1986 22.8316
<module> (<string>:1) 1 22.8316
test_build_big_ifc_pipe (tests/profiling/test_ifc_creation.py:48) 1 6.2892
to_ifc (src/ada/api/spatial/assembly.py:220) 5 5.7853
sync (src/ada/cadit/ifc/store.py:158) 5 5.6714
sync_added_physical_objects (src/ada/cadit/ifc/write/write_ifc.py:92) 5 5.6200
add (src/ada/cadit/ifc/write/write_ifc.py:493) 2200 5.5437
test_bench_batch_tessellate (tests/profiling/test_cad_backend_bench.py:63) 1 5.0832
__truediv__ (src/ada/api/spatial/part.py:1682) 8 4.8377
run (tests/profiling/test_cad_backend_bench.py:73) 6 4.6758
batch_tessellate (src/ada/occ/tessellating.py:339) 1446 4.6744
add_object (src/ada/api/spatial/part.py:309) 1201 3.8500
add_pipe (src/ada/api/spatial/part.py:164) 200 3.8314
tessellate_geom (src/ada/occ/tessellating.py:306) 1440 3.3352
add_section (src/ada/api/spatial/part.py:294) 801 3.2088
add (src/ada/api/containers/sections.py:139) 805 3.2041
equal_props (src/ada/sections/concept.py:92) 241399 2.9999
unique_props (src/ada/sections/concept.py:99) 482798 2.7198
tessellate_occ_geom (src/ada/occ/tessellating.py:256) 1440 2.4087
tessellate_shape (src/ada/occ/tessellating.py:180) 1440 2.3872

📈 Performance Impact per Commit

Commit Total Top-20 Duration (s) Change
40c156f 116.1544 -
51613b4 118.7475 ⚪ +2.5931 (+2.23%)
9ed37b9 121.4801 ⚪ +2.7326 (+2.30%)
c68f57a 119.8083 ⚪ -1.6718 (-1.38%)
229a985 121.7894 ⚪ +1.9810 (+1.65%)

@github-actions

Copy link
Copy Markdown

👋 Hi there! I have checked your PR and found no issues. Thanks for your contribution!

PR Review:

I found no pr-related issues.

  • ✅ PR title is ok
  • ✅ Release label is ok
  • ✅ SOURCE_KEY is set as a secret
  • ✅ Calculated next version: "0.18.4"

Python Review:

I found no python-related issues.

Python Linting results:

  • ✅ Isort
  • ✅ Black
  • ✅ Ruff

Python Packaging results:

  • ✅ I found the PYPI_API_TOKEN secret.
Packaging Type Package Name Version
pyproject.toml ada-py 0.18.3
pypi ada-py 0.18.3

@Krande Krande merged commit ccab2d8 into main Jun 11, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant