From ba0c1e886256b3bcd3138feccb1830e56d601f7e Mon Sep 17 00:00:00 2001 From: Sam Gallagher Date: Mon, 18 Nov 2024 07:34:48 -0500 Subject: [PATCH 1/5] Update gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index d83956f8..eb5350ab 100644 --- a/.gitignore +++ b/.gitignore @@ -302,3 +302,6 @@ output.png .reference-assets.lock output.svg formatted.gbr + +# Local test files +test/local/ From d729d21afd78de2c3ec2f0a99abee2186c8f861b Mon Sep 17 00:00:00 2001 From: Sam Gallagher Date: Mon, 18 Nov 2024 10:56:41 -0500 Subject: [PATCH 2/5] Add .idea to gitignore --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index eb5350ab..2fa749e5 100644 --- a/.gitignore +++ b/.gitignore @@ -260,7 +260,7 @@ cython_debug/ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. -#.idea/ +.idea/ ### Python Patch ### # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration From b0fbf6fad10b3f925e71d4384290f919620fa5dc Mon Sep 17 00:00:00 2001 From: Sam Gallagher Date: Mon, 18 Nov 2024 10:56:57 -0500 Subject: [PATCH 3/5] Update to allow implied leading zeros for Altium --- src/pygerber/gerber/ast/nodes/enums.py | 3 +++ src/pygerber/gerber/ast/state_tracking_visitor.py | 2 +- src/pygerber/gerber/parser/pyparsing/grammar.py | 2 +- 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pygerber/gerber/ast/nodes/enums.py b/src/pygerber/gerber/ast/nodes/enums.py index e798ebda..be164e72 100644 --- a/src/pygerber/gerber/ast/nodes/enums.py +++ b/src/pygerber/gerber/ast/nodes/enums.py @@ -14,6 +14,9 @@ class Zeros(Enum): SKIP_TRAILING = "T" """Skip trailing zeros mode.""" + SKIP_LEADING_IMPLIED = "" + """Implied skip leading zeros mode.""" + def __repr__(self) -> str: return f"{self.__class__.__name__}.{self.name}" diff --git a/src/pygerber/gerber/ast/state_tracking_visitor.py b/src/pygerber/gerber/ast/state_tracking_visitor.py index 08a869d5..55ca27b6 100644 --- a/src/pygerber/gerber/ast/state_tracking_visitor.py +++ b/src/pygerber/gerber/ast/state_tracking_visitor.py @@ -111,7 +111,7 @@ class CoordinateFormat(_StateModel): def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - if self.zeros == Zeros.SKIP_LEADING: + if self.zeros == Zeros.SKIP_LEADING or self.zeros == Zeros.SKIP_LEADING_IMPLIED: self.unpack_x = self._unpack_skip_leading(self.x_integral, self.x_decimal) # type: ignore[method-assign] self.unpack_y = self._unpack_skip_leading(self.y_integral, self.y_decimal) # type: ignore[method-assign] self.pack_x = self._pack_skip_leading(self.x_integral, self.x_decimal) # type: ignore[method-assign] diff --git a/src/pygerber/gerber/parser/pyparsing/grammar.py b/src/pygerber/gerber/parser/pyparsing/grammar.py index 4abfe801..145279ed 100644 --- a/src/pygerber/gerber/parser/pyparsing/grammar.py +++ b/src/pygerber/gerber/parser/pyparsing/grammar.py @@ -1762,7 +1762,7 @@ def fs(self) -> pp.ParserElement: return ( self._extended_command( pp.Literal("FS") - + pp.one_of(("L", "T")).set_results_name("zeros") + + pp.one_of(("L", "T", "")).set_results_name("zeros") + pp.one_of(("I", "A")).set_results_name("coordinate_mode") + pp.CaselessLiteral("X") + pp.Regex(r"[0-9]").set_results_name("x_integral") From 6fa8df8d78829a671644a1f0564db446450a47db Mon Sep 17 00:00:00 2001 From: Sam Gallagher Date: Tue, 19 Nov 2024 10:41:07 -0500 Subject: [PATCH 4/5] Fix .gitignore and state_tracking_visitor.py style --- .gitignore | 4 +--- src/pygerber/gerber/ast/state_tracking_visitor.py | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 2fa749e5..3341e811 100644 --- a/.gitignore +++ b/.gitignore @@ -265,6 +265,7 @@ cython_debug/ ### Python Patch ### # Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration poetry.toml +A # ruff .ruff_cache/ @@ -302,6 +303,3 @@ output.png .reference-assets.lock output.svg formatted.gbr - -# Local test files -test/local/ diff --git a/src/pygerber/gerber/ast/state_tracking_visitor.py b/src/pygerber/gerber/ast/state_tracking_visitor.py index 55ca27b6..10efd1e3 100644 --- a/src/pygerber/gerber/ast/state_tracking_visitor.py +++ b/src/pygerber/gerber/ast/state_tracking_visitor.py @@ -111,7 +111,7 @@ class CoordinateFormat(_StateModel): def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) - if self.zeros == Zeros.SKIP_LEADING or self.zeros == Zeros.SKIP_LEADING_IMPLIED: + if self.zeros in (Zeros.SKIP_LEADING, Zeros.SKIP_LEADING_IMPLIED): self.unpack_x = self._unpack_skip_leading(self.x_integral, self.x_decimal) # type: ignore[method-assign] self.unpack_y = self._unpack_skip_leading(self.y_integral, self.y_decimal) # type: ignore[method-assign] self.pack_x = self._pack_skip_leading(self.x_integral, self.x_decimal) # type: ignore[method-assign] From f7d4ac707663004a86d5b939addbb0a9f044fcf0 Mon Sep 17 00:00:00 2001 From: Sam Gallagher Date: Tue, 19 Nov 2024 11:15:51 -0500 Subject: [PATCH 5/5] Update tests for altium implied leading zero omission support --- .../gerberx3/issues/340/altium_example.grb | 54 ++++++++++++++++ .../gerberx3/tokens/properties/FSAX44Y44.grb | 1 + .../test_ast/test_state_tracking_visitor.py | 62 +++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 test/assets/gerberx3/issues/340/altium_example.grb create mode 100644 test/assets/gerberx3/tokens/properties/FSAX44Y44.grb diff --git a/test/assets/gerberx3/issues/340/altium_example.grb b/test/assets/gerberx3/issues/340/altium_example.grb new file mode 100644 index 00000000..56f292f6 --- /dev/null +++ b/test/assets/gerberx3/issues/340/altium_example.grb @@ -0,0 +1,54 @@ +G04* +G04 #@! TF.GenerationSoftware,Altium Limited,Altium Designer,21.9.1 (22)* +G04* +G04 Layer_Physical_Order=1* +G04 Layer_Color=255* +%FSAX44Y44*% +%MOMM*% +G71* +G04* +G04 #@! TF.SameCoordinates,D1130218-381A-440B-BF67-0167512D4D43* +G04* +G04* +G04 #@! TF.FilePolarity,Positive* +G04* +G01* +G75* +%ADD14R,3.4000X1.5500*% +%ADD15C,1.5240*% +%ADD24C,1.0160*% +%ADD25R,1.3700X1.3700*% +%ADD26C,1.3700*% +%ADD27C,3.0000*% +%ADD28C,2.0320*% +%ADD29R,2.0320X2.0320*% +%ADD30C,7.0000*% +D14* +X01100000Y01800000D02* +D03* +Y01760000D02* +D03* +X00560000Y01660000D02* +D03* +Y01620000D02* +D03* +X00605000Y01660000D02* +D03* +Y01620000D02* +D03* +X00650000Y01660000D02* +D03* +Y01620000D02* +D03* +X00695000Y01660000D02* +D03* +Y01620000D02* +D03* +X00740000Y01660000D02* +D03* +Y01620000D02* +D03* +X00785000Y01660000D02* +D03* +Y01620000D02* +M02* diff --git a/test/assets/gerberx3/tokens/properties/FSAX44Y44.grb b/test/assets/gerberx3/tokens/properties/FSAX44Y44.grb new file mode 100644 index 00000000..f8d42ffd --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/FSAX44Y44.grb @@ -0,0 +1 @@ +%FSAX44Y44*% diff --git a/test/unit/test_gerber/test_ast/test_state_tracking_visitor.py b/test/unit/test_gerber/test_ast/test_state_tracking_visitor.py index 7719a39d..18cf5325 100644 --- a/test/unit/test_gerber/test_ast/test_state_tracking_visitor.py +++ b/test/unit/test_gerber/test_ast/test_state_tracking_visitor.py @@ -463,6 +463,14 @@ def test_d01_dispatch( 3, 7, ), + ( + Zeros.SKIP_LEADING_IMPLIED, + CoordinateNotation.ABSOLUTE, + 2, + 6, + 3, + 7, + ), ( Zeros.SKIP_LEADING, CoordinateNotation.INCREMENTAL, @@ -479,6 +487,14 @@ def test_d01_dispatch( 3, 7, ), + ( + Zeros.SKIP_LEADING_IMPLIED, + CoordinateNotation.INCREMENTAL, + 2, + 6, + 3, + 7, + ), ], ) def test_coordinate_format( @@ -540,6 +556,15 @@ class TestCoordinateFormat: y_decimal=6, ) + fmt_2 = FMT( + zeros=Zeros.SKIP_LEADING_IMPLIED, + coordinate_mode=CoordinateNotation.ABSOLUTE, + x_integral=2, + x_decimal=6, + y_integral=2, + y_decimal=6, + ) + params_0: ClassVar[ParamsT] = [ (fmt_0, "0", 0), (fmt_0, "1", 0.000001), @@ -580,6 +605,13 @@ class TestCoordinateFormat: (fmt_1, "+1", 10), (fmt_1, "-1", -10), (fmt_0, "-0", -0), + (fmt_2, "+", PackedCoordinateTooShortError), + (fmt_2, "+100000000", PackedCoordinateTooLongError), + (fmt_2, "", PackedCoordinateTooShortError), + (fmt_2, "100000001", PackedCoordinateTooLongError), + (fmt_2, "-", PackedCoordinateTooShortError), + (fmt_2, "-100000000", PackedCoordinateTooLongError), + (fmt_2, "-0", -0), ] params_4: ClassVar[ParamsT] = [ (fmt_1, "00000001", 0.000001), @@ -609,6 +641,36 @@ class TestCoordinateFormat: (fmt_1, "-101", -10.1), (fmt_1, "-11", -11), ] + params_7: ClassVar[ParamsT] = [ + (fmt_2, "0", 0), + (fmt_2, "1", 0.000001), + (fmt_2, "11", 0.000011), + (fmt_2, "101", 0.000101), + (fmt_2, "1001", 0.001001), + (fmt_2, "10001", 0.010001), + (fmt_2, "100001", 0.100001), + (fmt_2, "1000001", 1.000001), + (fmt_2, "10000001", 10.000001), + ] + params_8: ClassVar[ParamsT] = [ + (fmt_2, "+0", 0), + (fmt_2, "+10", 0.00001), + (fmt_2, "+100", 0.0001), + (fmt_2, "+1000", 0.001), + (fmt_2, "+10000", 0.01), + (fmt_2, "+100000", 0.1), + (fmt_2, "+1000000", 1), + (fmt_2, "+10000000", 10), + ] + params_9: ClassVar[ParamsT] = [ + (fmt_2, "-10", -0.00001), + (fmt_2, "-100", -0.0001), + (fmt_2, "-1000", -0.001), + (fmt_2, "-10000", -0.01), + (fmt_2, "-100000", -0.1), + (fmt_2, "-1000000", -1), + (fmt_2, "-10000000", -10), + ] @pytest.mark.parametrize( ("fmt", "input_value", "expected"),