Skip to content

Fix projection hang, parser panic, nondeterminism and sphere faces (ENG-3712) - #22

Merged
akhilles merged 4 commits into
masterfrom
akhil/eng-3712-foxtrot-hangs-nondeterminism-and-failed-faces-found-by-the
Oct 9, 2026
Merged

akhilles merged 4 commits into
masterfrom
akhil/eng-3712-foxtrot-hangs-nondeterminism-and-failed-faces-found-by-the

Conversation

@akhilles

@akhilles akhilles commented Oct 9, 2026

Copy link
Copy Markdown

This change fixes four tessellation defects that the board corpus finds (ENG-3712). NURBS projection stops after 65536 patches near a collapsed edge, the parser keeps the first items of an over-long bounded list, shapes and colour groups come out in a fixed order while shell faces mesh in parallel, and whole spheres bounded by a vertex loop mesh as rings of latitude. Of 885 corpus STEP models, 877 give the same mesh as master, 7 change only because they now tessellate with no failed faces, and one times out on both.

akhilles and others added 4 commits October 9, 2026 17:35
When the incumbent is the pole of a collapsed edge, every patch along that
edge reaches it, so certifying one projection examined millions of patches:
a 4x3 NURBS face on an FMC connector took minutes. Settle for the incumbent
after 65536 patches, twice the most any ordinary query in the board corpus
examines.

Amp-Thread-ID: https://ampcode.com/threads/T-01a12192-0f95-7657-bde4-754232566545
Co-authored-by: Amp <amp@ampcode.com>
Some exporters write four-component directions, which panicked the parser
and lost the whole model. Keep the first items, as OCCT does.

Amp-Thread-ID: https://ampcode.com/threads/T-01a12192-0f95-7657-bde4-754232566545
Co-authored-by: Amp <amp@ampcode.com>
Sample every edge of a shell up front, so faces sharing an edge still share
its points, then mesh the faces on the rayon pool and merge them in face
order. Shapes and their copies were meshed in hash map order, and colour
groups of equal size were emitted in hash map order, so output changed from
run to run. Sort shapes by entity id, copies by transform, and colour groups
by size, then colour.

Amp-Thread-ID: https://ampcode.com/threads/T-01a12192-0f95-7657-bde4-754232566545
Co-authored-by: Amp <amp@ampcode.com>
BGA solder balls are whole spheres bounded only by a vertex loop at the
pole, which no chart covers, so every ball failed to tessellate. Mesh them
as rings of latitude between two poles within the tolerance.

Amp-Thread-ID: https://ampcode.com/threads/T-01a12192-0f95-7657-bde4-754232566545
Co-authored-by: Amp <amp@ampcode.com>

akhilles commented Oct 9, 2026

Copy link
Copy Markdown
Author

Verification (orb, 16 threads, tessellate_step_bytes with parallel, median of 3 in-process runs):

Model master this PR
ASP-134486-01.step aborts after 1 min 56 s: 11.6 GB resident, a 3.4 GB queue allocation fails 757 ms, 5167 faces, 0 failed
36907406S.stp panics (CapacityError) 16 ms, 315 faces, 0 failed
TCM1-63AX.step 75 ms, exact output differs on every run (6 of 6) 15 ms, identical on every run
901-144-8RFX.step 634 ms 144 ms, same mesh
STM32H563IGK6.step 201 of 209 faces fail 0 failed, 13 ms
MT48LC16M16A2B4-6A_G.step 54 of 62 faces fail 0 failed, 6 ms
TPS22916BYFPR.step 4 of 12 faces fail 0 failed, 2 ms
STM32H747XIH6.step 265 of 273 faces fail 0 failed, 23 ms
125_3203_001_A.stp (not in the issue) 1183 of 26239 faces fail (all vertex-loop spheres) 0 failed, 747 ms

Board corpus: all 893 distinct embedded models from the 619 boards, of which 885 are STEP (8 are WRL). Each model ran in its own process with a 300 s timeout and an 8 GB address-space limit. The canonical mesh hash is the sorted set of triangles with colours.

  • 877 models give the same mesh as master. 7 differ: the six from the issue plus 125_3203_001_A.stp. All 7 now tessellate with no failed faces.
  • Two full runs of this branch give identical exact (ordered) output for all 884 models that finish. Before the colour-group tie-break, 7 models still changed order between runs.
  • RedPitaya_3Dmodel.stp exceeds 300 s on both master and this branch. That is a separate, existing problem.
  • Models still partial on both master and this branch, unchanged: ASP-184330-01 (6 faces), Multispectral Thermal Camera PAG7936 (2), BSS138BKT-TP (1).

Tests: cargo test --workspace --all-features passes at every commit. The triangulate tests also pass with rayon removed from the build, and the wasm32 check passes. Each new regression test fails on master: the sphere test with CouldNotLower, the parser test with CapacityError, and the order tests by changing order. The projection test does not finish within 60 s on master; with the cap it takes about 6 s in a debug build.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Not approved: this is a functional tessellation and mesh-output change, not a small fixup, so human review is required. Cursor Security Agent passed with no findings; Cursor Bugbot was not present. Human review is needed and no reviewers were assigned.

Open in Web View Automation 

Sent by Cursor Approval Agent: Pull Request Router and Approver

@akhilles
akhilles requested review from LK and lucia3e8 October 9, 2026 17:50
@akhilles
akhilles merged commit 34b2d05 into master Oct 9, 2026
4 checks passed
akhilles added a commit to diodeinc/pcb that referenced this pull request Oct 9, 2026
Picks up the projection, parser, determinism and sphere fixes from
diodeinc/foxtrot#22 (ENG-3712).
akhilles added a commit to diodeinc/pcb that referenced this pull request Oct 9, 2026
* refactor(step): build a public scene that the STEP writer consumes

Split `pcb_step::export` into `Scene::build`, which turns a board into body
solids, copper, silkscreen and solder mask faces and model placements, and
the STEP writer that consumes it, so other formats can export the same
assembly. STEP output is byte-identical on every board in the DiodeHub
corpus that exported before.

Move the `pcb step export` command line into `pcb_step::cli` behind a `cli`
feature so other exporters take the same arguments.

* feat(gltf): add pcb gltf export

Write a GLB for a KiCad board without kicad-cli. The board side comes from
the pcb-step scene: prisms become caps and walls, round holes are revolved
from their profiles, and flat faces are triangulated with i_triangle. Each
distinct embedded STEP model is tessellated once with foxtrot and placed by
a node per footprint. Takes the same arguments as `pcb step export`.

* chore(deps): bump foxtrot to 34b2d05

Picks up the projection, parser, determinism and sphere fixes from
diodeinc/foxtrot#22 (ENG-3712).

* fix(gltf): write exports atomically and reject empty ones

Replace the hand-rolled .part staging with atomicwrites, so an output
named like the staging file is no longer truncated and deleted when an
export fails. Reject a GLB with no board or components as STEP does.

* fix(gltf): print export warnings when the export fails

Exporters now add warnings to a report the command owns, so a failure
such as every model failing to load still names the models.
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