gerberdiff 0.29.0__py3-none-any.whl

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 (38) hide show
  1. gerberdiff/__init__.py +86 -0
  2. gerberdiff/cli.py +527 -0
  3. gerberdiff/diff/__init__.py +0 -0
  4. gerberdiff/diff/diff_engine.py +409 -0
  5. gerberdiff/diff/layer_matcher.py +202 -0
  6. gerberdiff/export/__init__.py +0 -0
  7. gerberdiff/export/json_report.py +184 -0
  8. gerberdiff/export/png_export.py +92 -0
  9. gerberdiff/export/svg_export.py +187 -0
  10. gerberdiff/geometry/__init__.py +37 -0
  11. gerberdiff/geometry/attribute.py +203 -0
  12. gerberdiff/geometry/driver.py +227 -0
  13. gerberdiff/geometry/expand.py +232 -0
  14. gerberdiff/geometry/geom_diff.py +153 -0
  15. gerberdiff/geometry/layer_geometry.py +665 -0
  16. gerberdiff/geometry/macro_geom.py +215 -0
  17. gerberdiff/geometry/primitives.py +108 -0
  18. gerberdiff/geometry/types.py +85 -0
  19. gerberdiff/parse/__init__.py +0 -0
  20. gerberdiff/parse/arc_math.py +162 -0
  21. gerberdiff/parse/excellon_parser.py +338 -0
  22. gerberdiff/parse/gerber_parser.py +244 -0
  23. gerberdiff/parse/gerber_state.py +780 -0
  24. gerberdiff/parse/macro_parser.py +604 -0
  25. gerberdiff/parse/tokenizer.py +153 -0
  26. gerberdiff/py.typed +0 -0
  27. gerberdiff/render/__init__.py +0 -0
  28. gerberdiff/render/compiled_render.py +240 -0
  29. gerberdiff/render/draw_ops.py +205 -0
  30. gerberdiff/render/macro_renderer.py +343 -0
  31. gerberdiff/render/renderer.py +283 -0
  32. gerberdiff/render/viewport.py +85 -0
  33. gerberdiff/types.py +360 -0
  34. gerberdiff-0.29.0.dist-info/METADATA +105 -0
  35. gerberdiff-0.29.0.dist-info/RECORD +38 -0
  36. gerberdiff-0.29.0.dist-info/WHEEL +4 -0
  37. gerberdiff-0.29.0.dist-info/entry_points.txt +2 -0
  38. gerberdiff-0.29.0.dist-info/licenses/LICENSE +201 -0
