gerberdiff 0.29.0__tar.gz → 0.30.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (134) hide show
  1. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.github/ISSUE_TEMPLATE/config.yml +1 -1
  2. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.github/workflows/ci.yml +3 -3
  3. gerberdiff-0.30.0/.github/workflows/docs.yml +71 -0
  4. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.github/workflows/release.yml +2 -2
  5. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/CHANGELOG.md +220 -16
  6. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/CONTRIBUTING.md +11 -7
  7. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/PKG-INFO +12 -9
  8. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/README.md +4 -1
  9. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/SECURITY.md +5 -3
  10. gerberdiff-0.30.0/docs/changelog.md +1 -0
  11. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/docs/cli.md +17 -1
  12. gerberdiff-0.30.0/docs/contributing.md +1 -0
  13. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/docs/geometry-diff.md +7 -1
  14. gerberdiff-0.30.0/docs/index.md +78 -0
  15. gerberdiff-0.30.0/docs/javascripts/mathjax.js +20 -0
  16. gerberdiff-0.30.0/docs/reference/geometry.md +24 -0
  17. gerberdiff-0.30.0/docs/reference/package.md +32 -0
  18. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/docs/schema.md +17 -4
  19. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/__init__.py +10 -1
  20. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/cli.py +59 -19
  21. gerberdiff-0.30.0/gerberdiff/diff/__init__.py +1 -0
  22. gerberdiff-0.30.0/gerberdiff/export/__init__.py +1 -0
  23. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/export/json_report.py +7 -1
  24. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/driver.py +8 -0
  25. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/expand.py +15 -1
  26. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/layer_geometry.py +50 -3
  27. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/types.py +49 -0
  28. gerberdiff-0.30.0/gerberdiff/parse/__init__.py +1 -0
  29. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/parse/gerber_state.py +27 -2
  30. gerberdiff-0.30.0/gerberdiff/render/__init__.py +1 -0
  31. gerberdiff-0.30.0/mkdocs.yml +87 -0
  32. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/pyproject.toml +17 -8
  33. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_cli_diff.py +24 -1
  34. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_cli_geomdiff.py +33 -4
  35. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_cli_render.py +1 -1
  36. gerberdiff-0.30.0/tests/test_geometry_edge_cases.py +385 -0
  37. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_report.py +6 -2
  38. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_gerber_state.py +68 -2
  39. gerberdiff-0.30.0/tests/test_indeterminate.py +178 -0
  40. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/uv.lock +552 -1
  41. gerberdiff-0.29.0/gerberdiff/export/__init__.py +0 -0
  42. gerberdiff-0.29.0/gerberdiff/parse/__init__.py +0 -0
  43. gerberdiff-0.29.0/gerberdiff/render/__init__.py +0 -0
  44. gerberdiff-0.29.0/tests/__init__.py +0 -0
  45. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.editorconfig +0 -0
  46. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
  47. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
  48. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  49. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.github/dependabot.yml +0 -0
  50. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.gitignore +0 -0
  51. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/.pre-commit-config.yaml +0 -0
  52. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/LICENSE +0 -0
  53. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/docs/api.md +0 -0
  54. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/docs/architecture.md +0 -0
  55. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/diff/diff_engine.py +0 -0
  56. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/diff/layer_matcher.py +0 -0
  57. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/export/png_export.py +0 -0
  58. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/export/svg_export.py +0 -0
  59. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/__init__.py +0 -0
  60. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/attribute.py +0 -0
  61. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/geom_diff.py +0 -0
  62. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/macro_geom.py +0 -0
  63. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/geometry/primitives.py +0 -0
  64. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/parse/arc_math.py +0 -0
  65. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/parse/excellon_parser.py +0 -0
  66. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/parse/gerber_parser.py +0 -0
  67. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/parse/macro_parser.py +0 -0
  68. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/parse/tokenizer.py +0 -0
  69. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/py.typed +0 -0
  70. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/render/compiled_render.py +0 -0
  71. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/render/draw_ops.py +0 -0
  72. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/render/macro_renderer.py +0 -0
  73. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/render/renderer.py +0 -0
  74. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/render/viewport.py +0 -0
  75. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/gerberdiff/types.py +0 -0
  76. {gerberdiff-0.29.0/gerberdiff/diff → gerberdiff-0.30.0/tests}/__init__.py +0 -0
  77. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/cairo_support.py +0 -0
  78. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/conftest.py +0 -0
  79. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/drill-inch-tz.drl +0 -0
  80. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/drill-metric-lz.drl +0 -0
  81. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-B.Cu.gbr +0 -0
  82. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-B.Mask.gbr +0 -0
  83. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-B.Paste.gbr +0 -0
  84. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-B.SilkS.gbr +0 -0
  85. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-Edge.Cuts.gbr +0 -0
  86. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-F.Cu.gbr +0 -0
  87. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-F.Mask.gbr +0 -0
  88. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-F.Paste.gbr +0 -0
  89. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-F.SilkS.gbr +0 -0
  90. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-In1.Cu.gbr +0 -0
  91. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-In2.Cu.gbr +0 -0
  92. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-In3.Cu.gbr +0 -0
  93. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-In4.Cu.gbr +0 -0
  94. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-NPTH.drl +0 -0
  95. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-after/A64-OlinuXino-PTH.drl +0 -0
  96. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-B.Cu.gbr +0 -0
  97. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-B.Mask.gbr +0 -0
  98. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-B.Paste.gbr +0 -0
  99. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-B.SilkS.gbr +0 -0
  100. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-Edge.Cuts.gbr +0 -0
  101. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-F.Cu.gbr +0 -0
  102. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-F.Mask.gbr +0 -0
  103. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-F.Paste.gbr +0 -0
  104. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-F.SilkS.gbr +0 -0
  105. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-In1.Cu.gbr +0 -0
  106. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-In2.Cu.gbr +0 -0
  107. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-In3.Cu.gbr +0 -0
  108. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-In4.Cu.gbr +0 -0
  109. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-NPTH.drl +0 -0
  110. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/fixtures/gerbers-before/A64-OlinuXino-PTH.drl +0 -0
  111. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_arc_math.py +0 -0
  112. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_block_aperture.py +0 -0
  113. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_cli_parse.py +0 -0
  114. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_diff_engine.py +0 -0
  115. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_draw_ops.py +0 -0
  116. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_excellon_parser.py +0 -0
  117. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_attribute.py +0 -0
  118. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_booldiff.py +0 -0
  119. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_expand.py +0 -0
  120. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_layer.py +0 -0
  121. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_macro.py +0 -0
  122. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_oracle.py +0 -0
  123. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_geometry_primitives.py +0 -0
  124. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_gerber_parser.py +0 -0
  125. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_json_report.py +0 -0
  126. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_layer_matcher.py +0 -0
  127. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_macro_parser.py +0 -0
  128. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_macro_renderer.py +0 -0
  129. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_png_export.py +0 -0
  130. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_renderer.py +0 -0
  131. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_scaffold.py +0 -0
  132. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_tokenizer.py +0 -0
  133. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_types.py +0 -0
  134. {gerberdiff-0.29.0 → gerberdiff-0.30.0}/tests/test_viewport.py +0 -0
