Skip to content

Fix KiCad via rendering and per-side tenting - #572

Merged
qu1ck merged 2 commits into
openscopeproject:masterfrom
tytelmaier:fix/kicad-via-visibility
Sep 28, 2026
Merged

qu1ck merged 2 commits into
openscopeproject:masterfrom
tytelmaier:fix/kicad-via-visibility

Conversation

@tytelmaier

@tytelmaier tytelmaier commented Sep 27, 2026 •

Copy link
Copy Markdown
Contributor

Fix KiCad 10 via tenting export and render tented vias

The patch and additional test checks were developed with AI assistance. I tested the resulting plugin on my board using KiCad 10.0.6 and confirmed that it fixes the reported via visibility issue.

Problem

With Include tracks/zones enabled, iBOM 2.12.0 exports KiCad 10 vias as zero-length tracks without drill information. On the reported KiCad 10.0.6 board, changing individual or global tenting did not make the expected via holes appear. Tented vias were also difficult to see in the generated view.
ibom_no_via_rendering

parse_tracks() defaults to a board-wide GetTentVias() value that is unavailable in KiCad 10, then reuses one dictionary for both faces. The renderer relies on a zero-length stroke for tented vias. Together these prevent accurate per-side tenting and reliable via visibility.

Change

  • Export a separate marked via entry for each occupied outer copper layer.
  • Read effective tenting with IsTented(layer); support older KiCad's no-argument IsTented() and the existing board-wide fallback.
  • Read each side's via diameter with GetWidth(layer), falling back to GetWidth() on older KiCad.
  • Draw every marked via as an explicit circle. Draw its hole only when that face has drillsize. The renderer still recognizes legacy drilled-via data without the new marker.

Expected view

Via state Front Back
Tented on both Filled circle Filled circle
Untented on both Circle with hole Circle with hole
Untented on front only Circle with hole Filled circle
Untented on back only Filled circle Circle with hole

Only an outer copper layer occupied by the via receives an entry.

Compatibility with older KiCad versions

The parser tries the layer-aware IsTented(layer) and GetWidth(layer) calls first, then falls back to the no-argument IsTented() and GetWidth() signatures used by older bindings. When IsTented is unavailable, it uses the existing board-wide tenting fallback.

These compatibility paths were checked with API mocks, including KiCad 8-style signatures. They have not been tested in an installed older KiCad release.

Testing

  • Generated a fresh HTML BOM from the sample PCB using KiCad 10.0.6. All 12 vias were exported on both faces. One front entry and two back entries included drill sizes, matching the board's tenting settings.
  • Checked all four front/back tenting combinations by changing a via in memory. Drill sizes appeared only on the untented sides.
  • Checked export for a via occupying only one outer layer and for different front/back diameters, using wrappers and mocks.
  • Checked ordinary tracks, tented and untented vias, older drilled-via records, and net highlighting with a canvas-call harness. Representative EasyEDA and Eagle/Fusion track records also passed; complete projects from those applications were not tested.
  • Python compilation, JavaScript syntax, git diff --check, and the module-import smoke check passed. The import check was run directly under KiCad Python rather than through pytest.

The sample board contains through vias on a two-layer PCB; a real blind or buried via was not part of the test project.
image

@qu1ck qu1ck left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Looks good, just get rid of via property

Comment thread InteractiveHtmlBom/ecad/kicad.py Outdated
if track.IsOnLayer(layer):
result[layer].append(track_dict)
via_dict = track_dict.copy()
via_dict["via"] = True

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This extra property does not really carry any useful information and just bloats the output size.
Tented via is really not any different than a track segment for rendering purposes so the only thing we need to pass is drill hole size for untented vias.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed the via field in commit 85ae313.

Comment thread InteractiveHtmlBom/web/render.js Outdated
track.start[1] == track.end[1]);

// First draw tracks and tented vias
var isVia = (track) => track.via || hasHole(track);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Keep old logic here. Only reason there is a separate second pass is to draw holes after everything else to have them always visible.

Tracks and tented vias can go in the first pass.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I've removed the via field and kept the original hasHole logic and pass order: tracks and tented vias first, then untented vias and their holes.

I've kept explicit circle drawing because zero-length strokes paint nothing in the browser tests, even with round line caps. This reproduced in headless Edge 154 and Chrome 153. The circles use the existing coordinates and width, so no extra data field is needed.

Here's a standalone example of the stroke behavior:

const canvas = document.createElement("canvas");
canvas.width = canvas.height = 96;
const ctx = canvas.getContext("2d");
ctx.strokeStyle = "#00ff00";
ctx.lineCap = "round";
ctx.lineWidth = 24;
ctx.beginPath();
ctx.moveTo(48, 48);
ctx.lineTo(48, 48);
ctx.stroke();
console.log(ctx.getImageData(48, 48, 1, 1).data[3]);
// 0 in the tested browsers: the center pixel is transparent.

The Canvas standard removes zero-length segments when tracing a path for stroking, so a round-capped zero-length stroke is not a dependable way to draw a dot.

Earlier working results could reflect a different browser implementation or version, but I haven't compared older browsers or known-good older HTML exports. The circle change fixes the behavior reproduced here; I can't yet say when or why earlier results differed.

The revised export and pixel checks pass, including all four front/back tenting combinations on a KiCad 10.0.6 via, net highlighting, and holes remaining visible above overlapping tracks. Older KiCad API fallbacks were checked with mocks rather than installed older releases.

@qu1ck
qu1ck merged commit 55f81dc into openscopeproject:master Sep 28, 2026
@qu1ck

qu1ck commented Sep 28, 2026

Copy link
Copy Markdown
Member

Yeah the change to drop 0 length segments in paths is recent. Chrome definitely used to draw it few versions back.

Thanks for your contribution.

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