Skip to content

4-layer boards silently export as 2-layer (no In1_Cu / In2_Cu gerbers) #78

@kcknox

Description

@kcknox

Summary

convertSoupToGerberCommands only emits F_Cu and B_Cu copper gerbers, even when the input circuit_json has pcb_trace segments on inner1 / inner2 and a pcb_board.num_layers === 4. Inner-layer trace data is silently dropped from the gerber zip — a fab building from these files produces a 2-layer board with broken connectivity, with no error or warning surfaced to the user.

Repro

Build any 4-layer tscircuit board (<board layers={4}>) where the autorouter places traces on inner1 / inner2. Confirm via circuit.json:

const innerLayers = new Set();
for (const t of circuit_json) if (t.type === 'pcb_trace')
  for (const r of t.route) if (r.layer) innerLayers.add(r.layer);
// → Set { 'top', 'inner1', 'inner2', 'bottom' }

Run bunx tsci export <file> --format gerbers --output ../board.zip and inspect the zip:

F_Cu.gbr   F_SilkScreen.gbr   F_Mask.gbr   F_Paste.gbr
B_Cu.gbr   B_SilkScreen.gbr   B_Mask.gbr   B_Paste.gbr
Edge_Cuts.gbr   drill.drl   drill_npth.drl   bom.csv   pick_and_place.csv

No In1_Cu.gbr / In2_Cu.gbr. The two inner copper layers' traces are absent from the output.

Where it's missing

Across the package, the type system, layer maps, and rendering loops all hardcode top / bottom:

  • src/gerber/convert-soup-to-gerber-commands/GerberLayerName.tsLayerToGerberCommandsMap only declares F_Cu / B_Cu / etc.
  • src/gerber/convert-soup-to-gerber-commands/getGerberLayerName.tslayerRefToGerberPrefix has only { top: "F_", bottom: "B_" }.
  • src/gerber/convert-soup-to-gerber-commands/getCommandHeaders.ts — has the // TODO inner layers comment alongside an inner1 | inner2 | inner3 | inner4 accepted-layer union, but the corresponding layerAndTypeToFileFunction entries are missing.
  • src/gerber/convert-soup-to-gerber-commands/getAllTraceWidths.ts — return shape is { top, bottom } only.
  • src/gerber/convert-soup-to-gerber-commands/index.tsglayers object, the aperture-defining loop (~line 79), and two trace/via render loops (~lines 456 and 594) are hardcoded to top/bottom.
  • src/gerber/convert-soup-to-gerber-commands/defineAperturesForLayer.tsglayer_name.startsWith("F_") ? "top" : "bottom" collapses inner layers into the bottom bucket; getAllApertureTemplateConfigsForLayer only accepts "top" | "bottom".

Proposed fix

I have a working patch on kcknox/circuit-json-to-gerber branch feature/inner-layer-gerbers that:

  1. Adds In1_Cu / In2_Cu to LayerToGerberCommandsMap.
  2. Adds inner1: "In1_" / inner2: "In2_" to layerRefToGerberPrefix.
  3. Adds inner1-copper: "Copper,L2,Inr" / inner2-copper: "Copper,L3,Inr" to layerAndTypeToFileFunction (Gerber X2 file-function attribute).
  4. Adds inner1 / inner2 keys to getAllTraceWidths return shape.
  5. Adds In1_Cu / In2_Cu headers to the glayers object and includes them in the aperture-defining loop.
  6. Generalises the two render loops to iterate ["top", "inner1", "inner2", "bottom"] (and + "edgecut" for the second), with early-continue guards so handlers for pcb_smtpad / pcb_solder_paste / pcb_silkscreen_* / pcb_hole skip inner layers (they only carry copper traces, vias, plated holes, and copper text).
  7. Generalises getApertureConfigsForLayer and defineAperturesForLayer's layer-bucket lookup to handle inner layers.

After the patch, exporting a 4-layer board produces:

F_Cu.gbr  In1_Cu.gbr  In2_Cu.gbr  B_Cu.gbr   ...

26/27 existing tests pass (the 1 failing test, generate-gerber-macrokeypad, was already failing on main before my changes — looks like a pre-existing 20s timeout / snapshot drift issue unrelated to this).

Questions before opening a PR

A few design questions I'd want maintainer input on before sending the diff:

  1. N inner layers vs just 2? I implemented inner1 + inner2 because that covers 4-layer (the common case for medium-density designs) and the existing getCommandHeaders already accepts the inner1..4 union. Should it generalise to inner1..6 (12-layer support) on day one, or is incremental fine?
  2. .FileFunction Lk numbering. I left bottom-copper as Copper,L2,Bot rather than rewriting it to L4,Bot for 4-layer boards, since the layer count isn't always known at gerber-emit time. Most fabs identify stack position from the filename, not this attribute. OK?
  3. Inner-layer file extension. Many fabs (JLCPCB, PCBWay, Eurocircuits) reject In1_Cu.gbr / In2_Cu.gbr and expect Protel-style .g1 / .g2. Should circuit-json-to-gerber keep emitting .gbr (current convention) and let tscircuit/cli's zip-write step handle the rename, or should this package own the extension mapping?

Happy to open the PR once there's a thumbs-up on direction. Branch is ready at https://github.com/kcknox/circuit-json-to-gerber/tree/feature/inner-layer-gerbers — no PR opened yet to avoid blind-PRing.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions