陈咕咕
Published © MIT

Check Gerber layers beyond their filenames

Inspect one KiCad export and two altered copies to find a missing outline and a filename that disagrees with X2 metadata.

BeginnerProtip17
Check Gerber layers beyond their filenames

Things used in this project

Software apps and online services

KiCad
KiCad
KiCad 10.0.6 was used for the native export. Optional for regenerating the source; a browser, archive utility and text editor are enough for the supplied-file exercise. No physical PCB components are required.

Story

Read more

Code

read_gerber_cases.py

Python
Read-only Python 3 check for the three supplied teaching ZIPs: file inventory, X2 metadata, stale job paths and byte-identical rename. MIT, copyright 2026 PCBA Partner; AI-assisted. Not a CAM or manufacturing validator. Run: python3 read_gerber_cases.py /path/to/extracted-package/downloads
"""Read the three teaching ZIPs without extraction or writes. Not a CAM validator.

MIT License; copyright (c) 2026 PCBA Partner. See LICENSE.
OpenAI Codex assisted in creating this fixture-specific teaching code.
"""
from pathlib import Path
import hashlib
import json
import re
import sys
import zipfile


def inspect(path):
    with zipfile.ZipFile(path) as archive:
        names = archive.namelist()
        layers, conflicts, missing = [], [], []
        for name in names:
            suffix = Path(name).suffix.lower()
            if suffix == ".gbrjob":
                job = json.loads(archive.read(name))
                missing.extend(item["Path"] for item in job["FilesAttributes"]
                               if item["Path"] not in names)
            elif suffix in {".gtl", ".gbl", ".gts", ".gbs", ".gtp", ".gbp", ".gto", ".gbo", ".gm1"}:
                text = archive.read(name).decode("utf-8")
                match = re.search(r"%TF\.FileFunction,([^*]+)\*%", text)
                function = match.group(1) if match else None
                layers.append({"name": name, "fileFunction": function})
                # Only the copper-side conventions exercised by these fixtures.
                expected = {".gtl": "Top", ".gbl": "Bot"}.get(suffix)
                if expected and function and function.startswith("Copper,"):
                    if function.split(",")[-1] != expected:
                        conflicts.append(name)
        return {"scenario": path.stem, "gerberLayers": len(layers),
                "profileFiles": [x["name"] for x in layers
                                 if x["fileFunction"] and x["fileFunction"].startswith("Profile,")],
                "copperNameMetadataConflicts": conflicts,
                "jobPathsMissingFromArchive": missing, "layerMetadata": layers}


def main():
    root = Path(sys.argv[1]) if len(sys.argv) > 1 else Path("downloads")
    cases = ["01-original-export", "02-missing-outline", "03-filename-x2-conflict"]
    observations = [inspect(root / (case + ".zip")) for case in cases]
    with zipfile.ZipFile(root / (cases[0] + ".zip")) as baseline:
        original = baseline.read("pcba-partner-file-interop-teaching-B_Cu.gbl")
    with zipfile.ZipFile(root / (cases[2] + ".zip")) as altered:
        renamed = altered.read("misleading-F_Cu.gtl")
    same = original == renamed
    expected = [(9, 1, 0, 0), (8, 0, 0, 1), (9, 1, 1, 1)]
    actual = [(x["gerberLayers"], len(x["profileFiles"]),
               len(x["copperNameMetadataConflicts"]), len(x["jobPathsMissingFromArchive"]))
              for x in observations]
    result = {"observations": observations, "renamedFileBytesUnchanged": same,
              "bottomCopperSha256": hashlib.sha256(original).hexdigest(),
              "fixtureExpectationsMatch": actual == expected and same,
              "scope": "File inventory and metadata only; not rendering or manufacturing validation."}
    print(json.dumps(result, indent=2))
    return 0 if result["fixtureExpectationsMatch"] else 1


if __name__ == "__main__":
    raise SystemExit(main())

LICENSE — original teaching code

Plain text
MIT license for the original PCBA Partner teaching source and read_gerber_cases.py. Third-party software and trademarks retain their own rights; this does not relicense the entire viewer.
MIT License

Copyright (c) 2026 PCBA Partner

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Credits

陈咕咕
1 project • 0 followers
PCBA Partner shares practical DFM, BOM, assembly and test-readiness lessons for prototype and pilot hardware builds.

Comments