Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for Altium implied leading zeros omitted #340

Merged
merged 9 commits into from
Nov 19, 2024
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
3 changes: 3 additions & 0 deletions src/pygerber/gerber/ast/nodes/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -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}"

Expand Down
2 changes: 1 addition & 1 deletion src/pygerber/gerber/ast/state_tracking_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion src/pygerber/gerber/parser/pyparsing/grammar.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down
54 changes: 54 additions & 0 deletions test/assets/gerberx3/issues/340/altium_example.grb
Original file line number Diff line number Diff line change
@@ -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*
1 change: 1 addition & 0 deletions test/assets/gerberx3/tokens/properties/FSAX44Y44.grb
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
%FSAX44Y44*%
62 changes: 62 additions & 0 deletions test/unit/test_gerber/test_ast/test_state_tracking_visitor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -479,6 +487,14 @@ def test_d01_dispatch(
3,
7,
),
(
Zeros.SKIP_LEADING_IMPLIED,
CoordinateNotation.INCREMENTAL,
2,
6,
3,
7,
),
],
)
def test_coordinate_format(
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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),
Expand Down Expand Up @@ -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"),
Expand Down
Loading