You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue was investigated and written up with the help of Claude
(Anthropic's AI assistant), while trying to debug rendering glitches in a
real-world Gerber file. The root cause was narrowed down interactively
(checking D-code counts, isolating a minimal reproduction, then checking
whether the bug was specific to the legacy Parser2 or also present in the
new Parser on the 3.0 pre-release line, etc.), and the reproduction script
/ minimal Gerber file below were also written with Claude's assistance and
verified by actually running them. I'm flagging this up front in case it's
useful context for how to weigh/triage the report; happy to answer
follow-up questions or run further tests myself.
Mandatory checks
I have reviewed the README for guidelines and haven't found a solution there.
I have reviewed the PyGerber documentation and haven't found a solution there.
I have reviewed the existing open issues and verified that this is not a
duplicate.
I have reviewed the existing closed issues and verified that this was already
resolved or marked as won't fix.
I have reviewed the existing pull requests and verified that this is not a already
known issue.
I have reviewed the existing discussions and verified that this is not a already
known issue.
I found one issue with a superficially similar symptom: #67 ("Drawing with 0
surface apertures may still create lines"), which describes unexpected
lines appearing between parts of a board and mentions "incorrect default
values for D02 and D03 coordinates" as a possible cause. It was closed as
fixed by the 2.1.0 tokenizer refactor. I believe this is a different bug
(details in Additional context below), but linking it here per the
guidelines above in case it turns out to be related after all.
This requests three flashes of a 2mm circular aperture at (10, 10),
(50, 50) and (90, 10) mm. The second coordinate block
(X050000Y050000*) omits the D-code, so per the Gerber Layer Format
Specification (section 4.5, "Modal parameters") it should continue in
D03 (flash) mode and produce a flash at (50, 50).
See a diagonal line connecting the three points, instead of three
independent circular pads (see attached modal_d03_before_after.png, left side).
A self-contained script that also verifies this programmatically (by
scanning rows of the output image that should be empty if only three
separate pads exist) is attached as reproduce_modal_d03_bug.py. Running
it prints, for rows far from both pad y-coordinates, a run of ~55 drawn
pixels whose x-position shifts smoothly with y — this is the spurious
diagonal line, not part of either pad:
Row scan (should be mostly empty if only 3 separate pads exist):
y= 84: 55 drawn px, x range [728, 782] <-- unexpected if not at a pad
y= 252: 55 drawn px, x range [560, 614] <-- unexpected if not at a pad
y= 420: 55 drawn px, x range [392, 446] <-- unexpected if not at a pad
y= 588: 55 drawn px, x range [224, 278] <-- unexpected if not at a pad
y= 756: 55 drawn px, x range [56, 110] <-- unexpected if not at a pad
Three separate circular pads, with no connecting lines between them, since
the coordinate-only line should continue the previously active D03 (flash)
mode rather than being reinterpreted as D01 (draw).
I did not check this specific file against the Reference Gerber
Viewer, but the expected behavior
follows directly from the Gerber Layer Format Specification's definition
of modal parameters (an operation code omitted in a coordinate data block
reuses whichever operation code was last set), and is also consistent with
how PyGerber itself already handles modal continuation for D01 and D02
(both work correctly; only D03 is affected — see below).
Additional context
Confirmed on two PyGerber versions
2.4.3 (pip install pygerber), using pygerber.gerberx3.api.v2.GerberFile.render_raster.
3.0.0a4 (pip install pygerber==3.0.0a4 --pre), using the new pygerber.gerber.api.GerberFile.render_with_pillow. I understand Parser2 (used by the 2.x line) has been fully replaced by a new Parser on the road to 3.0, so I wanted to check whether this was
specific to the old parser before reporting — it isn't. The same
spurious diagonal line appears with the new parser/renderer as well, and
the same workaround (re-inserting D03, see below) resolves it there
too:
Row scan on 3.0.0a4 output (bug present):
y= 84: 59 drawn px, x range [726, 784]
y= 252: 59 drawn px, x range [558, 616]
y= 420: 59 drawn px, x range [390, 448]
y= 588: 59 drawn px, x range [222, 280]
y= 756: 59 drawn px, x range [54, 112]
drawn ratio: 0.0362
Row scan on 3.0.0a4 output (after re-inserting D03):
y= 84: empty (OK, no spurious line)
y= 252: empty (OK, no spurious line)
y= 420: empty (OK, no spurious line)
y= 588: empty (OK, no spurious line)
y= 756: empty (OK, no spurious line)
drawn ratio: 0.0029
(Pixel counts/positions differ slightly from the 2.4.3 numbers above,
likely due to anti-aliasing / rendering differences between the two
backends, but the pattern — a diagonal run of drawn pixels whose
position shifts smoothly with y — is identical.)
Since the bug reproduces on both the legacy Parser2 and the new Parser,
it doesn't seem to be an artifact of one particular parser implementation,
but rather something present in the underlying logic/model for how modal
D-code state is tracked and carried across both parser generations. It
might be worth checking whether D03 is handled by a separate code path
from D01/D02 when a command's D-code is implicit / inherited from a
previous modal state.
Left (current behavior): a visible diagonal line connects the
three intended flash points instead of three independent pads.
Right (after applying the workaround described below): only the
three separate circular pads are drawn, with no connecting line.
Workaround
The "after" image above was produced by taking the exact same modal_d03_repro.gbr file and simply re-inserting an explicit D03 on the
coordinate-only line, i.e. changing X050000Y050000* to X050000Y050000D03*:
Re-running the same row-scan on this corrected file confirms all five
sampled rows are empty (no spurious line), and the drawn pixel ratio drops
from ~0.034 to ~0.003 (consistent with only the three small pads
remaining). This strongly suggests the fix is simply to make the parser's
modal D-code tracking apply to D03 the same way it already does for
D01/D02.
More generally, as a workaround in our own code, we pre-process the Gerber
source before passing it to PyGerber: we track the currently active D-code
while splitting the file on *, and whenever a coordinate-only token
follows a D03 context, we explicitly re-insert D03 on that token. Modal
continuation for D01/D02 is left untouched, since it already works
correctly.
Real-world impact
This was originally discovered while rendering a much larger (~3 MB)
real-world Gerber file that makes heavy use of D03 flashes followed by
D-code-omitted coordinate blocks (a common pattern in files that keep the
D-code fully modal for long stretches of the file). In that file, this bug
produced a large number of spurious long diagonal lines crossing the
entire board image, since many unrelated flash points ended up being
connected by draw commands. Applying the pre-processing workaround
described above dropped the drawn pixel ratio in the rendered output from
~63% to ~35%, consistent with a large number of spurious lines being
removed. The source file itself is confidential (real-world PCB design),
so I'm not attaching it, but the minimal reproduction above shows the same
underlying issue.
While searching for existing reports, I found #67 ("Drawing with 0 surface
apertures may still create lines"), which describes a superficially
similar symptom and mentions "incorrect default values for D02 and D03
coordinates" as a possible cause. It was closed as fixed by the 2.1.0
tokenizer refactor. I believe this is a different underlying bug:
Drawing with 0 surface apertures may still create lines #67's working theory was about zero-width apertures still rendering a
1-2px line (a Pillow/rounding artifact tied to D02 aperture
repositioning), not about D-code mode being misinterpreted.
In my testing, modal continuation of D01 and D02 both work correctly;
only D03 modal continuation is affected. If this were the same
zero-width-aperture issue, I'd expect D02 repositioning itself to be the
problem, not D03 specifically.
Happy to be told this is in fact the same root cause if it looks that way
from the implementation side.
Environment:
Please complete the following information:
Operating system: Ubuntu 24.04, x86_64
Python version: 3.12.3
PyGerber version: 2.4.3 (also reproduced on 3.0.0a4, see Additional context above)
Optional checks
I want to contribute example source files attached to this issue in the test suite
of PyGerber for regression testing purposes.
I want to include separate LICENSE file for resource files attached as a result of
an agreement described in first checkbox in this section.
I want to include separate README file for resource files attached as a result of
an agreement described in first checkbox in this section.
PyGerber Bug Report
Note on how this report was produced
This issue was investigated and written up with the help of Claude
(Anthropic's AI assistant), while trying to debug rendering glitches in a
real-world Gerber file. The root cause was narrowed down interactively
(checking D-code counts, isolating a minimal reproduction, then checking
whether the bug was specific to the legacy
Parser2or also present in thenew
Parseron the 3.0 pre-release line, etc.), and the reproduction script/ minimal Gerber file below were also written with Claude's assistance and
verified by actually running them. I'm flagging this up front in case it's
useful context for how to weigh/triage the report; happy to answer
follow-up questions or run further tests myself.
Mandatory checks
duplicate.
resolved or marked as won't fix.
known issue.
known issue.
I found one issue with a superficially similar symptom: #67 ("Drawing with 0
surface apertures may still create lines"), which describes unexpected
lines appearing between parts of a board and mentions "incorrect default
values for D02 and D03 coordinates" as a possible cause. It was closed as
fixed by the 2.1.0 tokenizer refactor. I believe this is a different bug
(details in
Additional contextbelow), but linking it here per theguidelines above in case it turns out to be related after all.
To Reproduce
Save the following as
modal_d03_repro.gbr:This requests three flashes of a 2mm circular aperture at (10, 10),
(50, 50) and (90, 10) mm. The second coordinate block
(
X050000Y050000*) omits the D-code, so per the Gerber Layer FormatSpecification (section 4.5, "Modal parameters") it should continue in
D03 (flash) mode and produce a flash at (50, 50).
Render it with PyGerber, e.g. with the 2.x API:
or with the new 3.x API:
Open the resulting PNG.
See a diagonal line connecting the three points, instead of three
independent circular pads (see attached
modal_d03_before_after.png, left side).A self-contained script that also verifies this programmatically (by
scanning rows of the output image that should be empty if only three
separate pads exist) is attached as
reproduce_modal_d03_bug.py. Runningit prints, for rows far from both pad y-coordinates, a run of ~55 drawn
pixels whose x-position shifts smoothly with y — this is the spurious
diagonal line, not part of either pad:
modal_d03_repro.gbr.txt
reproduce_modal_d03_bug.py
Expected behavior
Three separate circular pads, with no connecting lines between them, since
the coordinate-only line should continue the previously active D03 (flash)
mode rather than being reinterpreted as D01 (draw).
I did not check this specific file against the Reference Gerber
Viewer, but the expected behavior
follows directly from the Gerber Layer Format Specification's definition
of modal parameters (an operation code omitted in a coordinate data block
reuses whichever operation code was last set), and is also consistent with
how PyGerber itself already handles modal continuation for D01 and D02
(both work correctly; only D03 is affected — see below).
Additional context
Confirmed on two PyGerber versions
2.4.3 (
pip install pygerber), usingpygerber.gerberx3.api.v2.GerberFile.render_raster.3.0.0a4 (
pip install pygerber==3.0.0a4 --pre), using the newpygerber.gerber.api.GerberFile.render_with_pillow. I understandParser2(used by the 2.x line) has been fully replaced by a newParseron the road to 3.0, so I wanted to check whether this wasspecific to the old parser before reporting — it isn't. The same
spurious diagonal line appears with the new parser/renderer as well, and
the same workaround (re-inserting
D03, see below) resolves it theretoo:
(Pixel counts/positions differ slightly from the 2.4.3 numbers above,
likely due to anti-aliasing / rendering differences between the two
backends, but the pattern — a diagonal run of drawn pixels whose
position shifts smoothly with y — is identical.)
Since the bug reproduces on both the legacy
Parser2and the newParser,it doesn't seem to be an artifact of one particular parser implementation,
but rather something present in the underlying logic/model for how modal
D-code state is tracked and carried across both parser generations. It
might be worth checking whether D03 is handled by a separate code path
from D01/D02 when a command's D-code is implicit / inherited from a
previous modal state.
Attached files
modal_d03_repro.gbr— minimal reproduction (8 lines).reproduce_modal_d03_bug.py— script that renders it and programmaticallydetects the spurious line.
modal_d03_before_after.png— side-by-side comparison:three intended flash points instead of three independent pads.
three separate circular pads are drawn, with no connecting line.
Workaround
The "after" image above was produced by taking the exact same
modal_d03_repro.gbrfile and simply re-inserting an explicitD03on thecoordinate-only line, i.e. changing
X050000Y050000*toX050000Y050000D03*:Re-running the same row-scan on this corrected file confirms all five
sampled rows are empty (no spurious line), and the drawn pixel ratio drops
from ~0.034 to ~0.003 (consistent with only the three small pads
remaining). This strongly suggests the fix is simply to make the parser's
modal D-code tracking apply to D03 the same way it already does for
D01/D02.
More generally, as a workaround in our own code, we pre-process the Gerber
source before passing it to PyGerber: we track the currently active D-code
while splitting the file on
*, and whenever a coordinate-only tokenfollows a D03 context, we explicitly re-insert
D03on that token. Modalcontinuation for D01/D02 is left untouched, since it already works
correctly.
Real-world impact
This was originally discovered while rendering a much larger (~3 MB)
real-world Gerber file that makes heavy use of D03 flashes followed by
D-code-omitted coordinate blocks (a common pattern in files that keep the
D-code fully modal for long stretches of the file). In that file, this bug
produced a large number of spurious long diagonal lines crossing the
entire board image, since many unrelated flash points ended up being
connected by draw commands. Applying the pre-processing workaround
described above dropped the drawn pixel ratio in the rendered output from
~63% to ~35%, consistent with a large number of spurious lines being
removed. The source file itself is confidential (real-world PCB design),
so I'm not attaching it, but the minimal reproduction above shows the same
underlying issue.
Relationship to issue #67
While searching for existing reports, I found #67 ("Drawing with 0 surface
apertures may still create lines"), which describes a superficially
similar symptom and mentions "incorrect default values for D02 and D03
coordinates" as a possible cause. It was closed as fixed by the 2.1.0
tokenizer refactor. I believe this is a different underlying bug:
1-2px line (a Pillow/rounding artifact tied to D02 aperture
repositioning), not about D-code mode being misinterpreted.
fix mentioned in Drawing with 0 surface apertures may still create lines #67, so whatever was fixed there does not seem to cover
this case.
only D03 modal continuation is affected. If this were the same
zero-width-aperture issue, I'd expect D02 repositioning itself to be the
problem, not D03 specifically.
Happy to be told this is in fact the same root cause if it looks that way
from the implementation side.
Environment:
Please complete the following information:
Additional contextabove)Optional checks
of PyGerber for regression testing purposes.
an agreement described in first checkbox in this section.
an agreement described in first checkbox in this section.