gerberdiff/types.py ADDED
@@ -0,0 +1,360 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+ from dataclasses import dataclass, field
5
+ from enum import StrEnum
6
+ from pathlib import Path
7
+ from typing import TYPE_CHECKING, Literal, TypeAlias
8
+
9
+ if TYPE_CHECKING:
10
+ # MacroDef lives in parse/macro_parser.py (Phase 4). Imported only for
11
+ # type-checking to avoid a runtime circular dependency.
12
+ from gerberdiff.parse.macro_parser import MacroDef
13
+
14
+
15
+ # ---------------------------------------------------------------------------
16
+ # Enums
17
+ # ---------------------------------------------------------------------------
18
+
19
+
20
+ class ApertureType(StrEnum):
21
+ Circle = "circle"
22
+ Rectangle = "rectangle"
23
+ Obround = "obround"
24
+ Polygon = "polygon"
25
+ Macro = "macro"
26
+ Block = "block"
27
+
28
+
29
+ class ApertureState(StrEnum):
30
+ Off = "off"
31
+ On = "on"
32
+ Flash = "flash"
33
+
34
+
35
+ class InterpolationMode(StrEnum):
36
+ Linear = "linear"
37
+ ClockwiseCircular = "cw"
38
+ CounterClockwiseCircular = "ccw"
39
+
40
+
41
+ class Polarity(StrEnum):
42
+ Dark = "dark"
43
+ Clear = "clear"
44
+
45
+
46
+ class MirrorState(StrEnum):
47
+ # None_ avoids collision with the Python builtin None
48
+ None_ = "none"
49
+ FlipA = "flipA"
50
+ FlipB = "flipB"
51
+ FlipAB = "flipAB"
52
+
53
+
54
+ class UnitType(StrEnum):
55
+ Inch = "inch"
56
+ Millimeter = "mm"
57
+
58
+
59
+ class ZeroOmission(StrEnum):
60
+ Leading = "leading" # leading zeros omitted (most common, RS-274X default)
61
+ Trailing = "trailing" # trailing zeros omitted
62
+ Explicit = "explicit" # all digits present (rare)
63
+
64
+
65
+ class CoordinateMode(StrEnum):
66
+ Absolute = "absolute"
67
+ Incremental = "incremental"
68
+
69
+
70
+ class DiagnosticSeverity(StrEnum):
71
+ Error = "error" # abort: parse result is unusable
72
+ Warning = "warning" # proceed: result may be degraded
73
+ Info = "info" # informational, suppressed unless -v
74
+
75
+
76
+ class LayerStatus(StrEnum):
77
+ Matched = "matched"
78
+ Added = "added"
79
+ Removed = "removed"
80
+
81
+
82
+ class LayerType(StrEnum):
83
+ FCu = "FCu"
84
+ BCu = "BCu"
85
+ InCu = "InCu"
86
+ FMask = "FMask"
87
+ BMask = "BMask"
88
+ FPaste = "FPaste"
89
+ BPaste = "BPaste"
90
+ FSilk = "FSilk"
91
+ BSilk = "BSilk"
92
+ EdgeCuts = "EdgeCuts"
93
+ NPTH = "NPTH"
94
+ PTH = "PTH"
95
+ Drill = "Drill"
96
+ Unknown = "Unknown"
97
+
98
+
99
+ # ---------------------------------------------------------------------------
100
+ # Geometric primitives
101
+ # ---------------------------------------------------------------------------
102
+
103
+
104
+ @dataclass
105
+ class ArcSegment:
106
+ """Fully resolved arc geometry. Angles in degrees."""
107
+
108
+ center_x: float
109
+ center_y: float
110
+ radius: float
111
+ start_angle_deg: float
112
+ end_angle_deg: float
113
+
114
+
115
+ @dataclass
116
+ class BoundingBox:
117
+ """Axis-aligned bounding box. All values in inches.
118
+
119
+ Initialises to the sentinel state {+inf, +inf, -inf, -inf} so that the
120
+ first call to expand() sets the box correctly. Check is_valid before use.
121
+ """
122
+
123
+ # math.inf is an immutable float constant -- field(default=...) is correct.
124
+ min_x: float = field(default=math.inf)
125
+ min_y: float = field(default=math.inf)
126
+ max_x: float = field(default=-math.inf)
127
+ max_y: float = field(default=-math.inf)
128
+
129
+ @property
130
+ def is_valid(self) -> bool:
131
+ """True if at least one point has been added."""
132
+ return math.isfinite(self.min_x)
133
+
134
+ def expand(self, x: float, y: float, radius: float = 0.0) -> None:
135
+ """Expand to include the point (x+/-radius, y+/-radius)."""
136
+ self.min_x = min(self.min_x, x - radius)
137
+ self.min_y = min(self.min_y, y - radius)
138
+ self.max_x = max(self.max_x, x + radius)
139
+ self.max_y = max(self.max_y, y + radius)
140
+
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Step-and-repeat / layer / net state
144
+ # ---------------------------------------------------------------------------
145
+
146
+
147
+ @dataclass
148
+ class StepAndRepeat:
149
+ x: int = 1 # repeat count X (>=1)
150
+ y: int = 1 # repeat count Y (>=1)
151
+ dist_x: float = 0.0 # step distance X in inches
152
+ dist_y: float = 0.0 # step distance Y in inches
153
+
154
+
155
+ @dataclass
156
+ class LayerState:
157
+ polarity: Polarity = Polarity.Dark
158
+ rotation: float = 0.0
159
+ mirror: MirrorState = MirrorState.None_
160
+ scale: float = 1.0
161
+ step_and_repeat: StepAndRepeat = field(default_factory=StepAndRepeat)
162
+ name: str | None = None
163
+
164
+
165
+ @dataclass
166
+ class CoordState:
167
+ """Snapshot of global image-level state at the time a net was emitted.
168
+
169
+ ``unit`` is stored for diagnostic/display purposes only. All coordinates
170
+ in DrawOp are already normalised to inches by convert_coordinate at parse
171
+ time. The deprecated RS-274X image/axis commands (%MI%, %AS%, %OF%,
172
+ %SF%) are silently ignored by the parser; their fields have been removed
173
+ from this type.
174
+ """
175
+
176
+ unit: UnitType = UnitType.Inch
177
+
178
+
179
+ @dataclass
180
+ class DrawOp:
181
+ """A single drawing operation. All coordinates in inches."""
182
+
183
+ start_x: float
184
+ start_y: float
185
+ stop_x: float
186
+ stop_y: float
187
+ aperture_index: int
188
+ aperture_state: ApertureState
189
+ interpolation: InterpolationMode
190
+ layer_index: int
191
+ net_state_index: int
192
+ arc_segment: ArcSegment | None = None
193
+ attributes: dict[str, str] | None = None # %TO.* object attributes
194
+
195
+
196
+ @dataclass
197
+ class RegionFill:
198
+ """A filled region produced by a G36..G37 block.
199
+
200
+ ``segments`` are the ``DrawOp`` objects from inside the region (G36/G37
201
+ are not included). All coordinates in inches.
202
+ """
203
+
204
+ layer_index: int
205
+ net_state_index: int
206
+ segments: list[DrawOp]
207
+
208
+
209
+ @dataclass
210
+ class Diagnostic:
211
+ severity: DiagnosticSeverity
212
+ message: str
213
+ line: int | None = None
214
+
215
+
216
+ # ---------------------------------------------------------------------------
217
+ # Aperture definitions
218
+ # ---------------------------------------------------------------------------
219
+
220
+
221
+ @dataclass
222
+ class CircleAperture:
223
+ aperture_type: Literal[ApertureType.Circle] = ApertureType.Circle
224
+ diameter: float = 0.0 # inches
225
+ hole_diameter: float | None = None
226
+
227
+
228
+ @dataclass
229
+ class RectangleAperture:
230
+ aperture_type: Literal[ApertureType.Rectangle] = ApertureType.Rectangle
231
+ width: float = 0.0
232
+ height: float = 0.0
233
+ hole_diameter: float | None = None
234
+
235
+
236
+ @dataclass
237
+ class ObroundAperture:
238
+ aperture_type: Literal[ApertureType.Obround] = ApertureType.Obround
239
+ width: float = 0.0
240
+ height: float = 0.0
241
+ hole_diameter: float | None = None
242
+
243
+
244
+ @dataclass
245
+ class PolygonAperture:
246
+ aperture_type: Literal[ApertureType.Polygon] = ApertureType.Polygon
247
+ outer_diameter: float = 0.0
248
+ num_vertices: int = 4
249
+ rotation: float = 0.0
250
+ hole_diameter: float | None = None
251
+
252
+
253
+ @dataclass
254
+ class MacroAperture:
255
+ aperture_type: Literal[ApertureType.Macro] = ApertureType.Macro
256
+ macro_def: MacroDef | None = None # defined in parse/macro_parser.py (Phase 4)
257
+ params: list[float] = field(default_factory=list)
258
+ unit_scale: float = 1.0 # 1.0 for inch files; 1/25.4 for mm files
259
+
260
+
261
+ @dataclass
262
+ class BlockAperture:
263
+ aperture_type: Literal[ApertureType.Block] = ApertureType.Block
264
+ draw_ops: list[DrawOp | RegionFill] = field(default_factory=list)
265
+ apertures: dict[int, Aperture] = field(default_factory=dict)
266
+ layers: list[LayerState] = field(default_factory=list)
267
+ bounding_box: BoundingBox = field(default_factory=BoundingBox)
268
+
269
+
270
+ # Union type alias -- used for aperture dict values and type-narrowing dispatch.
271
+ Aperture: TypeAlias = (
272
+ CircleAperture
273
+ | RectangleAperture
274
+ | ObroundAperture
275
+ | PolygonAperture
276
+ | MacroAperture
277
+ | BlockAperture
278
+ )
279
+
280
+
281
+ # ---------------------------------------------------------------------------
282
+ # Top-level IR output
283
+ # ---------------------------------------------------------------------------
284
+
285
+
286
+ @dataclass
287
+ class ParsedImage:
288
+ """The complete output of parsing one Gerber or Excellon file."""
289
+
290
+ draw_ops: list[DrawOp | RegionFill]
291
+ apertures: dict[int, Aperture] # D-code -> aperture
292
+ layers: list[LayerState]
293
+ coord_states: list[CoordState]
294
+ bounding_box: BoundingBox
295
+ diagnostics: list[Diagnostic]
296
+ source_path: Path | None = None
297
+
298
+
299
+ # ---------------------------------------------------------------------------
300
+ # Diff result types
301
+ # ---------------------------------------------------------------------------
302
+
303
+
304
+ @dataclass
305
+ class Region:
306
+ """A contiguous changed area. Coordinates in inches."""
307
+
308
+ id: int
309
+ centroid_x: float
310
+ centroid_y: float
311
+ bounding_box: BoundingBox # consistent naming with ParsedImage, BlockAperture
312
+ pixel_count: int
313
+
314
+
315
+ @dataclass
316
+ class LayerDiffResult:
317
+ name: str
318
+ status: LayerStatus # matched | added | removed
319
+ layer_type: LayerType
320
+ changed_pixel_count: int
321
+ total_pixel_count: int
322
+ regions: list[Region]
323
+
324
+ @property
325
+ def changed_fraction(self) -> float:
326
+ if self.total_pixel_count == 0:
327
+ return 0.0
328
+ return self.changed_pixel_count / self.total_pixel_count
329
+
330
+
331
+ @dataclass
332
+ class DiffResult:
333
+ layers: list[LayerDiffResult]
334
+
335
+ @property
336
+ def has_changes(self) -> bool:
337
+ """True when any layer was added, removed, or has changed pixels."""
338
+ return any(
339
+ lr.changed_pixel_count > 0 or lr.status != LayerStatus.Matched for lr in self.layers
340
+ )
341
+
342
+
343
+ # ---------------------------------------------------------------------------
344
+ # Exceptions
345
+ # ---------------------------------------------------------------------------
346
+
347
+
348
+ class GerberParseError(Exception):
349
+ """Raised by ``compute_full_diff`` when a fatal parse error is encountered.
350
+
351
+ Attributes
352
+ ----------
353
+ path : Path
354
+ The file that triggered the error.
355
+ """
356
+
357
+ def __init__(self, path: Path, message: str, line: int | None = None) -> None:
358
+ self.path = path
359
+ loc = f" (line {line})" if line is not None else ""
360
+ super().__init__(f"{path.name}: {message}{loc}")
@@ -0,0 +1,105 @@
1
+ Metadata-Version: 2.4
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
9
+ Author-email: Cameron Brooks <cameronbrooks11@gmail.com>
10
+ License-Expression: Apache-2.0
11
+ License-File: LICENSE
12
+ Keywords: diff,eda,excellon,gerber,grbl,kicad,pcb
13
+ Classifier: Development Status :: 4 - Beta
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Manufacturing
16
+ Classifier: License :: OSI Approved :: Apache Software License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
23
+ Classifier: Typing :: Typed
24
+ Requires-Python: >=3.11
25
+ Requires-Dist: cairocffi>=1.6
26
+ Requires-Dist: click>=8
27
+ Requires-Dist: numpy>=1.24
28
+ Requires-Dist: scipy>=1.10
29
+ Requires-Dist: shapely>=2.0
30
+ Provides-Extra: rich
31
+ Requires-Dist: rich>=13; extra == 'rich'
32
+ Description-Content-Type: text/markdown
33
+
34
+ # gerberdiff
35
+
36
+ [![CI](https://github.com/CameronBrooks11/gerberdiff/actions/workflows/ci.yml/badge.svg)](https://github.com/CameronBrooks11/gerberdiff/actions/workflows/ci.yml)
37
+ [![PyPI](https://img.shields.io/pypi/v/gerberdiff)](https://pypi.org/project/gerberdiff/)
38
+ [![Python](https://img.shields.io/pypi/pyversions/gerberdiff)](https://pypi.org/project/gerberdiff/)
39
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache--2.0-blue.svg)](LICENSE)
40
+
41
+ Diff tool for Gerber/Excellon PCB design files, with two complementary
42
+ engines:
43
+
44
+ - **Raster diff** (`diff`) -- visual overlay PNGs of changed pixels.
45
+ - **Geometry diff** (`geomdiff`) -- resolution-independent, attributed
46
+ changes (`moved` / `resized` / `added` / `removed`) computed on the
47
+ parsed vector geometry, down to micrometre displacements.
48
+
49
+ ## Install
50
+
51
+ ```sh
52
+ pip install gerberdiff
53
+ ```
54
+
55
+ Requires Python >= 3.11. The raster engine needs the system Cairo library
56
+ (`libcairo2` on Debian/Ubuntu, `cairo` via Homebrew); the geometry engine
57
+ is Cairo-free.
58
+
59
+ ## Quick start
60
+
61
+ ```sh
62
+ # Geometry diff: what moved, resized, was added or removed -- and by how much
63
+ gerberdiff geomdiff before/ after/ --out-json report.json --out-svg overlays/
64
+
65
+ # Raster diff: visual overlay PNGs
66
+ gerberdiff diff before/ after/ --out-json report.json --out-png diffs/
67
+
68
+ # Exit 1 if any changes detected (useful in CI)
69
+ gerberdiff geomdiff before/ after/ --fail-on-diff
70
+ ```
71
+
72
+ ```python
73
+ import gerberdiff
74
+ from pathlib import Path
75
+
76
+ result = gerberdiff.compute_geometry_diff(Path("before/"), Path("after/"))
77
+ for layer in result.layers:
78
+ for change in layer.changes:
79
+ print(f"{layer.name}: {change.kind} {change.op_kind} "
80
+ f"dx={change.dx_mm} dy={change.dy_mm}")
81
+ ```
82
+
83
+ ## Docs
84
+
85
+ | Topic | File |
86
+ | -------------------- | -------------------------------------------- |
87
+ | CLI reference | [docs/cli.md](docs/cli.md) |
88
+ | Python API | [docs/api.md](docs/api.md) |
89
+ | JSON report schemas | [docs/schema.md](docs/schema.md) |
90
+ | Architecture | [docs/architecture.md](docs/architecture.md) |
91
+ | Geometry diff engine | [docs/geometry-diff.md](docs/geometry-diff.md) |
92
+
93
+ ## Known limitations
94
+
95
+ - **Excellon rout mode:** only drill hits are processed; routing paths produce a
96
+ `Warning` diagnostic but no geometry.
97
+ - **Deprecated RS-274X commands (`%MI%`, `%OF%`, `%SF%`, `%AS%`):** ignored with an
98
+ `Info` diagnostic.
99
+ - **Rectangle/obround aperture strokes:** the raster engine strokes with
100
+ `max(width, height)`; the geometry engine computes the exact Minkowski sum
101
+ for linear strokes (see [docs/geometry-diff.md](docs/geometry-diff.md)).
102
+
103
+ ## Development
104
+
105
+ See [CONTRIBUTING.md](CONTRIBUTING.md).
@@ -0,0 +1,38 @@
1
+ gerberdiff/__init__.py,sha256=k4SXSMEbSXgIhrUEzQmCfqiJ28rjNbJ-VjVtVeXpQDc,2126
2
+ gerberdiff/cli.py,sha256=i7aYlcYie7jnfJMPXIboeXWU-cQsVOXV0j0Nmc53bm4,18796
3
+ gerberdiff/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ gerberdiff/types.py,sha256=oH782FolWQepErTjjUR1yIN5Owum9ASkWAzkC0nhz5I,9710
5
+ gerberdiff/diff/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ gerberdiff/diff/diff_engine.py,sha256=lr0Ppw4N9Bf5gqpBIr3lu218ZPQzbPMRuhOhDCxBq7Q,14219
7
+ gerberdiff/diff/layer_matcher.py,sha256=ygj1nYC6-8KiVfinNSR4OY_3POtAnzCInMn762UbImo,6732
8
+ gerberdiff/export/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
9
+ gerberdiff/export/json_report.py,sha256=RjVE2cGzgkLKYdPcdVZ2gwh_2G-EADMQEUDDlQuZHAM,6075
10
+ gerberdiff/export/png_export.py,sha256=2WNb_Jfpj8G57kKsx6hMJzkNbgJrh5JMJp93SiM_2lw,3110
11
+ gerberdiff/export/svg_export.py,sha256=npkFHTH9--ZXwKGLsQJ-s3wuUbawWadRtISr0xtC8w4,6302
12
+ gerberdiff/geometry/__init__.py,sha256=K3uHr08lxQgEbTrRfvyTKAILVLDloV1lvj-IbizFNfo,929
13
+ gerberdiff/geometry/attribute.py,sha256=M2yjYATNywovknHQmNh7Vw0cIDXWafcV-hN_DOsjS3g,7470
14
+ gerberdiff/geometry/driver.py,sha256=SR3MM9JQOYrmnXyu8W0hvKYoUeEoQ7zlIg6vR6e4BFk,8042
15
+ gerberdiff/geometry/expand.py,sha256=gLpXGjMJAXzepuuu09-sKn5fjwBtdNFjiLAkkrUNOgc,7818
16
+ gerberdiff/geometry/geom_diff.py,sha256=5jIY5ybKskQYpq3H1St0gghPjxIvJhi9C6sZqidp-oc,5596
17
+ gerberdiff/geometry/layer_geometry.py,sha256=RyAJnwW2yKVrQ99z0cN86TSgEEuP1aEOs9IHNpeGKmk,22165
18
+ gerberdiff/geometry/macro_geom.py,sha256=TKiu-fH4SSMg2XwPzfMv_PKooZeTlO6sXZxsoAGyizI,7304
19
+ gerberdiff/geometry/primitives.py,sha256=By8gdNQLqwb3pwoimjvM2gXrmaWGMcjmIAAWwt3rdQw,3675
20
+ gerberdiff/geometry/types.py,sha256=DtTCZ2RTVokgos2DyfMzkAfrDnnysNA-JUnFMCLI25w,2667
21
+ gerberdiff/parse/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ gerberdiff/parse/arc_math.py,sha256=pwe6Hva6lwrZzX8VMu2Pd0XrUL1cPsc-p3571EWCpSQ,5379
23
+ gerberdiff/parse/excellon_parser.py,sha256=pdRu7rAY3MzE9mIFLrkV7wGx86Ntff1ZmCzpEDRy5Io,10900
24
+ gerberdiff/parse/gerber_parser.py,sha256=3HVX2Oz-Ng7C5KvKahkgYMvYD1sZGxsZhbThnsjMryw,7594
25
+ gerberdiff/parse/gerber_state.py,sha256=e3XfM6hy3K8aQrkNdQ8wdjON_Ycv9RT5sXX9ir69ajo,28747
26
+ gerberdiff/parse/macro_parser.py,sha256=WIEXtEzYeb5RwUoEhFMGUQGpcACleh3hIlzXOlOEWQU,19841
27
+ gerberdiff/parse/tokenizer.py,sha256=6mhCv_VngFMKqVjMJ4KelHyWesgVbSFnn5EPXk2HzSs,4649
28
+ gerberdiff/render/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ gerberdiff/render/compiled_render.py,sha256=yyKeeYZsgFl4o4aNS5_G6Z0Y7b3JOtTX5b-g-pY0w28,7865
30
+ gerberdiff/render/draw_ops.py,sha256=CeINqVED4btp-_ZorjmCj1PvGn842eMVZz0hEyr4laA,7033
31
+ gerberdiff/render/macro_renderer.py,sha256=mCbwONgG2v2QSSzwZM4R8xyU18oEPtZPqst2002gIgY,10776
32
+ gerberdiff/render/renderer.py,sha256=sNoG0xZFnwb-Y2WAw02ZdyXaVRqiL4JtXdf5Bblt_jQ,9657
33
+ gerberdiff/render/viewport.py,sha256=Ym2iCFT4GkFEOp5Mtb6IGfy_LGI051VSM4TReWmzQxU,2520
34
+ gerberdiff-0.29.0.dist-info/METADATA,sha256=NoejnAu3AVP2g7D_Gox9B_2zekpn43DasibWLAOsLW8,4180
35
+ gerberdiff-0.29.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
36
+ gerberdiff-0.29.0.dist-info/entry_points.txt,sha256=0p8xVyTFlgL4mFCJwsKRrOPAIIF-X9HBut7jG16SbBE,50
37
+ gerberdiff-0.29.0.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
38
+ gerberdiff-0.29.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gerberdiff = gerberdiff.cli:cli