Skip to content

The PCB fabrication drawing

The Gerbers and the Excellon drill program are what a board house machines from; the fabrication drawing is what a board house reads. It is the human- readable sheet that sits beside the fab data: the board outline at a fitted scale, a drill map with a symbol at every drilled feature, a drill table (a keyed legend) grouping the board’s holes, vias and through-hole pad drills by size, a layer stackup table, and a fabrication notes block — all on the shared engineering frame a mechanical drawing uses.

The one rule: it reads the board, it never edits it

A PcbFabricationSheet derives everything from the layout’s own public read surface — the board outline, its holes, the placed vias, the stackup — so it cannot disagree with the board it documents (the ECAD one-declaration rule, applied to a drawing). The drill table is the sharpest form of that: its rows partition the board’s holes, vias and through-hole pad drills exactly, so Σ count equals the number of drilled features and adding a hole (or a through-hole pad) adds exactly one to its row. That is a closed-form oracle, not a picture.

The shared frame — the third consumer

A fabrication drawing is an engineering drawing, so its border and title block come from the same DrawingFrame the mechanical drawing sheet and the schematic sheet use — the three-band EngineeringTitleBlock on the SheetLayers. Given the same paper and the same title-block fields, sheet.Frame().Compute() is byte-identical to a mechanical DrawingSheet’s frame. That is the payoff of one shared frame: a drawing and its fab drawing of one board cannot draw different furniture.

A drill map, a stackup and notes

A four-layer board with two mounting holes and a couple of via sizes, drawn on A3:

// A 4-layer board (60 x 40) with two Ø3.2 mounting holes and one Ø0.6 board via.
var board = new PcbBoard(
[
new Vector2d(-30, -20), new Vector2d(30, -20),
new Vector2d(30, 20), new Vector2d(-30, 20),
],
LayerStackup.FourLayer(copper: 0.035, prepreg: 0.2, core: 1.13),
holes:
[
new BoardHole(new Vector2d(-26, -16), 3.2, BoardHoleKind.Mounting),
new BoardHole(new Vector2d(26, 16), 3.2, BoardHoleKind.Mounting),
new BoardHole(new Vector2d(0, 15), 0.6, BoardHoleKind.Via),
]);
// A through-hole header — its Ø0.9 plated pad drills join the SAME drill table as the board's
// own holes and vias (an SMD part's lands would carry no drill and add no row).
var sch = new Schematic("regulator");
sch.Add("J1", new PartDefinition("HDR_1x2", "J",
[new Pin("1", PinType.Passive), new Pin("2", PinType.Passive)],
new Footprint("HDR254", [
Pad.ThroughHole("1", new Vector2d(-1.27, 0), pad: 1.6, drill: 0.9),
Pad.ThroughHole("2", new Vector2d(1.27, 0), pad: 1.6, drill: 0.9),
])));
var layout = new PcbLayout(sch, board);
// Placed vias in two sizes — they join the drill table with the board's own holes.
layout.AddVia("GND", 8, 6, "Top", "Bottom", drill: 0.3, pad: 0.6);
layout.AddVia("GND", -8, 6, "Top", "Bottom", drill: 0.3, pad: 0.6);
layout.AddVia("GND", 0, -6, "Top", "Bottom", drill: 0.3, pad: 0.6);
layout.AddVia("VIN", 12, -10, "Top", "Bottom", drill: 0.4, pad: 0.8);
layout.Place("J1", 0, 0, rotationDegrees: 0, side: CopperSide.Top);
// The fabrication drawing — the shared engineering frame plus the fab content.
var title = new TitleBlock
{
Title = "REGULATOR PCB", DrawingNumber = "PCB-042", Author = "EngrCAD",
Revision = "A", Company = "ACME ELECTRONICS",
};
var sheet = new PcbFabricationSheet(layout, SheetFormat.A3, title);
var drawing = sheet.Compute();
// The drill table partitions the board's holes, vias AND the header's through-hole pad drills:
// Ø0.3 PTH x3, Ø0.4 PTH x1, Ø0.6 PTH x1, Ø0.9 PTH x2, Ø3.2 NPTH x2 — nine features, five sizes.
var svg = drawing.ToSvg(); // also drawing.ToDxf(), drawing.ToPdf()

The regulator PCB's fabrication drawing.

The drill table’s counts and diameters are the board’s own — the drawing is a view of the layout’s drilled features, so it cannot omit a hole nor invent one. The same Compute() feeds the SVG, DXF and PDF writers, so the three cannot disagree about the drawing.

The drill legend, and through-hole component pad drills

