Skip to content

feat(cad): read Gerber PCB layers (RS-274X / X2) to SVG, PDF and images - #1458

Open
Ugoyo wants to merge 4 commits into
KnifeLemon:mainfrom
Ugoyo:claude/gerber
Open

Ugoyo wants to merge 4 commits into
KnifeLemon:mainfrom
Ugoyo:claude/gerber

Conversation

@Ugoyo

@Ugoyo Ugoyo commented Nov 13, 2026

Copy link
Copy Markdown
Collaborator

What does this change?

Filee can now read Gerber files, the layers of a printed circuit board as KiCad, Eagle, Altium, EasyEDA and
other PCB programs write them for board makers (copper, solder mask, silkscreen, paste, board outline). A layer
becomes an SVG at its real size, and the vector engine takes it to PDF (vector), PNG, JPG… and DXF / DWG
(the board outline as CAD geometry, handy for the enclosure). Until now a .gtl or -F_Cu.gbr was an unknown file.

The format is open and documented ("The Gerber Layer Format Specification", Ucamco), so it is unlike the closed CAD
formats closed as not planned (#1377, #1380). No issue or PR on it existed.

  • Reader (Filee.Engines/Vector/Gerber, built in, no dependency): coordinate format and units (FS, MO,
    G70/G71, leading/trailing zeros, absolute/incremental); standard apertures C, R, O, P with holes (also the
    rectangular holes of older files); aperture macros with variables, expressions and every primitive (exposure off,
    rotation about the macro origin); linear and circular draws (single- and multi-quadrant, full circles); regions with
    several contours, arcs and cut-ins; LPD/LPC; LM/LR/LS; nested aperture blocks (AB); step and repeat (SR); IPNEG; the
    X2 file function (also KiCad's G04 #@! TF… comment form).
  • Look: KiCad's default colours on its dark board background, by the layer's role (X2 file function, else the
    file name: -F_Cu, -B.SilkS, -Edge_Cuts, .gtl, .gbo, .g2…). Pale silkscreen and outlines would
    vanish on white, and this is what designers see in KiCad and Gerber viewers.
  • Clear polarity without SVG masks: Skia rasterises an SVG mask in a PDF (checked: a 355×355 image appeared), so
    a dark object that a later clear object overlaps is replaced by its outline minus what stays clear, with Skia's
    path operations. The PDF stays vector, and the SVG is really transparent there. A hole that is drawn over again is
    not cut, so no anti-aliasing seam shows. Past a budget, clear objects are painted in the background colour, which
    looks the same.
  • Defensive reading (as asked on feat(image): read and write Paint.NET .pdn files, keeping layers with PSD #1384): a damaged file fails with an InvalidDataException saying what is wrong
    (a GIMP brush, which also uses .gbr; RS-274-D without apertures; undefined apertures or macros; malformed numbers;
    an empty layer…). Budgets are checked before the memory is used: 256 MB file, 2 M objects, 10 M segments written
    and 50 M drawn once blocks and copies are placed (a panel of a large board passes; blocks flashed inside blocks
    can't multiply without end), 100 000 apertures, block depth 8, 10 000 SR copies, macro primitives, outline points,
    expression length and depth, coordinates within 100 m. A file that just stops keeps what was read.
  • Format: gerber, read only, category CAD, grade specialist, field cad (can be turned off; rule 5 of
    FORMAT-RULES admits read-only formats). The CAD donut (PDF, PNG, SVG, DXF, DWG) fits it as it is. Extensions:
    gbr ger gtl gbl gto gbo gts gbs gtp gbp gko gm1 gml, none taken before.
  • Settings migration v87: existing CAD donuts learn the extensions (custom claims stay put). Main moves fast:
    this may need renumbering.
  • Docs: a "Gerber PCB layers" section in docs/ENGINES.md, the README format table.

Not in this PR, on purpose: Excellon drill files (.drl). Their main use is showing the holes on the copper,
which needs several files of one board drawn together; that is a separate design (stacking layers), better reviewed
on its own. I'd propose it next if you want it.

Type of change

  • Bug fix
  • New feature or improvement
  • New format or engine
  • Translation
  • Documentation
  • Build, CI or dependencies

How was it tested?

  • dotnet format Filee.slnx --verify-no-changes --severity warn
  • dotnet build Filee.slnx -c Release
  • dotnet test: 63 new Gerber tests (GerberTests, GerberFormatTests) on layers generated by the tests, all
    passing; FormatFieldsTests counters updated; TracingTests knows Gerber → SVG isn't tracing. Engines: 18
    failures, the same as on main in this environment (Pandoc, FFmpeg and LibreOffice not installed, a Korean font
    missing, FolderIso632Tests); Core: 2, the tests that need a non-root user. App, filtered to profiles,
    donuts, settings, migrations, formats, fields and translations: 136 passed.
  • Compared with gerbv 2.10 (same window, same colour) on ~180 real files: tracespace's fixtures and boards
    (KiCad, Eagle) and gerbv's examples (Eagle, OrCAD, Mentor, Protel, KiCad, hand-written tests). Real boards
    render like gerbv's, apart from anti-aliasing along thin lines. Where shapes differ, the specification and
    tracespace's reference SVGs agree with Filee: single-quadrant arcs, rotated macro circles, x in macro
    expressions, LS on draws, polarity inside step and repeat, nested aperture blocks (gerbv draws nothing). Two
    files fail with a reason: an empty paste layer, and a board whose aperture definition is really damaged
    (R,0.03260.326).
  • Stress layer (a ground plane, 20 000 clear pads, 100 000 tracks): SVG in 2 s, PNG 9 s, vector PDF 10 s.
  • Tried it in the app: converted with the CLI (Linux) only; the app's CAD donut gets the extensions through the
    registry and the migration (covered by tests), not clicked through.

Not tested: the deprecated image commands IR, MI, OF, SF, AS, IJ, KO are ignored (the real files met write only
their neutral values, OFA0B0, SFA1B1, MIA0B0); Gerber X3 component attributes are ignored like the other
attributes.

Screenshots (UI changes)

No UI change. These compare Filee's output (left, Gerber → SVG → PNG) with gerbv 2.10 (right), which was given the
same window and the layer colour Filee picked.

A KiCad board, layer by layer (tracespace's clockblock): top copper, bottom copper, top silkscreen and top
solder mask, coloured from the file name (-F_Cu, -B_Cu, -F_SilkS, -F_Mask).

board-clockblock

The hard cases: a real power plane with clear thermal reliefs (gerbv's dsp.GP1), a macro thermal flashed in
clear polarity, a moiré macro, step and repeat with dark and clear objects inside, a region with an arc cut-in,
multi-quadrant arcs, and nested aperture blocks, which gerbv doesn't draw at all.

hard-cases

Checklist

  • New UI strings are in all src/Filee.App/Assets/i18n/*.json files (none: format names aren't translated)
  • New third-party code or binaries are listed in THIRD-PARTY-NOTICES.md (none added)
  • The PR does one thing (unrelated changes go in a separate PR)

🤖 Generated with Claude Code

Ugoyo and others added 2 commits November 12, 2026 21:47
Gerber files (.gbr, .gtl, .gbl, .gto, .gbo, .gts, .gbs, .gtp, .gbp, .gko,
.gm1, .gml) are drawn as SVG at their real size by a built-in reader, and the
vector engine takes them to PDF (vector), PNG, JPG, DXF and DWG.

- Apertures C/R/O/P with holes, aperture macros with variables, expressions
  and every primitive, linear and circular draws (single- and multi-quadrant),
  regions, LPD/LPC, LM/LR/LS, nested aperture blocks, step and repeat, IPNEG.
- KiCad colours on its dark background, by the layer's role (X2 file function,
  else the file name).
- Clear polarity applied as geometry with Skia path operations, so the PDF
  stays vector; past a budget, painted in the background colour.
- Defensive reading with budgets checked before allocation.
- Read only, CAD field, learnt by the CAD donut (settings schema 87).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5sc7UXmkshJ7pDMouF
- Count block flashes and step-and-repeat copies at their drawn size against
  their own budget, so a panel of a large board passes while blocks flashed
  inside blocks can't multiply without end.
- A budget for macro primitives over all aperture definitions.
- Numbers too long for their field fail as damaged files, not overflows.
- dotnet format.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5sc7UXmkshJ7pDMouF
@Ugoyo
Ugoyo requested a review from KnifeLemon as a code owner November 13, 2026 06:12
@github-actions github-actions Bot added the feature New features label Nov 13, 2026

@KnifeLemon KnifeLemon left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Thanks for this, @Ugoyo. It's a careful reader: the budgets, the reasoned InvalidDataExceptions, the gerbv/tracespace
comparison and the docs section make it easy to follow. Doing clear polarity with path operations instead of masks is
a nice touch for vector PDF. A few things should change before merge, mostly about crafted input getting past the
budgets.

  1. Block chains aren't depth-limited. MaxBlockDepth only counts lexical nesting (_frames.Count). A top-level
    %ABD10*% can flash the previous block, the next one flashes that, and so on (two codes alternating are enough,
    since CloseFrame overwrites _blockApertures[code]). About 1 M links fit in about 22 MB and pass every budget.
    With one overlapping clear object, GerberBlockFlash.Outline() -> Run.Outline() recurses 1 M levels: a
    StackOverflowException, which takes the whole app down. Without one, Svg.Skia meets a <use> chain just as deep.
    Could each GerberBlock carry a depth (1 + the deepest block it flashes) that's refused past MaxBlockDepth?
    Also, ApertureBlock doesn't count against MaxApertures, and empty %ABD10*%%AB*% / empty SR blocks pile up in
    _image.Blocks uncounted. Please count opened blocks too.
  2. Subtract's overlap search can run for hours and ignores Cancel. The budget only grows when hits.Count > 0,
    but Grid.After walks every entry of the cells it touches plus _large, and skips k <= index for free. A layer
    with one dark flash, 900 k clear flashes and then 1 M dark flashes at the same spot (rectangle aperture, about
    8 MB) does about 1e12 iterations in the counting loop, which has no ct check. Please count visited entries
    against a budget and fall back to background painting when it's exceeded, and check ct in both counting loops.
    Cell lists and _large are filled in ascending index order, so a binary search to the first k > index would
    also help. GerberMacro.Evaluate and a %...% block with many AD words could use a ct check too.
  3. UTF-8 BOM. Encoding.UTF8.GetString keeps U+FEFF, and char.IsWhiteSpace says no, so a BOM-prefixed file
    reads %FS... as a word up to the * inside FS. The % pairing then shifts and the file fails on MOMM.
    Skipping a leading U+FEFF fixes it.
  4. Extent. Only X/Y/I/J are held to 100 m. Aperture parameters, macro parameters, LS (up to 1e6) and SR
    I/J aren't, so a file can make a page thousands of km wide (the raster path then asks for a bitmap far over
    SvgRenderer's cap). A 1e308 inch parameter times 25.4 becomes Infinity in the SVG. A range check on those
    numbers, plus a final bounds check in GerberSvg.Write, would turn these into the usual reasoned error.
  5. DXF/DWG gets the background. The full-size <rect fill="#001023"> becomes a closed LWPOLYLINE 2 mm around the
    board in DXF, which gets in the way of the "board outline for the enclosure" use. The dark background is fine for
    SVG, PDF and the pictures, so keep it there, but please leave it out of the DXF/DWG route.

Smaller:

  • RoleOf maps .g1/.g2/.gl2 to inner copper (and the docs say so), but those extensions aren't registered, so
    the branch can't be reached. Either register them or drop it.
  • The drawn-segment budget counts a flash as at least 4 segments, so about 12 M placed <use> instances can pass.
    Svg.Skia builds a drawable per instance, so a separate cap on placed instances may be worth adding.
  • .gml: please leave it out for now. It's also the GIS Geography Markup Language, which would fit the geo formats
    later, and Eagle users can pick their milling layer by its other names.
  • The Excellon drill-file follow-up you mentioned is welcome, as a separate PR after this one.
  • Heads-up, nothing to do on your side: #1444 has just taken schema 87, other open PRs want the next numbers, and
    #1461 touches the same cad lists in FormatFieldsTests. We'll renumber and sort out the counts at merge time.

CI is green. Happy to look again once these are in.

Ugoyo and others added 2 commits November 13, 2026 13:08
- Block reference depth, not only lexical nesting, is limited: a chain of
  top-level blocks each flashing the previous one is refused.
- Opened aperture and SR blocks (empty ones too) count against the budget.
- A cap on shapes placed once blocks and copies are expanded (Svg.Skia makes
  a drawable per <use>), separate from the segment budget.
- The overlap search is budgeted (grid entries visited) and checks the
  cancellation token in both counting loops; a binary search skips entries at
  or before the current index instead of scanning them.
- Macro evaluation and extended-command runs check the cancellation token.
- A leading UTF-8 BOM is skipped.
- Aperture, macro, scale and step-and-repeat sizes are range-checked like
  coordinates, and the whole drawing's extent is checked before output.
- The DXF/DWG route leaves out the board background rectangle.
- Drop the unreachable .g1/.g2/.gl2 role branch, and .gml (GIS Geography
  Markup Language; Eagle's milling layer has other names).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5sc7UXmkshJ7pDMouF
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BPxh5sc7UXmkshJ7pDMouF
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature New features

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants