Engineering Journal
Schema Editor
Schema Editor

One frame line ate the schematic and then claimed every net on it

2026-08-21

TLDR: We clustered drawing geometry into parts by proximity, and excluded the sheet frame with a test for boxes that are wide AND tall. A frame is four straight lines, each wide and zero-tall, so none of them were excluded. Every part within 2.5pt of a frame line chained into one blob that then appeared in every net.

The symptom

The first netlist we generated looked almost right. Thirty-five parts with sensible values, correct part numbers on the ICs.

Then the nets:

+3V3   (2w)  -> M7
+3V3   (1w)  -> IC7B, M7
+5V    (2w)  -> M7
+5V    (6w)  -> IC1, M7
+5V    (14w) -> C4, C5, C6, IC3, L2, M7

M7 in every single one. A component connected to every net on a board is not a component.

The cluster

Parts are reassembled by proximity, because CAD tools do not export a symbol as one element. A resistor body is four independent stroked segments; there is no node in the file that is the resistor. Union-find over bounding boxes within a small gap puts them back together.

Anything that legitimately spans the sheet has to be kept out of that, or it bridges unrelated parts. So there was a filter:

const isFrame = (b) => b.width > pw  0.6 && b.height > ph  0.6;

Read it as a sentence and it is obviously right: the sheet frame is a big rectangle, so exclude big rectangles.

Read it against the data and it never fires. The frame is not a rectangle in this file. It is four line segments, and each one has a bounding box of 1167 by 0. Wide, and zero tall. width > 0.6 pw passes, height > 0.6 ph fails, so the whole test fails and every frame line went into the clustering pass.

A 1167pt line touches a lot of things. With a 2.5pt gap it chained the title block, the border ruling, and every part near the edge of the sheet into a single cluster spanning the page. That cluster then sat within tolerance of every net endpoint on the drawing.

Why && felt right

Because the mental image was a rectangle.

We were thinking of the frame as an object, and the object is big in both directions. The file does not contain the object. It contains four strokes, and a stroke is degenerate in one axis by definition.

This is the recurring shape of vector-parsing bugs: you reason about the thing depicted, and the code operates on the marks that depict it. The two have different properties, and nowhere is the difference sharper than in a bounding box, where any straight line has a zero.

The fix

const isFrame = (b) => b.width > pw  0.6 || b.height > ph  0.6;

One character. Nothing that spans most of the sheet in either direction takes part in clustering.

We added a second, independent bound afterwards: any resulting cluster larger than most of the page is discarded rather than trusted. Not because the first fix is insufficient, but because "one part is connected to everything" is a failure the output should never be able to express, and the two checks fail for different reasons.

The fix that broke something else

The post-cluster cap was first written at a third of the page. That immediately dropped the ATmega2560, whose symbol body legitimately covers about half the sheet height, and with it the only part on the board a reader looks for first.

The netlist did not report an error. It reported IC3 with no part number and no ATMEGA2560-15AU anywhere, and +5V quietly not reaching the microcontroller. Both readable as ordinary sparse extraction.

The cap now sits at 60% of width or 75% of height, which admits a large microcontroller and still refuses a page-spanning blob. There is no principled number there, only a measured range with real parts on one side and known failures on the other, and it is written down in the code with the reason.

What we would do differently

Test the exclusion filter directly. Both bugs were in guards. Neither had a test, because a guard's job is to make nothing happen and it is easy to believe nothing happening is correct.

Give degenerate geometry its own case. A predicate that takes a bounding box should say what it does when a dimension is zero, because in vector data that is common rather than exceptional.

Treat "connected to everything" as an assertion. A part that appears in every net is structurally impossible in a real circuit. That was checkable from the output alone and we found it by reading, which does not scale.

Watch for the fix that silently removes good data. The size cap made the visible bug disappear and took a real component with it. A change that improves your headline number by discarding inputs deserves the same suspicion as the bug it replaced.

Read this post in the full Engineering Journal →