The drill table is a legend: its SYM column keys each size to a symbol — a letter (A, B, …) beside its glyph, the same glyph the drill map draws at every hole of that size — so a reader ties a marked hole back to its row. The glyphs come from PcbFabricationSheet’s canonical, ordered symbol set (DrillGlyphPalette: plus, saltire, square, circle, triangle, diamond, six-point asterisk, hexagon, down-triangle, pentagon), assigned by ascending diameter (non-plated before plated at equal diameter), so the symbol assignment is a deterministic function of the board. The letter is the always-distinct key; when a board carries more distinct sizes than the palette has shapes the glyph cycles and the letter distinguishes the repeat. A board with more distinct drill sizes than the A–Z alphabet holds (MaxLegendSizes = 26) is refused by name — a real board carries a handful of drill sizes.

The partition covers through-hole component pad drills too, not just the board’s own holes and vias. A through-hole pad has a drill; a surface-mount land does not — the same SMD-vs-through-hole distinction the solder-paste stencil reads off the copper model — so a placed header’s pads join the same (diameter, plated) partition (plated, PTH) while an SMD part contributes no drill row.

Fabrication requirements: the notes the geometry cannot carry

The geometry states a thickness and a copper-layer count, but a board house also needs the base material, the copper weight, the surface finish, the mask and legend colours, the IPC-6012 class, and the minimum trace and clearance — none of which the geometry carries. A PcbFabricationSpec states them, and the fabrication drawing prints them write-only-when-stated: every field is optional, a stated field prints its note, and an unstated one is simply absent — the drawing never invents a value. The spec rides in the layout as layout truth (layout.WithFabrication(...), layout.Fabrication), the same kind of thing the solder-mask / silkscreen / paste settings are, so it persists in the layout file write-only-when-stated (a layout that states none saves byte-identically to a pre-spec one).

// A 2-layer board (60 x 40 x 1.6) — the geometry says nothing about material or finish.
var board = new PcbBoard(
[
new Vector2d(-30, -20), new Vector2d(30, -20),
new Vector2d(30, 20), new Vector2d(-30, 20),
],
thickness: 1.6,
holes:
[
new BoardHole(new Vector2d(-26, -16), 3.2, BoardHoleKind.Mounting),
new BoardHole(new Vector2d(26, 16), 3.2, BoardHoleKind.Mounting),
]);
// The fabrication REQUIREMENTS the geometry cannot carry. Every field is optional — this spec
// states a material, finish, copper weight, colours, class and minimum trace/clearance, and
// leaves everything else (e.g. an impedance tolerance) to the fabricator's default.
var layout = new PcbLayout(new Schematic("sensor"), board)
.WithFabrication(new PcbFabricationSpec
{
BaseMaterial = "FR-4",
FinishedThicknessMm = 1.6,
CopperWeightOz = 1,
SurfaceFinish = PcbSurfaceFinish.Enig,
SolderMaskColour = "Blue",
SilkscreenColour = "White",
Ipc6012Class = 2,
MinTraceWidthMm = 0.15,
MinClearanceMm = 0.15,
Notes = ["50 OHM CONTROLLED IMPEDANCE ON ROUTED NETS."],
});
var title = new TitleBlock
{
Title = "SENSOR PCB", DrawingNumber = "PCB-108", Author = "EngrCAD", Company = "ACME",
};
var drawing = new PcbFabricationSheet(layout, SheetFormat.A3, title).Compute();
// The notes block now carries the stated requirements, e.g. "MATERIAL: FR-4.",
// "SURFACE FINISH: ENIG.", "COPPER WEIGHT: 1 oz (35 um).", "FABRICATE TO IPC-6012 CLASS 2." —
// each printed only because the spec states it.
var svg = drawing.ToSvg();

The sensor PCB's fabrication drawing, with its fabrication-requirements notes.

A stated finished thickness is authoritative: it is the delivered stackup thickness (copper and finish) a fabricator quotes to, so it replaces the modelled plate thickness in the finished- thickness note rather than printing a second one. With no spec, that note is the modelled thickness exactly as before — the drawing is byte-identical to one built before the spec existed.

A house standard from the catalogue

Typing the same fields for every board gets old, so StandardFabSpecs is a small catalogue of common house specs — TwoLayerFr4Hasl, TwoLayerFr4Enig, FourLayerFr4Enig, FlexPolyimideEnig — each an ordinary PcbFabricationSpec you pass to WithFabrication. It is the same verify-against-datasheet pattern as StandardHoles / SheetMaterials: ⚠ nominal transcribed figures — check them against your fabricator’s capability sheet. Because a catalogue entry is an ordinary spec, it persists and drives the fab drawing through exactly the machinery above — there is no second application path. The spec carries no layer count (that lives on the board’s stackup), so the two 2-layer entries differ only by finish (HASL vs ENIG) and the 4-layer one is a higher-reliability class-3 build; tweak any preset with a record with expression.

The per-layer plots — the sheet set beside the Gerbers

A fab package ships more than the Gerbers and the drill program: a board house also reads a plot per layer — a human-readable sheet showing one copper / solder-mask / silkscreen / solder-paste layer’s own geometry as line-work. PcbFabricationPlots.For(layout) produces that set: one PcbLayerPlot per copper layer (in stackup order), then one per declared mask, silk and paste side (write-only-when-stated, so a bare copper board plots just its copper layers).

