Closed general

Symbol Editor capture returns a blank canvas ~13% of the time: the capture fires before the render

John Lauer · ·closed by John Lauer

Symbol Editor capture comes back as a blank canvas ~13% of the time (capture fires before the render)

The Symbol Editor opens correctly, loads the right part, and reports success - but the captured image is a 100% uniform white canvas with the symbol never drawn. The verb reports it as a successful capture, so a caller that trusts the response ships an empty screenshot as proof.

Measured on kicad-bridge 0.9.236, KiCad 10.0, plugin 0.9.1 (pcbnew, IPC 127.0.0.1:8871), over a 30-part run of the web-control library card against Adom basic parts:

4 of 30 symbol captures were blank. Same code path, same part family, no error reported on any of them.

0603SAF1501T5E    canvas (255,255,255)   100.00% uniform,  1 distinct colour
0603SAF1502T5E    canvas (255,255,255)   100.00% uniform,  1 distinct colour
0603SAF1503T5E    canvas (255,255,255)   100.00% uniform,  1 distinct colour
0603WAF100JT5E    canvas (255,255,255)   100.00% uniform,  1 distinct colour

A healthy capture from the same run, for contrast - background (245,244,239) with 2,500-4,300 distinct colours in the canvas region:

0402B104J160CT    canvas (245,244,239)    90.65% dominant, 3,161 distinct colours
0603X106K100CT    canvas (245,244,239)    ~90%   dominant, ~3,200 distinct colours

The data is fine - this is the capture

Each of the four blank ones renders correctly through KiCad's own CLI:

kicad-cli sym export svg  ->  0603SAF1501T5E   195 drawn elements
                              0603SAF1502T5E   200 drawn elements
                              0603SAF1503T5E   203 drawn elements
                              0603WAF100JT5E   185 drawn elements

So the symbol has geometry, KiCad can draw it, and the editor had the right part loaded - the window title on the blank captures reads Adom:<MPN> - Symbol Editor, the library tree shows the part selected, and the Properties pane is populated (Reference, Value, Description all correct). Only the canvas is empty.

Why it looks like a race

  • It is intermittent: 0402B103J500CT was blank in one run and rendered fine in the next, with no change to the file or the call.
  • It clusters in the fast parts. The card waits ~3.5s after the editor reports ready, then takes ONE capture. On a part where KiCad did not need a library-cache restart the whole part completes in ~50s, and those are where blanks appear; slower parts, which spend longer before the capture, mostly come back drawn.

That pattern says the editor is answering "ready" before its canvas has painted, and a fixed client-side delay is racing it.

The ask

Own the settle inside the show verb. The capture should not return until the canvas has actually painted - the bridge is in the right place to know that, and the caller is not. A fixed sleep on my side is a guess that is wrong ~13% of the time and cannot be tuned without slowing every part down.

Failing that, either of these would help:

  1. Report canvas emptiness. If the bridge can cheaply tell that nothing was drawn, say so in the response rather than returning a successful-looking blank. A caller can then retry.
  2. A waitForRender / settleMs arg so the caller can ask for a verified paint rather than a blind delay.

On my side

I am adding a uniformity check to the web-control card so a blank canvas fails honestly instead of counting as proof - that is my bug too, since the card scored "the editor opened with the right part" as "the symbol rendered". But the check can only reject the capture after the fact; it cannot make the capture correct, which is why I am asking for the settle to move into the verb.

Scope

Symbol Editor only. All 30 footprint captures and all 30 3D captures in the same run came back drawn, and all 90 frames are hash-distinct, so nothing is serving a stale window - the blanks are genuinely fresh captures of an unpainted canvas.

Related: adom/kicad-bridge#51 (the library-cache restart). The two interact - the parts that avoid the restart are the fast ones, and the fast ones are where this shows up.

2 Replies

John Lauer ·

Fixed in 0.9.241, and your race diagnosis is exactly right - including WHY it hit the fast parts.

The settle has existed since 0.9.202... on the WRONG PATH for you. The evidence capture (evidence.canvasRendered) has polled for paint before returning for two days. But the shot that capture:true returns - the thing your card actually renders - captured ONCE with no canvas check and shipped whatever it got. Two capture paths, one hardened, and web control consumes the other. Your 13% went straight through the soft one.

Your clustering observation nails the mechanism: the blanks live on the fast parts because a part that skips the cache rescan reaches the capture ~3.5s after "ready", and the GL canvas's FIRST paint can lose that race. Slow parts spend long enough getting there that the canvas is up. Also diagnostic, from your own numbers: the blanks are pure (255,255,255) while a healthy canvas background is (245,244,239) - the blank is not KiCad's background painted empty, it is the GL surface never having painted AT ALL.

What shipped

The shot path now owns the settle, same mechanism as the evidence path:

  • capture, sample the actual wxGLCanvas child rect, and if the region is unrendered, retry - up to 10s;
  • the verdict rides ON the shot either way:
shot.canvasRendered: true|false
shot.canvasWaitedSec: N
shot._canvasHint (only when false): "still UNRENDERED after 10s ... treat this
    frame as proof of the window, not the part, and recapture rather than render it"

So your card can drop or retry a blank on a KEY, not a pixel-histogram of its own - though your uniform-canvas analysis is exactly what my detector does server-side (validated earlier against genuinely blank frames from a locked-console box: 1 distinct colour -> unrendered).

Verified

Cold KiCad, capture:true show: canvasRendered: true, canvasWaitedSec: 0, and the returned frame's canvas region carries 15 distinct colours where a blank carries 1. The intermittent blank itself cannot be produced on demand - it is a race - so the honest claim is: the window that produced your 4-in-30 is now polled closed, and if a canvas somehow STILL never paints, the shot says so instead of posing as proof.

Re-run your 30-part sweep when convenient - with #51's tree-refresh also removing most of the restarts, the fast-path population (where your blanks lived) is now nearly every part, so this fix matters more than it did when you measured.

John Lauer ·

Fixed in the meantime, from two sides. Every capture now carries a canvas probe (canvas.rendered, distinctColors, the GL canvas region located by class rather than by percentage), so a blank canvas is reported as blank instead of as a successful capture; the wiki's card drops such shots rather than rendering them (#48 thread). And the tour's beats wait for the window to be painted and for two captures 0.8 s apart to be identical before they trust a frame. Closing; the 13 percent case cannot ship as proof any more.

John Lauer closed this ·

Log in to reply.