@@ -1,5 +1,5 @@
1
1
  blank_issues_enabled: false
2
2
  contact_links:
3
3
  - name: Question / discussion
4
- url: https://github.com/CameronBrooks11/gerberdiff/discussions
4
+ url: https://github.com/orgs/heibench/discussions
5
5
  about: Ask questions and discuss ideas in GitHub Discussions
@@ -14,7 +14,7 @@ jobs:
14
14
  name: Lint
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
- - uses: actions/checkout@v6
17
+ - uses: actions/checkout@v7
18
18
  - uses: astral-sh/setup-uv@v7
19
19
  with:
20
20
  enable-cache: true
@@ -32,7 +32,7 @@ jobs:
32
32
  name: Type check
33
33
  runs-on: ubuntu-latest
34
34
  steps:
35
- - uses: actions/checkout@v6
35
+ - uses: actions/checkout@v7
36
36
  - uses: astral-sh/setup-uv@v7
37
37
  with:
38
38
  enable-cache: true
@@ -51,7 +51,7 @@ jobs:
51
51
  - python-version: "3.11"
52
52
  os: windows-latest
53
53
  steps:
54
- - uses: actions/checkout@v6
54
+ - uses: actions/checkout@v7
55
55
  - uses: astral-sh/setup-uv@v7
56
56
  with:
57
57
  enable-cache: true
@@ -0,0 +1,71 @@
1
+ name: Docs
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ paths:
7
+ - "docs/**"
8
+ - "mkdocs.yml"
9
+ - "gerberdiff/**"
10
+ - "CHANGELOG.md"
11
+ - "CONTRIBUTING.md"
12
+ - ".github/workflows/docs.yml"
13
+ # The site is built with `uv sync --group docs`, so what ships is decided
14
+ # by the lock, not just by the sources above. Without these two paths a
15
+ # dependency fix lands on main and the deployed site keeps serving the old
16
+ # build -- which is how mkdocs-material 9.7.6 stayed live on the published
17
+ # docs after GHSA-xvg9-69gf-fjrf was already patched in the lock.
18
+ - "uv.lock"
19
+ - "pyproject.toml"
20
+ pull_request:
21
+ branches: [main]
22
+ paths:
23
+ - "docs/**"
24
+ - "mkdocs.yml"
25
+ - "gerberdiff/**"
26
+ - "CHANGELOG.md"
27
+ - "CONTRIBUTING.md"
28
+ - ".github/workflows/docs.yml"
29
+ - "uv.lock"
30
+ - "pyproject.toml"
31
+ # Lets the site be rebuilt without inventing a commit -- there was previously
32
+ # no way to redeploy at all.
33
+ workflow_dispatch:
34
+
35
+ permissions:
36
+ contents: read
37
+
38
+ concurrency:
39
+ group: docs-${{ github.ref }}
40
+ cancel-in-progress: true
41
+
42
+ jobs:
43
+ build:
44
+ name: Build site
45
+ runs-on: ubuntu-latest
46
+ steps:
47
+ - uses: actions/checkout@v7
48
+ - uses: astral-sh/setup-uv@v7
49
+ with:
50
+ enable-cache: true
51
+ - run: uv sync --group docs
52
+ - run: uv run mkdocs build --strict
53
+ - uses: actions/upload-pages-artifact@v5
54
+ if: github.event_name == 'push'
55
+ with:
56
+ path: site/
57
+
58
+ deploy:
59
+ name: Deploy to GitHub Pages
60
+ if: github.event_name == 'push'
61
+ needs: build
62
+ runs-on: ubuntu-latest
63
+ permissions:
64
+ pages: write
65
+ id-token: write
66
+ environment:
67
+ name: github-pages
68
+ url: ${{ steps.deployment.outputs.page_url }}
69
+ steps:
70
+ - id: deployment
71
+ uses: actions/deploy-pages@v5
@@ -14,7 +14,7 @@ jobs:
14
14
  name: Build distribution