Each plot consumes the copper model’s own regions — the same geometry the Gerber exporter builds — so a plot and its Gerber cannot disagree about what is on a layer; a copper plot draws exactly the PcbCopperModel’s features for that layer, a mask plot the mask windows, a paste plot the SMD apertures, a silk plot the line-work. Each plot rides the same shared frame (plot.Frame() is byte-identical to a fab drawing’s frame at the same paper and fields), and a bottom-side layer is mirrored — plotted viewed from the bottom, the way a fabricator looks at that side — with the plot stating its ViewSide and Mirrored.

// A small 2-layer board with an SMD resistor and a through-hole header (copper on both layers),
// with the solder mask declared so the mask sides plot too.
var sch = new Schematic("blinky");
var r = sch.Add("R1", new PartDefinition("R_0805", "R",
[new Pin("1", PinType.Passive), new Pin("2", PinType.Passive)],
new Footprint("R0805", [
Pad.Smd("1", new Vector2d(-1.0, 0), 1.2, 1.4),
Pad.Smd("2", new Vector2d(1.0, 0), 1.2, 1.4),
])), "330");
var j = sch.Add("J1", new PartDefinition("HDR_1x2", "J",
[new Pin("1", PinType.Passive), new Pin("2", PinType.Passive)],
new Footprint("HDR254", [
Pad.ThroughHole("1", new Vector2d(-1.27, 0), pad: 1.6, drill: 0.9),
Pad.ThroughHole("2", new Vector2d(1.27, 0), pad: 1.6, drill: 0.9),
])));
sch.Connect("VCC", j.Pin("1"), r.Pin("1"));
sch.Connect("SIG", r.Pin("2"), j.Pin("2"));
var board = new PcbBoard(
[
new Vector2d(-15, -10), new Vector2d(15, -10),
new Vector2d(15, 10), new Vector2d(-15, 10),
],
thickness: 1.6,
holes: [new BoardHole(new Vector2d(-12, -7), 3.0, BoardHoleKind.Mounting)]);
var layout = new PcbLayout(sch, board);
layout.Place("R1", 6, 2, rotationDegrees: 0, side: CopperSide.Top);
layout.Place("J1", -6, -2, rotationDegrees: 30, side: CopperSide.Top);
layout.WithMask(new PcbMaskSettings(Expansion: 0.05));
// Copper Top / Bottom, then mask Top / Bottom — four plots. Each names its layer and side.
var plots = PcbFabricationPlots.For(layout, SheetFormat.A4,
new TitleBlock { Title = "BLINKY PCB", DrawingNumber = "PCB-001", Company = "ACME" });
// Render the TOP COPPER plot. plots[0].Compute() also has ToDxf() and ToPdf().
var topCopper = plots[0]; // copper layers come first, in stackup order
var svg = topCopper.Compute().ToSvg();

The blinky PCB's top-copper fabrication plot.

The bottom plots are the same geometry viewed from the other side: for a board point p, bottomPlot.Compute().Project(p) maps to the same sheet Y as the top plot but a reflected X, so the two are mirror images about the sheet — the fabrication convention that a bottom layer is read looking through the board. Because a plot draws exactly its layer’s own regions, the plot’s PlottedArea equals the copper model’s total region area for that layer — the plot is the layer’s geometry, not a re-derivation.

var board = new PcbBoard(
[
new Vector2d(-30, -20), new Vector2d(30, -20),
new Vector2d(30, 20), new Vector2d(-30, 20),
],
thickness: 1.6);
// Pick a house standard — then override just what differs (a black mask here).
var spec = StandardFabSpecs.TwoLayerFr4Enig with { SolderMaskColour = "Black" };
var layout = new PcbLayout(new Schematic("sensor"), board).WithFabrication(spec);
// It reaches the fab drawing through the same PcbFabricationSheet seam — no new drawing code.
var notes = new PcbFabricationSheet(layout).Compute().Notes;
if (!notes.Contains("SURFACE FINISH: ENIG."))
throw new Exception("the catalogue spec's finish should print on the fab drawing");
if (!notes.Contains("SOLDER MASK COLOUR: BLACK."))
throw new Exception("the override should carry through");
// And it persists like any other spec — a save -> load -> save fixed point.
if (PcbLayout.Load(layout.Save()).Save() != layout.Save())
throw new Exception("a catalogue spec should round-trip like any other");
// Every catalogue entry's stated minimums conform to the IPC class it claims.
foreach (var (name, s) in StandardFabSpecs.All)
if (!DrcRuleSet.CheckSpec(s).Conforms)
throw new Exception($"{name} contradicts its own IPC class");
Console.WriteLine($"catalogue: {StandardFabSpecs.All.Count} house specs, all class-conforming");