diff --git a/.gitignore b/.gitignore index d83956f8..3341e811 100644 --- a/.gitignore +++ b/.gitignore @@ -260,11 +260,12 @@ 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 poetry.toml +A # ruff .ruff_cache/ 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..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: + 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] 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") 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"),