15
15
  runs-on: ubuntu-latest
16
16
  steps:
17
- - uses: actions/checkout@v6
17
+ - uses: actions/checkout@v7
18
18
  - uses: astral-sh/setup-uv@v7
19
19
  with:
20
20
  enable-cache: true
@@ -41,7 +41,7 @@ jobs:
41
41
  needs: build
42
42
  runs-on: ubuntu-latest
43
43
  steps:
44
- - uses: actions/checkout@v6
44
+ - uses: actions/checkout@v7
45
45
  - uses: actions/download-artifact@v8
46
46
  with:
47
47
  name: dist
@@ -7,6 +7,91 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.30.0] - 2026-09-06
11
+
12
+ ### Changed
13
+
14
+ - **BREAKING -- the exit-code contract now matches partspec and netspec**
15
+ ([A3]). `0` no differences, `1` differences (with `--fail-on-diff`), `2` the
16
+ comparison could not be completed, `4` an input could not be read or parsed,
17
+ `64` usage (`EX_USAGE`). Previously `2` meant a parse error and `1` doubled as
18
+ both "differences found" and "could not write the output file". A gate written
19
+ `[ $? -eq 0 ]` is unaffected; one that treated `2` as a parse failure must now
20
+ read `4`.
21
+ - **Geometry report schema `version` 2 -> 3**, adding `summary.outcome`,
22
+ `summary.unrepresented` and a per-layer `unrepresented`. Additive: every v2
23
+ field keeps its meaning.
24
+
25
+ ### Added
26
+
27
+ - **`geomdiff` has a third outcome, and says what it could not model** ([A3]) --
28
+ `identical` | `different` | `indeterminate`. A stroke drawn with a macro or
29
+ block aperture is not modelled by the geometry engine, and used to vanish: a
30
+ board with an added trace reported `0 changes` at exit `0`, with JSON
31
+ byte-identical to comparing a board against a copy of itself, while the raster
32
+ engine reported the change. The two engines answered the same question
33
+ differently and the geometry one gave the dangerous answer. Such operations are
34
+ now counted in `summary.unrepresented`, named on stderr, and make the outcome
35
+ `indeterminate` at exit `2` -- which does not wait for `--fail-on-diff`, since
36
+ that flag chooses whether a *difference* fails, not whether the tool could look.
37
+ `different` outranks `indeterminate`, so one unmodellable stroke cannot mask a
38
+ trace that moved.
39
+
40
+ ### Fixed
41
+
42
+ - **Draw operations using an undefined aperture are no longer dropped in
43
+ silence** ([#17]) -- selecting a D-code that was never defined (or drawing
44
+ before any `Dnn` selection at all) left the flash or stroke carrying an
45
+ aperture index with no definition behind it. Every consumer treated that
46
+ as nothing to draw: both geometry emit helpers returned early and the
47
+ raster renderer drew nothing, with no diagnostic at any severity. `diff`
48
+ and `geomdiff` then reported `0 changes` at exit `0`, and the JSON report
49
+ was byte-identical to a comparison of two genuinely identical boards -- so
50
+ a real fabrication change could pass a `--fail-on-diff` gate invisibly.
51
+ The parser now emits an `Error` diagnostic at the offending draw
52
+ operation, which the existing promotion path turns into exit `2` for
53
+ `parse`, `render`, `diff` and `geomdiff` alike. D02 moves and G36/G37
54
+ region contours consume no aperture and are unaffected.
55
+
56
+ - **Degenerate region contours no longer leak line geometry** -- a G36/G37
57
+ contour whose points are collinear produced a zero-area `MultiLineString`
58
+ from `make_valid` that flowed into the geometry engine; region expansion
59
+ now keeps only polygonal parts.
60
+
61
+ ### Changed
62
+
63
+ - Local coverage floor aligned with the CI gate (`fail_under` 85 -> 90;
64
+ actual coverage is ~94%).
65
+ - Stale documentation corrected: the overlay-PNG colour table no longer
66
+ lists the removed "yellow" class; `SECURITY.md` supported versions
67
+ updated from 0.14.x to 0.29.x; `CONTRIBUTING.md` blesses
68
+ conventional-commit prefixes (matching recent history).
69
+ - Package `__init__` modules for `parse`, `render`, `diff`, and `export`
70
+ gained one-line docstrings.
71
+
72
+ ### Added
73
+
74
+ - **Documentation site** at <https://heibench.github.io/gerberdiff/>
75
+ -- MkDocs + Material with mkdocstrings API reference, MathJax for the
76
+ LaTeX notation already used in the docs, and Changelog/Contributing
77
+ included via snippets. Built strictly and deployed to GitHub Pages by
78
+ `.github/workflows/docs.yml` on every push to `main` that touches docs
79
+ or the package.
80
+
81
+ - Edge-case test suite for the geometry engine's guard paths: block
82
+ nesting depth limit, invalid layer indices, zero-dimension apertures,
83
+ degenerate regions and strokes, macro flash dispatch, the
84
+ equal-geometry attribution guard, and driver diagnostic forwarding.
85
+
86
+ ## [0.29.1] - 2026-06-13
87
+
88
+ ### Changed
89
+
90
+ - **Package metadata reflects both engines** -- the PyPI `description`
91
+ and the CLI group help text described only the raster engine; both now
92
+ cover the raster overlay and geometry diff pipelines. Added `geometry`
93
+ and `shapely` to the package keywords. No behaviour changes.
94
+
10
95
  ## [0.29.0] - 2026-06-12
11
96
 
12
97
  ### Added
@@ -77,6 +162,103 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77
162
  Raster-engine tests skip cleanly when cairo is unavailable, which
78
163
  fixes the previously failing Windows CI job.
79
164
 
165
+ - **Project renamed from `gerberdelta` to `gerberdiff`** (2026-04-25,
166
+ unreleased at the time). 0.29.0 is the first published release under
167
+ the new name; no release was ever made as `gerberdelta`. The rename
168
+ window also included: docs reorganised to lowercase-kebab filenames
169
+ with split CLI/API references, an ASCII-only character policy sweep,
170
+ LaTeX math notation for equations in docs, and strict mypy
171
+ annotations across the test suite.
172
+
173
+ ## [0.28.0] - 2026-04-25
174
+
175
+ ### Added
176
+
177
+ - **Render/diff regression coverage** -- 411 lines of new tests across
178
+ the renderer, draw-ops, diff engine, and parser: incremental (G91)
179
+ coordinates, step-and-repeat tiling, layer transforms (LM/LR/LS),
180
+ clear-polarity compositing, region merge cascade, polygon aperture
181
+ rotation, macro `unit_scale`, `alignment_offset`, and pixel-level
182
+ render verification. No production code changes.
183
+
184
+ ## [0.27.0] - 2026-04-25
185
+
186
+ ### Changed
187
+
188
+ - **`CompiledRender` cached per `BlockAperture`** -- repeated flashes of
189
+ the same block aperture no longer recompile its draw-op groups on
190
+ every flash. The cache is keyed by `id(block_ap)` and evicted by a
191
+ `weakref` finalizer when the aperture is garbage-collected, so stale
192
+ ids cannot produce false hits.
193
+
194
+ ## [0.26.0] - 2026-04-25
195
+
196
+ ### Fixed
197
+
198
+ - **Rect/obround stroke width** -- D01 strokes with rectangle or obround
199
+ apertures are drawn with line width `max(width, height)` instead of
200
+ `min(width, height)`, so traces whose long axis aligns with the stroke
201
+ direction (the common case) are no longer under-stroked; documented as
202
+ an approximation pending geometry-aware rendering.
203
+ - **Macro evaluation failures no longer abort the render** -- a macro
204
+ aperture whose primitive evaluation raises is skipped with a
205
+ `UserWarning` instead of crashing the whole render pass.
206
+
207
+ ## [0.25.0] - 2026-04-25
208
+
209
+ ### Fixed
210
+
211
+ - **Parser correctness sweep** --
212
+ - arc bounding boxes account for axis-extrema crossings (0/90/180/270
213
+ degrees), not just the endpoints;
214
+ - step-and-repeat tiles expand the image bounding box and respect
215
+ layer polarity;
216
+ - block aperture (`%AB%`) parsing isolates state correctly (nested
217
+ apertures/layers no longer leak into the parent image);
218
+ - `_apply_format` no longer truncates coordinates with more integer
219
+ digits than the format statement declares;
220
+ - referencing an unknown macro in an aperture definition is an
221
+ `Error`-severity diagnostic (was silently tolerated).
222
+
223
+ ## [0.24.0] - 2026-04-25
224
+
225
+ ### Changed
226
+
227
+ - **`DiffResult.has_changes` is a computed property** -- derived from the
228
+ layer results instead of stored at construction, so it cannot drift
229
+ from the data.
230
+ - **Excellon parser refactored** -- the nonlocal-closure state pattern is
231
+ replaced with explicit local parser state; behaviour unchanged.
232
+
233
+ ## [0.23.0] - 2026-04-25
234
+
235
+ ### Added
236
+
237
+ - **`compute_full_diff`** -- directory-vs-directory diff as a single
238
+ public API call (parse, match, diff, assemble `DiffResult`), with
239
+ `overlay_callback` and `on_diagnostic` hooks.
240
+ - **All IR and result types exported** from the top-level package with
241
+ an `__all__` list; README gains API examples.
242
+
243
+ ### Changed
244
+
245
+ - `diff_cmd` rewritten on top of `compute_full_diff` (the CLI no longer
246
+ duplicates the orchestration logic).
247
+
248
+ ## [0.22.0] - 2026-04-25
249
+
250
+ ### Added
251
+
252
+ - **`docs/schema.md`** -- canonical JSON report schema documentation.
253
+ - **CI hardening** -- coverage gate (`--cov-fail-under=90`), a Windows
254
+ test job in the matrix, and a non-ASCII character check.
255
+
256
+ ### Changed
257
+
258
+ - **`EXCELLON_SUFFIXES` is public** -- renamed from `_EXCELLON_SUFFIXES`
259
+ in `diff/layer_matcher.py`; it is part of the de-facto API used by the
260
+ CLI and downstream callers.
261
+
80
262
  ## [0.21.0] - 2026-04-25
81
263
 
82
264
  ### Changed
@@ -242,6 +424,10 @@ saved_bbox, saved_layer_idx)` with a named `_BlockFrame` dataclass so that
242
424
 
243
425
  ### Changed
244
426
 
427
+ - **License changed from AGPL-3.0 to Apache-2.0** so the tool can be
428
+ used commercially without copyleft obligations. (Landed immediately
429
+ before this version's other changes; recorded here for completeness.)
430
+
245
431
  - **Domain model rename** -- `Net` renamed to `DrawOp` and `NetState` renamed to `CoordState`
246
432
  throughout the codebase. The term "net" belongs to EDA net-list semantics; the IR types
247
433
  represent drawing primitives and coordinate-system snapshots, not electrical nets.
@@ -529,19 +715,37 @@ merge_tolerance) -> SingleLayerDiff`.
529
715
  - mypy `strict=true`, `warn_unused_ignores=true`, `cairocffi.*` override for missing stubs.
530
716
  - 2 smoke tests in `tests/test_scaffold.py`.
531
717
 
532
- [Unreleased]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.29.0...HEAD
533
- [0.29.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.21.0...v0.29.0
534
- [0.14.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.13.0...v0.14.0
535
- [0.13.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.12.0...v0.13.0
536
- [0.12.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.11.0...v0.12.0
537
- [0.11.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.10.0...v0.11.0
538
- [0.10.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.9.0...v0.10.0
539
- [0.9.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.8.0...v0.9.0
540
- [0.8.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.7.0...v0.8.0
541
- [0.7.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.6.0...v0.7.0
542
- [0.6.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.5.0...v0.6.0
543
- [0.5.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.4.0...v0.5.0
544
- [0.4.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.3.0...v0.4.0
545
- [0.3.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.2.0...v0.3.0
546
- [0.2.0]: https://github.com/CameronBrooks11/gerberdiff/compare/v0.1.0...v0.2.0
547
- [0.1.0]: https://github.com/CameronBrooks11/gerberdiff/releases/tag/v0.1.0
718
+ [#17]: https://github.com/heibench/gerberdiff/issues/17
719
+ [A3]: https://heibench.com/adjudications.html
720
+ [Unreleased]: https://github.com/heibench/gerberdiff/compare/v0.30.0...HEAD
721
+ [0.30.0]: https://github.com/heibench/gerberdiff/compare/v0.29.1...v0.30.0
722
+ [0.29.1]: https://github.com/heibench/gerberdiff/compare/v0.29.0...v0.29.1
723
+ [0.29.0]: https://github.com/heibench/gerberdiff/compare/9ffd4c8f...v0.29.0
724
+ [0.28.0]: https://github.com/heibench/gerberdiff/compare/b6b6b98d...9ffd4c8f
725
+ [0.27.0]: https://github.com/heibench/gerberdiff/compare/963eb957...b6b6b98d
726
+ [0.26.0]: https://github.com/heibench/gerberdiff/compare/6162e435...963eb957
727
+ [0.25.0]: https://github.com/heibench/gerberdiff/compare/e2519ec8...6162e435
728
+ [0.24.0]: https://github.com/heibench/gerberdiff/compare/91c154d4...e2519ec8
729
+ [0.23.0]: https://github.com/heibench/gerberdiff/compare/458734f9...91c154d4
730
+ [0.22.0]: https://github.com/heibench/gerberdiff/compare/1c96a7b8...458734f9
731
+ [0.21.0]: https://github.com/heibench/gerberdiff/compare/4d1201fe...1c96a7b8
732
+ [0.20.0]: https://github.com/heibench/gerberdiff/compare/ba9a5015...4d1201fe
733
+ [0.19.0]: https://github.com/heibench/gerberdiff/compare/c502171a...ba9a5015
734
+ [0.18.0]: https://github.com/heibench/gerberdiff/compare/10f8f392...c502171a
735
+ [0.17.0]: https://github.com/heibench/gerberdiff/compare/b04813ea...10f8f392
736
+ [0.16.0]: https://github.com/heibench/gerberdiff/compare/3044033b...b04813ea
737
+ [0.15.0]: https://github.com/heibench/gerberdiff/compare/9b4e3401...3044033b
738
+ [0.14.0]: https://github.com/heibench/gerberdiff/compare/1a399ea2...9b4e3401
739
+ [0.13.0]: https://github.com/heibench/gerberdiff/compare/c0672ea8...1a399ea2
740
+ [0.12.0]: https://github.com/heibench/gerberdiff/compare/1bbfb235...c0672ea8
741
+ [0.11.0]: https://github.com/heibench/gerberdiff/compare/2d1573ad...1bbfb235
742
+ [0.10.0]: https://github.com/heibench/gerberdiff/compare/6691b195...2d1573ad
743
+ [0.9.0]: https://github.com/heibench/gerberdiff/compare/2e944edc...6691b195
744
+ [0.8.0]: https://github.com/heibench/gerberdiff/compare/46b463de...2e944edc
745
+ [0.7.0]: https://github.com/heibench/gerberdiff/compare/5b9212fa...46b463de
746
+ [0.6.0]: https://github.com/heibench/gerberdiff/compare/e12ff13f...5b9212fa
747
+ [0.5.0]: https://github.com/heibench/gerberdiff/compare/6330d0cb...e12ff13f
748
+ [0.4.0]: https://github.com/heibench/gerberdiff/compare/f63a51a3...6330d0cb
749
+ [0.3.0]: https://github.com/heibench/gerberdiff/compare/3f1d1909...f63a51a3
750
+ [0.2.0]: https://github.com/heibench/gerberdiff/compare/674251dd...3f1d1909
751
+ [0.1.0]: https://github.com/heibench/gerberdiff/compare/2eeb692...674251dd
@@ -7,7 +7,7 @@ Thank you for your interest in contributing to **gerberdiff**!
7
7
  Requires Python >= 3.11 and [uv](https://docs.astral.sh/uv/).
8
8
 
9
9
  ```sh
10
- git clone https://github.com/CameronBrooks11/gerberdiff.git
10
+ git clone https://github.com/heibench/gerberdiff.git
11
11
  cd gerberdiff
12
12
  uv sync --dev
13
13
  ```
@@ -36,8 +36,11 @@ All three must pass with no errors before a PR will be merged. CI enforces this
36
36
 
37
37
  ## Commit messages
38
38
 
39
- - Single sentence, imperative mood, no trailing period
40
- - No phase or ticket prefix required (e.g. `Add viewport clamping for negative-Y boards`)
39
+ - Single-sentence subject, imperative mood, no trailing period
40
+ - [Conventional-commit](https://www.conventionalcommits.org/) type prefixes
41
+ (`feat:`, `fix:`, `docs:`, `test:`, `perf:`, `chore:`, `ci:`) are
42
+ encouraged and used throughout recent history
43
+ (e.g. `feat(geometry): add geometry diff engine core`)
41
44
 
42
45
  ## Pull request checklist
43
46
 
@@ -62,7 +65,7 @@ many fonts and meaningless to grep.
62
65
  **Banned characters and their ASCII replacements:**
63
66
 
64
67
  | Character | Codepoint | Use instead |
65
- | ----------------- | --------- | ----------- | ------ |
68
+ | ----------------- | --------- | ----------- |
66
69
  | em dash | U+2014 | `--` |
67
70
  | en dash | U+2013 | `-` |
68
71
  | ellipsis | U+2026 | `...` |
@@ -75,8 +78,8 @@ many fonts and meaningless to grep.
75
78
  | almost-equal | U+2248 | `~=` |
76
79
  | plus-minus | U+00B1 | `+/-` |
77
80
  | degree | U+00B0 | `deg` |
78
- | superscript 2 | U+00B2 | `^2` |
79
- | box-drawing chars | U+2500+ | `-`, ` | `, `+` |
81
+ | superscript 2 | U+00B2 | `^2` |
82
+ | box-drawing chars | U+2500+ | `-`, `\|`, `+` |
80
83
 
81
84
  To check a branch before committing:
82
85
 
@@ -106,4 +109,5 @@ EOF
106
109
  ## License
107
110
 
108
111
  By contributing you agree that your contributions will be licensed under the
109
- [Apache-2.0](LICENSE) licence.
112
+ [Apache-2.0](https://github.com/heibench/gerberdiff/blob/main/LICENSE)
113
+ licence.
@@ -1,15 +1,15 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.5
2
2
  Name: gerberdiff
3
- Version: 0.29.0
4
- Summary: Visual raster diff tool for Gerber/Excellon PCB design files
5
- Project-URL: Homepage, https://github.com/CameronBrooks11/gerberdiff
6
- Project-URL: Repository, https://github.com/CameronBrooks11/gerberdiff
7
- Project-URL: Changelog, https://github.com/CameronBrooks11/gerberdiff/blob/main/CHANGELOG.md
8
- Project-URL: Issues, https://github.com/CameronBrooks11/gerberdiff/issues
3
+ Version: 0.30.0
4
+ Summary: Diff tool for Gerber/Excellon PCB files: visual raster overlays and attributed vector-geometry change analysis
5
+ Project-URL: Homepage, https://github.com/heibench/gerberdiff
6
+ Project-URL: Repository, https://github.com/heibench/gerberdiff
7
+ Project-URL: Changelog, https://github.com/heibench/gerberdiff/blob/main/CHANGELOG.md
8
+ Project-URL: Issues, https://github.com/heibench/gerberdiff/issues
9
9
  Author-email: Cameron Brooks <cameronbrooks11@gmail.com>
10
10
  License-Expression: Apache-2.0
11
11
  License-File: LICENSE
12
- Keywords: diff,eda,excellon,gerber,grbl,kicad,pcb
12
+ Keywords: diff,eda,excellon,geometry,gerber,grbl,kicad,pcb,shapely
13
13
  Classifier: Development Status :: 4 - Beta
14
14
  Classifier: Intended Audience :: Developers
15
15
  Classifier: Intended Audience :: Manufacturing
@@ -33,7 +33,8 @@ Description-Content-Type: text/markdown
33
33
 
34
34
  # gerberdiff
35
35
 
36
- [![CI](https://github.com/CameronBrooks11/gerberdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/CameronBrooks11/gerberdiff/actions/workflows/ci.yml)
36
+ [![CI](https://github.com/heibench/gerberdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/heibench/gerberdiff/actions/workflows/ci.yml)
37
+ [![Docs](https://github.com/heibench/gerberdiff/actions/workflows/docs.yml/badge.svg)](https://heibench.com/gerberdiff/)
37
38
  [![PyPI](https://img.shields.io/pypi/v/gerberdiff)](https://pypi.org/project/gerberdiff/)
38
39
  [![Python](https://img.shields.io/pypi/pyversions/gerberdiff)](https://pypi.org/project/gerberdiff/)
39
40
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
@@ -82,6 +83,8 @@ for layer in result.layers:
82
83
 
83
84
  ## Docs
84
85
 
86
+ Full documentation site: **<https://heibench.com/gerberdiff/>**
87
+
85
88
  | Topic | File |
86
89
  | -------------------- | -------------------------------------------- |
87
90
  | CLI reference | [docs/cli.md](docs/cli.md) |
@@ -1,6 +1,7 @@
1
1
  # gerberdiff
2
2
 
3
- [![CI](https://github.com/CameronBrooks11/gerberdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/CameronBrooks11/gerberdiff/actions/workflows/ci.yml)
3
+ [![CI](https://github.com/heibench/gerberdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/heibench/gerberdiff/actions/workflows/ci.yml)
4
+ [![Docs](https://github.com/heibench/gerberdiff/actions/workflows/docs.yml/badge.svg)](https://heibench.com/gerberdiff/)
4
5
  [![PyPI](https://img.shields.io/pypi/v/gerberdiff)](https://pypi.org/project/gerberdiff/)
5
6
  [![Python](https://img.shields.io/pypi/pyversions/gerberdiff)](https://pypi.org/project/gerberdiff/)
6
7
  [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
@@ -49,6 +50,8 @@ for layer in result.layers:
49
50
 
50
51
  ## Docs
51
52
 
53
+ Full documentation site: **<https://heibench.com/gerberdiff/>**
54
+
52
55
  | Topic | File |
53
56
  | -------------------- | -------------------------------------------- |
54
57
  | CLI reference | [docs/cli.md](docs/cli.md) |
@@ -2,17 +2,19 @@
2
2
 
3
3
  ## Supported versions
4
4
 
5
+ Only the latest released minor version is supported.
6
+
5
7
  | Version | Supported |
6
8
  | ------- | --------- |
7
- | 0.14.x | Yes |
8
- | < 0.14 | No |
9
+ | 0.29.x | Yes |
10
+ | < 0.29 | No |
9
11
 
10
12
  ## Reporting a vulnerability
11
13
 
12
14
  **Do not open a public issue for security vulnerabilities.**
13
15
 
14
16
  Please report them via
15
- [GitHub private security advisories](https://github.com/CameronBrooks11/gerberdiff/security/advisories/new).
17
+ [GitHub private security advisories](https://github.com/heibench/gerberdiff/security/advisories/new).
16
18
 
17
19
  Include:
18
20
 
@@ -0,0 +1 @@
1
+ --8<-- "CHANGELOG.md"
@@ -1,5 +1,22 @@
1
1
  # CLI reference
2
2
 
3
+
4
+ ## Exit codes
5
+
6
+ Shared across the org's tools, so a script can branch on them the same way for
7
+ `gerberdiff`, `partspec` and `netspec`.
8
+
9
+ | code | meaning |
10
+ | ---- | -------- |
11
+ | `0` | no differences, and everything was modelled |
12
+ | `1` | differences found (with `--fail-on-diff`) |
13
+ | `2` | part of the comparison could not be made -- **not** a statement about the boards |
14
+ | `4` | an input could not be read or parsed -- also not a statement about the boards |
15
+ | `64` | usage (`EX_USAGE`): bad arguments, or an output file that exists without `--overwrite` |
16
+
17
+ `2` does not wait for `--fail-on-diff`. That flag chooses whether a *difference* is a
18
+ failure; it has no bearing on whether the tool could look.
19
+
3
20
  ## `parse` -- inspect a single file
4
21
 
5
22
  ```sh
@@ -49,7 +66,6 @@ gerberdiff diff before/ after/ --align-offset 0.5,0 # shift board B by 0.5 in
49
66
  | ------ | -------------------------------------------------- |
50
67
  | Red | Geometry present in **before** only (removed) |
51
68
  | Green | Geometry present in **after** only (added) |
52
- | Yellow | Geometry changed (both non-zero, different value) |
53
69
  | Grey | Unchanged geometry (only with `--png-show-common`) |
54
70
 
55
71
  ## `geomdiff` -- geometry-aware diff of two layer directories
@@ -0,0 +1 @@
1
+ --8<-- "CONTRIBUTING.md"
@@ -158,8 +158,14 @@ documented so the two engines' outputs are interpreted correctly:
158
158
 
159
159
  ## Known limitations
160
160
 
161
- - Strokes drawn with macro or block apertures are skipped (the raster
161
+ - Strokes drawn with macro or block apertures are not modelled (the raster
162
162
  engine draws them as hairlines; neither engine models them properly).
163
+ **They are reported, not skipped in silence.** Each one is counted in
164
+ `summary.unrepresented`, makes the layer `indeterminate`, and gives the
165
+ comparison an `outcome` of `"indeterminate"` at exit `2`. This limitation was
166
+ documented here long before the output admitted to it: a board with such a
167
+ stroke reported `0 changes` at exit `0`, with JSON byte-identical to comparing
168
+ a board against a copy of itself, while the raster engine reported the change.
163
169
  - `rerouted` classification is deferred: a redrawn trace reports as
164
170
  removed + added segments (or `resized` when endpoints stay close).
165
171
  - Geometry changes are op-granular: a moved multi-op footprint reports
@@ -0,0 +1,78 @@
1
+ # gerberdiff
2
+
3
+ Diff tool for Gerber/Excellon PCB design files, with two complementary
4
+ engines:
5
+
6
+ - **Raster diff** (`diff`) -- renders both revisions with Cairo and XORs the
7
+ pixels; produces visual overlay PNGs of changed regions.
8
+ - **Geometry diff** (`geomdiff`) -- computes resolution-independent,
9
+ **attributed** changes on the parsed vector geometry: every change is
10
+ classified as `added`, `removed`, `moved` (with dx/dy displacement, down
11
+ to micrometres), or `resized`, with net names propagated from `%TO.N%`
12
+ attributes.
13
+
14
+ The raster engine answers "*where* did pixels change?"; the geometry engine
15
+ answers "*what* changed, and by how much?". A $0.14\,\text{mm}$ component
16
+ move that renders as an unreadable field of XOR crescents in a raster diff
17
+ is reported by the geometry engine as "46 objects moved by
18
+ $(-0.139, -0.054)\,\text{mm}$".
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ pip install gerberdiff
24
+ ```
25
+
26
+ Requires Python >= 3.11. The raster engine needs the system Cairo library
27
+ (`libcairo2` on Debian/Ubuntu, `cairo` via Homebrew); the geometry engine
28
+ and the parsers are Cairo-free and work everywhere.
29
+
30
+ ## Quick start
31
+
32
+ ```sh
33
+ # Geometry diff: what moved, resized, was added or removed -- and by how much
34
+ gerberdiff geomdiff before/ after/ --out-json report.json --out-svg overlays/
35
+
36
+ # Raster diff: visual overlay PNGs
37
+ gerberdiff diff before/ after/ --out-json report.json --out-png diffs/
38
+
39
+ # Exit 1 if any changes detected (useful in CI)
40
+ gerberdiff geomdiff before/ after/ --fail-on-diff
41
+ ```
42
+
43
+ ```python
44
+ import gerberdiff
45
+ from pathlib import Path
46
+
47
+ result = gerberdiff.compute_geometry_diff(Path("before/"), Path("after/"))
48
+ for layer in result.layers:
49
+ for change in layer.changes:
50
+ print(f"{layer.name}: {change.kind} {change.op_kind} "
51
+ f"dx={change.dx_mm} dy={change.dy_mm}")
52
+ ```
53
+
54
+ ## Where to go next
55
+
56
+ | I want to... | Read |
57
+ | --- | --- |
58
+ | Use the command line | [CLI reference](cli.md) |
59
+ | Call it from Python | [Python API](api.md) |
60
+ | Parse the JSON reports | [JSON report schemas](schema.md) |
61
+ | Understand how it works | [Architecture](architecture.md) |
62
+ | Deep-dive the geometry engine | [Geometry diff engine](geometry-diff.md) |
63
+ | Browse the API surface | [API reference](reference/package.md) |
64
+ | Contribute | [Contributing](contributing.md) |
65
+
66
+ ## Known limitations
67
+
68
+ - **Excellon rout mode:** only drill hits are processed; routing paths
69
+ produce a `Warning` diagnostic but no geometry.
70
+ - **Deprecated RS-274X commands (`%MI%`, `%OF%`, `%SF%`, `%AS%`):** ignored
71
+ with an `Info` diagnostic.
72
+ - **Rectangle/obround aperture strokes:** the raster engine strokes with
73
+ `max(width, height)`; the geometry engine computes the exact Minkowski sum
74
+ for linear strokes (see [Geometry diff engine](geometry-diff.md)).
75
+
76
+ ## License
77
+
78
+ [Apache-2.0](https://github.com/heibench/gerberdiff/blob/main/LICENSE).