From 6893a86947da7410fea53aa3ca318fbf965ddfc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 15 Jul 2024 00:32:13 +0200 Subject: [PATCH 01/91] Add new ast scaffold --- src/pygerber/gerberx3/ast/__init__.py | 3 + src/pygerber/gerberx3/ast/nodes/__init__.py | 3 + .../gerberx3/ast/nodes/aperture/AB_close.py | 18 + .../gerberx3/ast/nodes/aperture/AB_open.py | 18 + .../gerberx3/ast/nodes/aperture/AD.py | 18 + .../gerberx3/ast/nodes/aperture/AM_close.py | 18 + .../gerberx3/ast/nodes/aperture/AM_open.py | 18 + .../gerberx3/ast/nodes/aperture/SR_close.py | 20 ++ .../gerberx3/ast/nodes/aperture/SR_open.py | 18 + .../gerberx3/ast/nodes/aperture/__init__.py | 3 + .../gerberx3/ast/nodes/attribute/TA.py | 18 + .../gerberx3/ast/nodes/attribute/TD.py | 18 + .../gerberx3/ast/nodes/attribute/TF.py | 18 + .../gerberx3/ast/nodes/attribute/TO.py | 18 + .../gerberx3/ast/nodes/attribute/__init__.py | 3 + src/pygerber/gerberx3/ast/nodes/base.py | 19 ++ .../gerberx3/ast/nodes/d_codes/D01.py | 18 + .../gerberx3/ast/nodes/d_codes/D02.py | 18 + .../gerberx3/ast/nodes/d_codes/D03.py | 18 + .../gerberx3/ast/nodes/d_codes/Dnn.py | 18 + .../gerberx3/ast/nodes/d_codes/__init__.py | 1 + .../gerberx3/ast/nodes/g_codes/G01.py | 18 + .../gerberx3/ast/nodes/g_codes/G02.py | 18 + .../gerberx3/ast/nodes/g_codes/G03.py | 18 + .../gerberx3/ast/nodes/g_codes/G04.py | 18 + .../gerberx3/ast/nodes/g_codes/G36.py | 18 + .../gerberx3/ast/nodes/g_codes/G37.py | 18 + .../gerberx3/ast/nodes/g_codes/G54.py | 18 + .../gerberx3/ast/nodes/g_codes/G55.py | 18 + .../gerberx3/ast/nodes/g_codes/G70.py | 18 + .../gerberx3/ast/nodes/g_codes/G71.py | 18 + .../gerberx3/ast/nodes/g_codes/G74.py | 18 + .../gerberx3/ast/nodes/g_codes/G75.py | 18 + .../gerberx3/ast/nodes/g_codes/G90.py | 18 + .../gerberx3/ast/nodes/g_codes/G91.py | 18 + .../gerberx3/ast/nodes/g_codes/__init__.py | 1 + src/pygerber/gerberx3/ast/nodes/load/LM.py | 18 + src/pygerber/gerberx3/ast/nodes/load/LN.py | 18 + src/pygerber/gerberx3/ast/nodes/load/LP.py | 18 + src/pygerber/gerberx3/ast/nodes/load/LR.py | 18 + src/pygerber/gerberx3/ast/nodes/load/LS.py | 18 + .../gerberx3/ast/nodes/load/__init__.py | 1 + .../gerberx3/ast/nodes/m_codes/M00.py | 18 + .../gerberx3/ast/nodes/m_codes/M01.py | 18 + .../gerberx3/ast/nodes/m_codes/M02.py | 18 + .../gerberx3/ast/nodes/m_codes/__init__.py | 1 + .../gerberx3/ast/nodes/math/__init__.py | 3 + .../gerberx3/ast/nodes/math/assignment.py | 18 + .../gerberx3/ast/nodes/math/constant.py | 18 + .../gerberx3/ast/nodes/math/expression.py | 20 ++ .../ast/nodes/math/operators/__init__.py | 3 + .../nodes/math/operators/binary/__init__.py | 3 + .../ast/nodes/math/operators/binary/add.py | 20 ++ .../ast/nodes/math/operators/binary/div.py | 20 ++ .../ast/nodes/math/operators/binary/mul.py | 20 ++ .../ast/nodes/math/operators/binary/sub.py | 20 ++ .../nodes/math/operators/unary/__init__.py | 3 + .../ast/nodes/math/operators/unary/neg.py | 20 ++ .../ast/nodes/math/operators/unary/pos.py | 20 ++ .../gerberx3/ast/nodes/math/variable.py | 18 + src/pygerber/gerberx3/ast/nodes/model.py | 23 ++ .../gerberx3/ast/nodes/other/__init__.py | 3 + .../gerberx3/ast/nodes/other/command_end.py | 20 ++ .../gerberx3/ast/nodes/other/coordinate.py | 20 ++ .../ast/nodes/other/extended_command_close.py | 20 ++ .../ast/nodes/other/extended_command_open.py | 20 ++ .../gerberx3/ast/nodes/primitives/__init__.py | 3 + .../gerberx3/ast/nodes/primitives/code_0.py | 18 + .../gerberx3/ast/nodes/primitives/code_1.py | 18 + .../gerberx3/ast/nodes/primitives/code_2.py | 18 + .../gerberx3/ast/nodes/primitives/code_20.py | 18 + .../gerberx3/ast/nodes/primitives/code_21.py | 18 + .../gerberx3/ast/nodes/primitives/code_22.py | 18 + .../gerberx3/ast/nodes/primitives/code_4.py | 18 + .../gerberx3/ast/nodes/primitives/code_5.py | 18 + .../gerberx3/ast/nodes/primitives/code_6.py | 18 + .../gerberx3/ast/nodes/primitives/code_7.py | 18 + .../gerberx3/ast/nodes/properties/FS.py | 18 + .../gerberx3/ast/nodes/properties/IN.py | 18 + .../gerberx3/ast/nodes/properties/IP.py | 18 + .../gerberx3/ast/nodes/properties/MO.py | 18 + .../gerberx3/ast/nodes/properties/OF.py | 18 + .../gerberx3/ast/nodes/properties/__init__.py | 3 + src/pygerber/gerberx3/ast/nodes/ruff.toml | 3 + src/pygerber/gerberx3/ast/visitor.py | 309 ++++++++++++++++++ 85 files changed, 1603 insertions(+) create mode 100644 src/pygerber/gerberx3/ast/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AD.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/attribute/TA.py create mode 100644 src/pygerber/gerberx3/ast/nodes/attribute/TD.py create mode 100644 src/pygerber/gerberx3/ast/nodes/attribute/TF.py create mode 100644 src/pygerber/gerberx3/ast/nodes/attribute/TO.py create mode 100644 src/pygerber/gerberx3/ast/nodes/attribute/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/base.py create mode 100644 src/pygerber/gerberx3/ast/nodes/d_codes/D01.py create mode 100644 src/pygerber/gerberx3/ast/nodes/d_codes/D02.py create mode 100644 src/pygerber/gerberx3/ast/nodes/d_codes/D03.py create mode 100644 src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py create mode 100644 src/pygerber/gerberx3/ast/nodes/d_codes/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G01.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G02.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G03.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G04.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G36.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G37.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G54.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G55.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G70.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G71.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G74.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G75.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G90.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G91.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/load/LM.py create mode 100644 src/pygerber/gerberx3/ast/nodes/load/LN.py create mode 100644 src/pygerber/gerberx3/ast/nodes/load/LP.py create mode 100644 src/pygerber/gerberx3/ast/nodes/load/LR.py create mode 100644 src/pygerber/gerberx3/ast/nodes/load/LS.py create mode 100644 src/pygerber/gerberx3/ast/nodes/load/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/m_codes/M00.py create mode 100644 src/pygerber/gerberx3/ast/nodes/m_codes/M01.py create mode 100644 src/pygerber/gerberx3/ast/nodes/m_codes/M02.py create mode 100644 src/pygerber/gerberx3/ast/nodes/m_codes/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/assignment.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/constant.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/expression.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/binary/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/unary/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py create mode 100644 src/pygerber/gerberx3/ast/nodes/math/variable.py create mode 100644 src/pygerber/gerberx3/ast/nodes/model.py create mode 100644 src/pygerber/gerberx3/ast/nodes/other/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/other/command_end.py create mode 100644 src/pygerber/gerberx3/ast/nodes/other/coordinate.py create mode 100644 src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py create mode 100644 src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_0.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_1.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_2.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_20.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_21.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_22.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_4.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_5.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_6.py create mode 100644 src/pygerber/gerberx3/ast/nodes/primitives/code_7.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/FS.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/IN.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/IP.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/MO.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/OF.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/__init__.py create mode 100644 src/pygerber/gerberx3/ast/nodes/ruff.toml create mode 100644 src/pygerber/gerberx3/ast/visitor.py diff --git a/src/pygerber/gerberx3/ast/__init__.py b/src/pygerber/gerberx3/ast/__init__.py new file mode 100644 index 00000000..c553971d --- /dev/null +++ b/src/pygerber/gerberx3/ast/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast` package contains all the node classes used to represent +the Gerber X3 abstract syntax tree. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/__init__.py b/src/pygerber/gerberx3/ast/nodes/__init__.py new file mode 100644 index 00000000..b81b39e6 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes` package contains all the node container classes +generated by the Gerber X3 parser. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py new file mode 100644 index 00000000..1e13f109 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.aperture.ABclose` module contains definition of `ABclose` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ABclose(Node): + """Represents AB Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ab_close(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py new file mode 100644 index 00000000..54578c40 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.aperture.ABopen` module contains definition of `ABopen` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ABopen(Node): + """Represents AB Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ab_open(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py new file mode 100644 index 00000000..59672f9d --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.aperture.AD` module contains definition of `AD` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class AD(Node): + """Represents AD Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ad(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py new file mode 100644 index 00000000..47a8b597 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.aperture.AMclose` module contains definition of `AMclose` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class AMclose(Node): + """Represents AM Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_am_close(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py new file mode 100644 index 00000000..cc32ab67 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.aperture.AMopen` module contains definition of `AMopen` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class AMopen(Node): + """Represents AM Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_am_open(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py new file mode 100644 index 00000000..cb305ae2 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.aperture.SR_close` module contains definition of `SRclose` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class SRclose(Node): + """Represents SR Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_sr_close(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py new file mode 100644 index 00000000..cbc3754a --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.aperture.SR_open` module contains definition of `SRopen` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class SRopen(Node): + """Represents SR Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_sr_open(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/__init__.py b/src/pygerber/gerberx3/ast/nodes/aperture/__init__.py new file mode 100644 index 00000000..09e9b414 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.aperture` package contains all the aperture definition +related nodes. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py new file mode 100644 index 00000000..9c70b2f1 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.attribute.TA` module contains definition of `TA` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class TA(Node): + """Represents TA Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ta(self) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TD.py b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py new file mode 100644 index 00000000..d936554b --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.attribute.TD` module contains definition of `TD` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class TD(Node): + """Represents TD Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_td(self) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py new file mode 100644 index 00000000..1b7b9790 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.attributes.TF` module contains definition of `TF` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class TF(Node): + """Represents TF Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf(self) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py new file mode 100644 index 00000000..0a168319 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.d_codes.TO` module contains definition of `TO` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class TO(Node): + """Represents TO Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to(self) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/__init__.py b/src/pygerber/gerberx3/ast/nodes/attribute/__init__.py new file mode 100644 index 00000000..94469f59 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/attribute/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.attribute` package contains all the attribute related +nodes. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py new file mode 100644 index 00000000..4e34b912 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -0,0 +1,19 @@ +"""`pygerber.gerberx3.ast.nodes.base` contains definition of `node` class.""" + +from __future__ import annotations + +from abc import abstractmethod +from typing import TYPE_CHECKING + +from pygerber.vm.types.model import ModelType + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Node(ModelType): + """Base class for all nodes.""" + + @abstractmethod + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py new file mode 100644 index 00000000..0401a18f --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.d_codes.D01` module contains definition of `D01` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class D01(Node): + """Represents D01 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_d01(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py new file mode 100644 index 00000000..2647cd1a --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.d_codes.D02` module contains definition of `D02` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class D02(Node): + """Represents D02 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_d02(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py new file mode 100644 index 00000000..37027b06 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.d_codes.D03` module contains definition of `D03` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class D03(Node): + """Represents D03 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_d03(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py new file mode 100644 index 00000000..695add8c --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.d_codes.DNN` module contains definition of `DNN` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Dnn(Node): + """Represents DNN Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_dnn(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/__init__.py b/src/pygerber/gerberx3/ast/nodes/d_codes/__init__.py new file mode 100644 index 00000000..96e220e5 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/__init__.py @@ -0,0 +1 @@ +"""`pygerber.gerberx3.ast.nodes.d_codes` package contains all the D-code nodes.""" diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py new file mode 100644 index 00000000..f6987fbb --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G01` module contains definition of `G01` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G01(Node): + """Represents G01 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g01(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py new file mode 100644 index 00000000..2c94b526 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G02` module contains definition of `G02` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G02(Node): + """Represents G02 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g02(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py new file mode 100644 index 00000000..57c8a49d --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G03` module contains definition of `G03` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G03(Node): + """Represents G03 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g03(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py new file mode 100644 index 00000000..2c7e5a78 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G04` module contains definition of `G04` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G04(Node): + """Represents G04 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g04(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py new file mode 100644 index 00000000..454cf125 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G36` module contains definition of `G36` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G36(Node): + """Represents G36 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g36(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py new file mode 100644 index 00000000..fcfea78c --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G37` module contains definition of `G37` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G37(Node): + """Represents G37 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g37(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py new file mode 100644 index 00000000..9ad02660 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G54` module contains definition of `G54` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G54(Node): + """Represents G54 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g54(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py new file mode 100644 index 00000000..bb36c199 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G55` module contains definition of `G55` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G55(Node): + """Represents G55 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g55(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py new file mode 100644 index 00000000..7164b820 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G70` module contains definition of `G70` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G70(Node): + """Represents G70 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g70(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py new file mode 100644 index 00000000..fd8cdb15 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G71` module contains definition of `G71` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G71(Node): + """Represents G71 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g71(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py new file mode 100644 index 00000000..451bb210 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G74` module contains definition of `G74` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G74(Node): + """Represents G74 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g74(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py new file mode 100644 index 00000000..2517566a --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G75` module contains definition of `G75` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G75(Node): + """Represents G75 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g75(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py new file mode 100644 index 00000000..51bc8a04 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G90` module contains definition of `G90` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G90(Node): + """Represents G90 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g90(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py new file mode 100644 index 00000000..911ae9eb --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.g_codes.G91` module contains definition of `G91` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class G91(Node): + """Represents G91 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_g91(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/__init__.py b/src/pygerber/gerberx3/ast/nodes/g_codes/__init__.py new file mode 100644 index 00000000..1c1541ef --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/__init__.py @@ -0,0 +1 @@ +"""`pygerber.gerberx3.ast.nodes.g_codes` package contains all the G-code nodes.""" diff --git a/src/pygerber/gerberx3/ast/nodes/load/LM.py b/src/pygerber/gerberx3/ast/nodes/load/LM.py new file mode 100644 index 00000000..b2fb5ed2 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/load/LM.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.load.LM` module contains definition of `LM` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class LM(Node): + """Represents LM Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_lm(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LN.py b/src/pygerber/gerberx3/ast/nodes/load/LN.py new file mode 100644 index 00000000..d59f07f6 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/load/LN.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.load.LN` module contains definition of `LN` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class LN(Node): + """Represents LN Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ln(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LP.py b/src/pygerber/gerberx3/ast/nodes/load/LP.py new file mode 100644 index 00000000..c7431733 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/load/LP.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.load.LP` module contains definition of `LP` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class LP(Node): + """Represents LP Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_lp(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LR.py b/src/pygerber/gerberx3/ast/nodes/load/LR.py new file mode 100644 index 00000000..e25f361e --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/load/LR.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.load.LR` module contains definition of `LR` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class LR(Node): + """Represents LR Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_lr(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LS.py b/src/pygerber/gerberx3/ast/nodes/load/LS.py new file mode 100644 index 00000000..c197d415 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/load/LS.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.load.LS` module contains definition of `LS` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class LS(Node): + """Represents LS Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ls(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/__init__.py b/src/pygerber/gerberx3/ast/nodes/load/__init__.py new file mode 100644 index 00000000..93f819d0 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/load/__init__.py @@ -0,0 +1 @@ +"""`pygerber.gerberx3.ast.nodes.load` package contains all the load_ nodes.""" diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py new file mode 100644 index 00000000..e5b39974 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.m_codes.M00` module contains definition of `M00` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class M00(Node): + """Represents M00 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_m00(self) diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py new file mode 100644 index 00000000..baeba584 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.m_codes.M01` module contains definition of `M01` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class M01(Node): + """Represents M01 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_m01(self) diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py new file mode 100644 index 00000000..8d299961 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.m_codes.M02` module contains definition of `M02` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class M02(Node): + """Represents M02 Gerber command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_m02(self) diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/__init__.py b/src/pygerber/gerberx3/ast/nodes/m_codes/__init__.py new file mode 100644 index 00000000..e6129fdf --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/__init__.py @@ -0,0 +1 @@ +"""`pygerber.gerberx3.ast.nodes.m_codes` package contains all the M-code nodes.""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/__init__.py b/src/pygerber/gerberx3/ast/nodes/math/__init__.py new file mode 100644 index 00000000..184d7f1e --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.math` package contains all the macro math expression +nodes. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/assignment.py b/src/pygerber/gerberx3/ast/nodes/math/assignment.py new file mode 100644 index 00000000..26224b36 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/assignment.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.math.Variable` module contains definition of `Variable` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Assignment(Node): + """Represents math expression variable.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_assignment(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/constant.py b/src/pygerber/gerberx3/ast/nodes/math/constant.py new file mode 100644 index 00000000..390fbfdb --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/constant.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.math.Constant` module contains definition of `Constant` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Constant(Node): + """Represents math expression constant.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_constant(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/expression.py b/src/pygerber/gerberx3/ast/nodes/math/expression.py new file mode 100644 index 00000000..69779861 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/expression.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.math.Expression` module contains definition of `Expression` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Expression(Node): + """Represents math expression expression.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_expression(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/__init__.py b/src/pygerber/gerberx3/ast/nodes/math/operators/__init__.py new file mode 100644 index 00000000..6f6ca380 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.math.operators` package contains all the macro math +expression operators. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/__init__.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/__init__.py new file mode 100644 index 00000000..6abca187 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.math.operators.binary` package contains all the macro +math expression binary operators. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py new file mode 100644 index 00000000..5db06add --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.math.operators.binary.Add` module contains definition of `Add` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Add(Node): + """Represents math expression addition operator.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_add(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py new file mode 100644 index 00000000..351395d4 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.math.operators.binary.Div` module contains definition of `Div` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Div(Node): + """Represents math expression division operator.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_div(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py new file mode 100644 index 00000000..154200ac --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.math.operators.binary.Mul` module contains definition of `Mul` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Mul(Node): + """Represents math expression multiplication operator.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_mul(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py new file mode 100644 index 00000000..33e3a38c --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.math.operators.binary.Sub` module contains definition of `Sub` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Sub(Node): + """Represents math expression subtraction operator.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_sub(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/__init__.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/__init__.py new file mode 100644 index 00000000..cef2861e --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.math.operators.unary` package contains all the macro +math expression unary operators. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py new file mode 100644 index 00000000..354675c8 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.math.operators.unary.Neg` module contains definition of `Neg` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Neg(Node): + """Represents math expression neg.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_neg(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py new file mode 100644 index 00000000..45e21d4d --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.math.operators.unary.Pos` module contains definition of `Pos` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Pos(Node): + """Represents math expression pos.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_pos(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/variable.py b/src/pygerber/gerberx3/ast/nodes/math/variable.py new file mode 100644 index 00000000..1b3ebd6c --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/variable.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.math.Variable` module contains definition of `Variable` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Variable(Node): + """Represents math expression variable.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_variable(self) diff --git a/src/pygerber/gerberx3/ast/nodes/model.py b/src/pygerber/gerberx3/ast/nodes/model.py new file mode 100644 index 00000000..fffd8899 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/model.py @@ -0,0 +1,23 @@ +"""`model` module definition of common base class for all `VirtualMachine` related +model types. +""" + +from __future__ import annotations + +from pydantic import BaseModel, ConfigDict, computed_field + + +class ModelType(BaseModel): + """Common base class for all node model types.""" + + model_config = ConfigDict( + extra="forbid", + frozen=True, + arbitrary_types_allowed=True, + ) + + @computed_field # type: ignore[misc] + @property + def __class_qualname__(self) -> str: + """Name of class.""" + return self.__class__.__qualname__ diff --git a/src/pygerber/gerberx3/ast/nodes/other/__init__.py b/src/pygerber/gerberx3/ast/nodes/other/__init__.py new file mode 100644 index 00000000..559d6c5b --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/other/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.other` package contains all the nodes that don't fit in +other categories. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/other/command_end.py b/src/pygerber/gerberx3/ast/nodes/other/command_end.py new file mode 100644 index 00000000..2e74c77c --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/other/command_end.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.other.command_end` module contains definition of `CommandEnd` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class CommandEnd(Node): + """Represents CommandEnd node.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_command_end(self) diff --git a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py new file mode 100644 index 00000000..ed509050 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.other.Coordinate` module contains definition of `Coordinate` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Coordinate(Node): + """Represents Coordinate node.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_coordinate(self) diff --git a/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py b/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py new file mode 100644 index 00000000..63977b5a --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.other.ExtendedCommandClose` module contains definition of +`ExtendedCommandClose` class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ExtendedCommandClose(Node): + """Represents ExtendedCommandClose node.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_extended_command_close(self) diff --git a/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py b/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py new file mode 100644 index 00000000..61ac0d2e --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py @@ -0,0 +1,20 @@ +"""`pygerber.nodes.other.ExtendedCommandOpen` module contains definition of +`ExtendedCommandOpen` class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ExtendedCommandOpen(Node): + """Represents ExtendedCommandOpen node.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_extended_command_open(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/__init__.py b/src/pygerber/gerberx3/ast/nodes/primitives/__init__.py new file mode 100644 index 00000000..c5a59fb0 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.primitives` package contains all the macro primitives +nodes. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py new file mode 100644 index 00000000..f9819121 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code0` module contains definition of `Code0` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code0(Node): + """Represents code 0 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_0(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py new file mode 100644 index 00000000..9fe41a42 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code1` module contains definition of `Code1` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code1(Node): + """Represents code 1 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_1(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py new file mode 100644 index 00000000..e847412c --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code2` module contains definition of `Code2` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code2(Node): + """Represents code 2 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_2(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py new file mode 100644 index 00000000..5174ba02 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code20` module contains definition of `Code20` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code20(Node): + """Represents code 20 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_20(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py new file mode 100644 index 00000000..3861feff --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code21` module contains definition of `Code21` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code21(Node): + """Represents code 21 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_21(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py new file mode 100644 index 00000000..a97b2cd9 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code22` module contains definition of `Code22` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code22(Node): + """Represents code 22 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_22(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py new file mode 100644 index 00000000..4e0dd4fe --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code4` module contains definition of `Code4` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code4(Node): + """Represents code 4 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_4(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py new file mode 100644 index 00000000..6f0c2209 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code5` module contains definition of `Code5` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code5(Node): + """Represents code 5 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_5(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py new file mode 100644 index 00000000..7596b04d --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code6` module contains definition of `Code6` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code6(Node): + """Represents code 6 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_6(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py new file mode 100644 index 00000000..75e054df --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.primitives.Code7` module contains definition of `Code7` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Code7(Node): + """Represents code 7 macro primitive.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_code_7(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py new file mode 100644 index 00000000..fc45855b --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.FS` module contains definition of `FS` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class FS(Node): + """Represents FS Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_fs(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IN.py b/src/pygerber/gerberx3/ast/nodes/properties/IN.py new file mode 100644 index 00000000..64054936 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/IN.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.IN` module contains definition of `IN` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class IN(Node): + """Represents IN Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_in(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IP.py b/src/pygerber/gerberx3/ast/nodes/properties/IP.py new file mode 100644 index 00000000..10a49d06 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/IP.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.IP` module contains definition of `IP` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class IP(Node): + """Represents IP Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ip(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MO.py b/src/pygerber/gerberx3/ast/nodes/properties/MO.py new file mode 100644 index 00000000..d1bc3d73 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/MO.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.MO` module contains definition of `MO` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class MO(Node): + """Represents MO Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_mo(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/OF.py b/src/pygerber/gerberx3/ast/nodes/properties/OF.py new file mode 100644 index 00000000..93310a2f --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/OF.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.OF` module contains definition of `OF` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class OF(Node): + """Represents OF Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_of(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/__init__.py b/src/pygerber/gerberx3/ast/nodes/properties/__init__.py new file mode 100644 index 00000000..8954a113 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.ast.nodes.properties` package contains all the nodes that set +image properties. +""" diff --git a/src/pygerber/gerberx3/ast/nodes/ruff.toml b/src/pygerber/gerberx3/ast/nodes/ruff.toml new file mode 100644 index 00000000..0fd74853 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/ruff.toml @@ -0,0 +1,3 @@ +extend = "../../../../../pyproject.toml" +[lint] +extend-ignore = ["N999"] diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py new file mode 100644 index 00000000..60b152f6 --- /dev/null +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -0,0 +1,309 @@ +"""`pygerber.gerberx3.node_visitor` contains definition of `NodeVisitor` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.math.assignment import Assignment +from pygerber.gerberx3.ast.nodes.math.constant import Constant +from pygerber.gerberx3.ast.nodes.math.expression import Expression +from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add +from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div +from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul +from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub +from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg +from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos +from pygerber.gerberx3.ast.nodes.math.variable import Variable + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose + from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen + from pygerber.gerberx3.ast.nodes.aperture.AD import AD + from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose + from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen + from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose + from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen + from pygerber.gerberx3.ast.nodes.attribute.TA import TA + from pygerber.gerberx3.ast.nodes.attribute.TD import TD + from pygerber.gerberx3.ast.nodes.attribute.TF import TF + from pygerber.gerberx3.ast.nodes.attribute.TO import TO + from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 + from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 + from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 + from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn + from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 + from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 + from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 + from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 + from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 + from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 + from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 + from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 + from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 + from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 + from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 + from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 + from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 + from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 + from pygerber.gerberx3.ast.nodes.load.LM import LM + from pygerber.gerberx3.ast.nodes.load.LN import LN + from pygerber.gerberx3.ast.nodes.load.LP import LP + from pygerber.gerberx3.ast.nodes.load.LR import LR + from pygerber.gerberx3.ast.nodes.load.LS import LS + from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 + from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 + from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 + from pygerber.gerberx3.ast.nodes.other.command_end import CommandEnd + from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate + from pygerber.gerberx3.ast.nodes.other.extended_command_close import ( + ExtendedCommandClose, + ) + from pygerber.gerberx3.ast.nodes.other.extended_command_open import ( + ExtendedCommandOpen, + ) + from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 + from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 + from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 + from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 + from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 + from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 + from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 + from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 + from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 + from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 + from pygerber.gerberx3.ast.nodes.properties.FS import FS + from pygerber.gerberx3.ast.nodes.properties.IN import IN + from pygerber.gerberx3.ast.nodes.properties.IP import IP + from pygerber.gerberx3.ast.nodes.properties.MO import MO + from pygerber.gerberx3.ast.nodes.properties.OF import OF + + +class AstVisitor: + """Interface of a node visitor. + + This class defines interface compliant with visitor pattern. + For more information on this pattern visit: + https://refactoring.guru/design-patterns/visitor + """ + + # Aperture + + def on_ab_close(self, node: ABclose) -> None: + """Handle `ABclose` node.""" + + def on_ab_open(self, node: ABopen) -> None: + """Handle `ABopen` node.""" + + def on_ad(self, node: AD) -> None: + """Handle `AD` node.""" + + def on_am_close(self, node: AMclose) -> None: + """Handle `AMclose` node.""" + + def on_am_open(self, node: AMopen) -> None: + """Handle `AMopen` node.""" + + def on_sr_close(self, node: SRclose) -> None: + """Handle `SRclose` node.""" + + def on_sr_open(self, node: SRopen) -> None: + """Handle `SRopen` node.""" + + # Attribute + + def on_ta(self, node: TA) -> None: + """Handle `TA` node.""" + + def on_td(self, node: TD) -> None: + """Handle `TD` node.""" + + def on_tf(self, node: TF) -> None: + """Handle `TF` node.""" + + def on_to(self, node: TO) -> None: + """Handle `TO` node.""" + + # D codes + + def on_d01(self, node: D01) -> None: + """Handle `D01` node.""" + + def on_d02(self, node: D02) -> None: + """Handle `D02` node.""" + + def on_d03(self, node: D03) -> None: + """Handle `D03` node.""" + + def on_dnn(self, node: Dnn) -> None: + """Handle `Dnn` node.""" + + # G codes + + def on_g01(self, node: G01) -> None: + """Handle `G01` node.""" + + def on_g02(self, node: G02) -> None: + """Handle `G02` node.""" + + def on_g03(self, node: G03) -> None: + """Handle `G03` node.""" + + def on_g04(self, node: G04) -> None: + """Handle `G04` node.""" + + def on_g36(self, node: G36) -> None: + """Handle `G36` node.""" + + def on_g37(self, node: G37) -> None: + """Handle `G37` node.""" + + def on_g54(self, node: G54) -> None: + """Handle `G54` node.""" + + def on_g55(self, node: G55) -> None: + """Handle `G55` node.""" + + def on_g70(self, node: G70) -> None: + """Handle `G70` node.""" + + def on_g71(self, node: G71) -> None: + """Handle `G71` node.""" + + def on_g74(self, node: G74) -> None: + """Handle `G74` node.""" + + def on_g75(self, node: G75) -> None: + """Handle `G75` node.""" + + def on_g90(self, node: G90) -> None: + """Handle `G90` node.""" + + def on_g91(self, node: G91) -> None: + """Handle `G91` node.""" + + # Load + + def on_lm(self, node: LM) -> None: + """Handle `LM` node.""" + + def on_ln(self, node: LN) -> None: + """Handle `LN` node.""" + + def on_lp(self, node: LP) -> None: + """Handle `LP` node.""" + + def on_lr(self, node: LR) -> None: + """Handle `LR` node.""" + + def on_ls(self, node: LS) -> None: + """Handle `LS` node.""" + + # M Codes + + def on_m00(self, node: M00) -> None: + """Handle `M00` node.""" + + def on_m01(self, node: M01) -> None: + """Handle `M01` node.""" + + def on_m02(self, node: M02) -> None: + """Handle `M02` node.""" + + # Math + + # Math :: Operators :: Binary + + def on_add(self, node: Add) -> None: + """Handle `Add` node.""" + + def on_div(self, node: Div) -> None: + """Handle `Div` node.""" + + def on_mul(self, node: Mul) -> None: + """Handle `Mul` node.""" + + def on_sub(self, node: Sub) -> None: + """Handle `Sub` node.""" + + # Math :: Operators :: Unary + + def on_neg(self, node: Neg) -> None: + """Handle `Neg` node.""" + + def on_pos(self, node: Pos) -> None: + """Handle `Pos` node.""" + + def on_assignment(self, node: Assignment) -> None: + """Handle `Assignment` node.""" + + def on_constant(self, node: Constant) -> None: + """Handle `Constant` node.""" + + def on_expression(self, node: Expression) -> None: + """Handle `Expression` node.""" + + def on_variable(self, node: Variable) -> None: + """Handle `Variable` node.""" + + # Other + + def on_command_end(self, node: CommandEnd) -> None: + """Handle `CommandEnd` node.""" + + def on_coordinate(self, node: Coordinate) -> None: + """Handle `Coordinate` node.""" + + def on_extended_command_close(self, node: ExtendedCommandClose) -> None: + """Handle `ExtendedCommandClose` node.""" + + def on_extended_command_open(self, node: ExtendedCommandOpen) -> None: + """Handle `ExtendedCommandOpen` node.""" + + # Primitives + + def on_code_0(self, node: Code0) -> None: + """Handle `Code0` node.""" + + def on_code_1(self, node: Code1) -> None: + """Handle `Code1` node.""" + + def on_code_2(self, node: Code2) -> None: + """Handle `Code2` node.""" + + def on_code_4(self, node: Code4) -> None: + """Handle `Code4` node.""" + + def on_code_5(self, node: Code5) -> None: + """Handle `Code5` node.""" + + def on_code_6(self, node: Code6) -> None: + """Handle `Code6` node.""" + + def on_code_7(self, node: Code7) -> None: + """Handle `Code7` node.""" + + def on_code_20(self, node: Code20) -> None: + """Handle `Code20` node.""" + + def on_code_21(self, node: Code21) -> None: + """Handle `Code21` node.""" + + def on_code_22(self, node: Code22) -> None: + """Handle `Code22` node.""" + + # Properties + + def on_fs(self, node: FS) -> None: + """Handle `FS` node.""" + + def on_in(self, node: IN) -> None: + """Handle `IN` node.""" + + def on_ip(self, node: IP) -> None: + """Handle `IP` node.""" + + def on_mo(self, node: MO) -> None: + """Handle `MO` node.""" + + def on_of(self, node: OF) -> None: + """Handle `OF` node.""" From c9ee3afc1adcd0c10b648f6aa67062ee77a835a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 15 Jul 2024 00:45:20 +0200 Subject: [PATCH 02/91] Move math node imports --- src/pygerber/gerberx3/ast/visitor.py | 21 ++++++++++----------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 60b152f6..4aa3712e 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -4,17 +4,6 @@ from typing import TYPE_CHECKING -from pygerber.gerberx3.ast.nodes.math.assignment import Assignment -from pygerber.gerberx3.ast.nodes.math.constant import Constant -from pygerber.gerberx3.ast.nodes.math.expression import Expression -from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add -from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div -from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul -from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub -from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg -from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos -from pygerber.gerberx3.ast.nodes.math.variable import Variable - if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen @@ -53,6 +42,16 @@ from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 + from pygerber.gerberx3.ast.nodes.math.assignment import Assignment + from pygerber.gerberx3.ast.nodes.math.constant import Constant + from pygerber.gerberx3.ast.nodes.math.expression import Expression + from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add + from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div + from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul + from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub + from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg + from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos + from pygerber.gerberx3.ast.nodes.math.variable import Variable from pygerber.gerberx3.ast.nodes.other.command_end import CommandEnd from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate from pygerber.gerberx3.ast.nodes.other.extended_command_close import ( From f0e76e543ada93ab9b39013dcc6c44315c04ed18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 16 Jul 2024 13:02:46 +0200 Subject: [PATCH 03/91] Add pyparsing based parser layout --- .../gerberx3/ast/nodes/d_codes/D01.py | 8 +- .../gerberx3/ast/nodes/d_codes/D02.py | 4 + .../gerberx3/ast/nodes/d_codes/D03.py | 4 + .../gerberx3/ast/nodes/d_codes/Dnn.py | 2 + .../gerberx3/ast/nodes/other/coordinate.py | 5 +- src/pygerber/gerberx3/parser/__init__.py | 2 +- src/pygerber/gerberx3/parser/errors.py | 76 ------- .../gerberx3/parser/native/__init__.py | 3 + src/pygerber/gerberx3/parser/parser.py | 183 ---------------- .../gerberx3/parser/pyparsing/__init__.py | 3 + .../gerberx3/parser/pyparsing/grammar.py | 206 ++++++++++++++++++ .../gerberx3/parser/pyparsing/parser.py | 27 +++ src/pygerber/gerberx3/parser/state.py | 131 ----------- test/gerberx3/test_parser/__init__.py | 0 .../test_parser/test_pyparsing/__init__.py | 0 .../test_parser/test_pyparsing/test_parser.py | 76 +++++++ 16 files changed, 337 insertions(+), 393 deletions(-) delete mode 100644 src/pygerber/gerberx3/parser/errors.py create mode 100644 src/pygerber/gerberx3/parser/native/__init__.py delete mode 100644 src/pygerber/gerberx3/parser/parser.py create mode 100644 src/pygerber/gerberx3/parser/pyparsing/__init__.py create mode 100644 src/pygerber/gerberx3/parser/pyparsing/grammar.py create mode 100644 src/pygerber/gerberx3/parser/pyparsing/parser.py delete mode 100644 src/pygerber/gerberx3/parser/state.py create mode 100644 test/gerberx3/test_parser/__init__.py create mode 100644 test/gerberx3/test_parser/test_pyparsing/__init__.py create mode 100644 test/gerberx3/test_parser/test_pyparsing/test_parser.py diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py index 0401a18f..6edfbfdc 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,11 @@ class D01(Node): """Represents D01 Gerber command.""" + x: Coordinate + y: Coordinate + i: Optional[Coordinate] + j: Optional[Coordinate] + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_d01(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py index 2647cd1a..1f887304 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,9 @@ class D02(Node): """Represents D02 Gerber command.""" + x: Coordinate + y: Coordinate + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_d02(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py index 37027b06..7e0fdf7c 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,9 @@ class D03(Node): """Represents D03 Gerber command.""" + x: Coordinate + y: Coordinate + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_d03(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py index 695add8c..8b0153a0 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py @@ -13,6 +13,8 @@ class Dnn(Node): """Represents DNN Gerber command.""" + value: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_dnn(self) diff --git a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py index ed509050..a14a44b6 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py +++ b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal from pygerber.gerberx3.ast.nodes.base import Node @@ -15,6 +15,9 @@ class Coordinate(Node): """Represents Coordinate node.""" + type: Literal["X", "Y", "I", "J"] + value: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_coordinate(self) diff --git a/src/pygerber/gerberx3/parser/__init__.py b/src/pygerber/gerberx3/parser/__init__.py index 66cb8603..2bb83147 100644 --- a/src/pygerber/gerberx3/parser/__init__.py +++ b/src/pygerber/gerberx3/parser/__init__.py @@ -1 +1 @@ -"""Gerber X3 parer.""" +"""`pygerber.gerberx3.parser` package contains Gerber X3 parser implementations.""" diff --git a/src/pygerber/gerberx3/parser/errors.py b/src/pygerber/gerberx3/parser/errors.py deleted file mode 100644 index 733ee1c9..00000000 --- a/src/pygerber/gerberx3/parser/errors.py +++ /dev/null @@ -1,76 +0,0 @@ -"""Base error classes used in this module.""" - -from __future__ import annotations - -from pygerber.gerberx3.tokenizer.tokens.bases.token import Token - - -class ParserError(ValueError): - """Base class for parser errors. - - Exceptions derived from this exception are exclusively raised in PyGerber's Gerber - X3 Parser. This exception can be used in - `#!python try: ... except ParserError: ...` block to catch all exceptions - raised by Parser while allowing other exceptions to interrupt execution. - """ - - def get_message(self) -> str: - """Get parser error help message.""" - return f"{self.__class__.__qualname__}: {self.__doc__}" - - -class ZeroOmissionNotSupportedError(ParserError): - """Raised when incremental coordinates are selected. (Spec. 8.2.1.2).""" - - -class IncrementalCoordinatesNotSupportedError(ParserError): - """Raised when incremental coordinates are selected. (Spec. 8.2.1.2).""" - - -class UnsupportedCoordinateTypeError(ParserError): - """Raised for unsupported coordinate types.""" - - -class InvalidCoordinateLengthError(ParserError): - """Raised when coordinate string is too long.""" - - -class ParserFatalError(ParserError): - """Raised when parser encounters fatal failure from non-parser specific - exception. - """ - - -class OnUpdateDrawingStateError(ParserError): - """Raised when parser encounters fatal failure from non-parser specific - exception during call to .update_drawing_state() call. - """ - - def __init__(self, token: Token, *args: object) -> None: - super().__init__(*args) - self.token = token - - def __str__(self) -> str: - return f"{self.token} {self.token.get_token_position()}" - - -class UnitNotSetError(ParserError): - """Raised when operation which requires units to be set is executed before units - are set. - """ - - -class ApertureNotDefinedError(ParserError): - """Raised when undefined aperture is selected.""" - - -class CoordinateFormatNotSetError(ParserError): - """Raised when coordinate parser is requested before coordinate format was set.""" - - -class ApertureNotSelectedError(ParserError): - """Raised when attempting to use aperture without selecting it first.""" - - -class ExitParsingProcessInterrupt(Exception): # noqa: N818 - """Raised to stop parsing.""" diff --git a/src/pygerber/gerberx3/parser/native/__init__.py b/src/pygerber/gerberx3/parser/native/__init__.py new file mode 100644 index 00000000..25350d49 --- /dev/null +++ b/src/pygerber/gerberx3/parser/native/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.parser.native` package will contain C++/Rust implementation +of Gerber X3 parser. +""" diff --git a/src/pygerber/gerberx3/parser/parser.py b/src/pygerber/gerberx3/parser/parser.py deleted file mode 100644 index 3a4facc5..00000000 --- a/src/pygerber/gerberx3/parser/parser.py +++ /dev/null @@ -1,183 +0,0 @@ -"""GerberX3 format parser.""" - -from __future__ import annotations - -import logging -from enum import Enum -from typing import TYPE_CHECKING, Callable, Generator, Optional - -from pygerber.backend.rasterized_2d.backend_cls import Rasterized2DBackend -from pygerber.gerberx3.parser.errors import ( - ExitParsingProcessInterrupt, - OnUpdateDrawingStateError, - ParserError, -) -from pygerber.gerberx3.parser.state import State - -if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.backend.abstract.draw_commands_handle import DrawCommandsHandle - from pygerber.gerberx3.tokenizer.tokens.bases.token import Token - from pygerber.gerberx3.tokenizer.tokens.groups.ast import AST - - -class Parser: - """Gerber X3 parser object.""" - - def __init__( - self, - options: Optional[ParserOptions] = None, - ) -> None: - """Initialize parser. - - Parameters - ---------- - options : ParserOptions | None - Additional options for modifying parser behavior. - - """ - self.options = ParserOptions() if options is None else options - self.state = ( - State() - if self.options.initial_state is None - else self.options.initial_state - ) - self.draw_actions: list[DrawCommand] = [] - - @property - def backend(self) -> Backend: - """Get reference to backend object.""" - return self.options.backend - - def parse(self, ast: AST) -> DrawCommandsHandle: - """Parse token stack.""" - for _ in self.parse_iter(ast): - pass - - return self.get_draw_commands_handle() - - def parse_iter(self, ast: AST) -> Generator[tuple[Token, State], None, None]: - """Iterate over tokens in stack and parse them.""" - self.state = ( - State() - if self.options.initial_state is None - else self.options.initial_state - ) - self.draw_actions = [] - - try: - for token in ast: - self._update_drawing_state(token) - - yield token, self.state - - except ExitParsingProcessInterrupt: - pass - - def get_draw_commands_handle(self) -> DrawCommandsHandle: - """Return handle to drawing commands.""" - return self.backend.get_draw_commands_handle_cls()( - self.draw_actions, - self.backend, - ) - - def _update_drawing_state(self, token: Token) -> None: - try: - self.state, actions = token.update_drawing_state(self.state, self.backend) - if actions is not None: - self.draw_actions.extend(actions) - - except ExitParsingProcessInterrupt: - return - - except Exception as e: - if self.options.on_update_drawing_state_error == ParserOnErrorAction.Ignore: - pass - - elif ( - self.options.on_update_drawing_state_error == ParserOnErrorAction.Raise - ): - if not isinstance(e, ParserError): - raise OnUpdateDrawingStateError(token) from e - - raise - - elif self.options.on_update_drawing_state_error == ParserOnErrorAction.Warn: - logging.warning( - "Encountered fatal error during call to update_drawing_state() " - "of '%s' token %s. Parser will skip this token and continue.", - token, - token.get_token_position(), - ) - else: - self.options.on_update_drawing_state_error(e, self, token) - - -class StatePreservingParser(Parser): - """Parser class which preserves all states for all tokens. - - Caution: High memory consumption. - """ - - state_index: list[tuple[int, State]] - - def __init__(self, options: ParserOptions | None = None) -> None: - super().__init__(options) - self.state_index = [] - - def parse(self, ast: AST) -> DrawCommandsHandle: - """Parse token stack.""" - self.state_index = [] - current_state = self.state - - for token, state in self.parse_iter(ast): - if state != current_state: - self.state_index.append((token.location, state)) - - return self.get_draw_commands_handle() - - def get_state_at(self, token: Token) -> State: - """Get state at given token. - - Parser must have been already used to parse some AST. - """ - for location, state in self.state_index: - if location <= token.location: - continue - return state - - return self.state - - -class ParserOnErrorAction(Enum): - """Possible error actions.""" - - Ignore = "ignore" - """Ignore parser errors. Errors which occurred will not be signaled. May yield - unexpected results for broken files, with missing draw commands or even more - significant errors.""" - - Warn = "warn" - """Warn on parser error. Parser will log warning message about what went wrong. - Best for supporting wide range of files without silently ignoring errors in code.""" - - Raise = "raise" - """Raise exception whenever parser encounters error. Will completely break out of - parsing process, making it impossible to render slightly malformed files.""" - - -class ParserOptions: - """Container class for Gerber parser options.""" - - def __init__( - self, - backend: Backend | None = None, - initial_state: State | None = None, - on_update_drawing_state_error: Callable[[Exception, Parser, Token], None] - | ParserOnErrorAction = ParserOnErrorAction.Raise, - ) -> None: - """Initialize options.""" - self.backend = Rasterized2DBackend() if backend is None else backend - self.initial_state = initial_state - self.on_update_drawing_state_error = on_update_drawing_state_error diff --git a/src/pygerber/gerberx3/parser/pyparsing/__init__.py b/src/pygerber/gerberx3/parser/pyparsing/__init__.py new file mode 100644 index 00000000..53756678 --- /dev/null +++ b/src/pygerber/gerberx3/parser/pyparsing/__init__.py @@ -0,0 +1,3 @@ +"""`pygerber.gerberx3.parser.pyparsing` package contains Gerber X3 parser implementation +based on pyparsing library. +""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py new file mode 100644 index 00000000..6a9d4bd6 --- /dev/null +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -0,0 +1,206 @@ +"""`pygerber.gerberx3.parser.pyparsing.grammar` module contains the Gerber X3 grammar +implemented using the pyparsing library. +""" + +from __future__ import annotations + +from typing import ClassVar, Type, TypeVar, cast + +import pyparsing as pp +from pydantic import ValidationError + +from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 +from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 +from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 +from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn +from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 +from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 +from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 +from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 +from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 +from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 +from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 +from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 +from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 +from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 +from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 +from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 +from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 +from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 +from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 +from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 +from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 +from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate + + +def g(value: int, cls: Type[Node]) -> pp.ParserElement: + """Create a parser element capable of parsing particular G-code.""" + element = pp.Regex(r"G0*" + str(value)).set_name(f"G{value}") + return element.setParseAction(lambda: cls()) + + +def m(value: int, cls: Type[Node]) -> pp.ParserElement: + """Create a parser element capable of parsing particular D-code.""" + element = pp.Regex(r"M0*" + str(value)).set_name(f"M{value}") + return element.setParseAction(lambda: cls()) + + +T = TypeVar("T", bound=Node) + + +class Grammar: + """Internal representation of the Gerber X3 grammar.""" + + DEFAULT: ClassVar[pp.ParserElement] + + def __init__(self, ast_node_class_overrides: dict[str, Type[Node]]) -> None: + self.ast_node_class_overrides = ast_node_class_overrides + + def build(self) -> pp.ParserElement: + """Build the grammar.""" + return pp.OneOrMore(self.g_codes() | self.m_codes() | self.d_codes()) + + def d_codes(self) -> pp.ParserElement: + """Create a parser element capable of parsing D-codes.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> D01: + parse_result_token_list = tokens.as_list() + token_list = cast(list[Coordinate], parse_result_token_list) + try: + return self.get_cls(D01)( + x=token_list[0], + y=token_list[1], + i=token_list[2] if len(token_list) >= 4 else None, # noqa: PLR2004 + j=token_list[3] if len(token_list) >= 5 else None, # noqa: PLR2004 + ) + except ValidationError as e: + raise pp.ParseFatalException(s, loc, "Invalid D01") from e + + d01 = ( + ( + self.coordinate + + self.coordinate + + pp.Opt(self.coordinate) + + pp.Opt(self.coordinate) + + pp.Regex(r"D0*1") + ) + .set_parse_action(_) + .set_name("D01") + ) + + def _(s: str, loc: int, tokens: pp.ParseResults) -> D02: + parse_result_token_list = tokens.as_list() + token_list = cast(list[Coordinate], parse_result_token_list) + try: + return self.get_cls(D02)( + x=token_list[0], + y=token_list[1], + ) + except ValidationError as e: + raise pp.ParseFatalException(s, loc, "Invalid D02") from e + + d02 = ( + (self.coordinate + self.coordinate + pp.Regex(r"D0*2")) + .set_parse_action(_) + .set_name("D02") + ) + + def _(s: str, loc: int, tokens: pp.ParseResults) -> D03: + parse_result_token_list = tokens.as_list() + token_list = cast(list[Coordinate], parse_result_token_list) + try: + return self.get_cls(D03)( + x=token_list[0], + y=token_list[1], + ) + except ValidationError as e: + raise pp.ParseFatalException(s, loc, "Invalid D03") from e + + d03 = ( + (self.coordinate + self.coordinate + pp.Regex(r"D0*3")) + .set_parse_action(_) + .set_name("D03") + ) + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Dnn: + parse_result_token_list = tokens.as_list() + token_list = cast(list[Coordinate], parse_result_token_list) + value = token_list[0] + assert isinstance(value, str) + assert value.startswith("D") + assert len(value) > 1 + + try: + return self.get_cls(Dnn)(value=value) + except ValidationError as e: + raise pp.ParseFatalException(s, loc, "Invalid Dnn") from e + + dnn = pp.Regex(r"D0*[0-9]*").set_parse_action(_).set_name("Dnn") + + return d01 | d02 | d03 | dnn + + def get_cls(self, node_cls: Type[T]) -> Type[T]: + """Get the class of the node.""" + return self.ast_node_class_overrides.get(node_cls.__qualname__, node_cls) + + @pp.cached_property + def coordinate(self) -> pp.ParserElement: + """Create a parser element capable of parsing coordinates.""" + + def _(tokens: pp.ParseResults) -> Coordinate: + type_, value = tokens.as_list() + assert isinstance(type_, str), type(type_) + assert type_ in ("X", "Y", "I", "J") + assert isinstance(value, str) + + return self.get_cls(Coordinate)(type=type_, value=value) + + return ( + ( + pp.oneOf(("X", "Y", "I", "J")).set_name("coordinate_type") + + pp.Regex(r"[+-]?[0-9]+").set_name("coordinate_value") + ) + .set_parse_action(_) + .set_name("coordinate") + ) + + def g_codes(self) -> pp.ParserElement: + """Create a parser element capable of parsing G-codes.""" + return pp.MatchFirst( + [ + g(value, self.get_cls(cls)) + for (value, cls) in ( + (1, G01), + (2, G02), + (3, G03), + (4, G04), + (36, G36), + (37, G37), + (54, G54), + (55, G55), + (70, G70), + (71, G71), + (74, G74), + (75, G75), + (90, G90), + (91, G91), + ) + ] + ) + + def m_codes(self) -> pp.ParserElement: + """Create a parser element capable of parsing M-codes.""" + return pp.MatchFirst( + [ + m(value, self.get_cls(cls)) + for (value, cls) in ( + (0, M00), + (1, M01), + (2, M02), + ) + ] + ) + + +Grammar.DEFAULT = Grammar({}).build() diff --git a/src/pygerber/gerberx3/parser/pyparsing/parser.py b/src/pygerber/gerberx3/parser/pyparsing/parser.py new file mode 100644 index 00000000..762f26c8 --- /dev/null +++ b/src/pygerber/gerberx3/parser/pyparsing/parser.py @@ -0,0 +1,27 @@ +"""`pygerber.gerberx3.parser.pyparsing.parser` module contains Gerber X3 parser +implementation based on pyparsing library. +""" + +from __future__ import annotations + +from typing import Optional, Type, cast + +from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.parser.pyparsing.grammar import Grammar + + +class Parser: + """Gerber X3 parser implementation.""" + + def __init__( + self, ast_node_class_overrides: Optional[dict[str, Type[Node]]] = None + ) -> None: + if ast_node_class_overrides is not None: + self.grammar = Grammar(ast_node_class_overrides).build() + else: + self.grammar = Grammar.DEFAULT + + def parse(self, code: str) -> list[Node]: + """Parse the input.""" + parse_result = self.grammar.parseString(code) + return cast(list[Node], parse_result.as_list()) diff --git a/src/pygerber/gerberx3/parser/state.py b/src/pygerber/gerberx3/parser/state.py deleted file mode 100644 index bb5726ff..00000000 --- a/src/pygerber/gerberx3/parser/state.py +++ /dev/null @@ -1,131 +0,0 @@ -"""Global drawing state containing configuration which can be altered by tokens.""" - -from __future__ import annotations - -from decimal import Decimal -from typing import Dict, List, Optional - -from pydantic import Field - -from pygerber.backend.abstract.aperture_handle import PublicApertureHandle -from pygerber.common.frozen_general_model import FrozenGeneralModel -from pygerber.gerberx3.math.offset import Offset -from pygerber.gerberx3.math.vector_2d import Vector2D -from pygerber.gerberx3.parser.errors import ( - ApertureNotSelectedError, - CoordinateFormatNotSetError, - UnitNotSetError, -) -from pygerber.gerberx3.state_enums import DrawMode, Mirroring, Polarity, Unit -from pygerber.gerberx3.tokenizer.tokens.coordinate import Coordinate, CoordinateType -from pygerber.gerberx3.tokenizer.tokens.dnn_select_aperture import ApertureID -from pygerber.gerberx3.tokenizer.tokens.fs_coordinate_format import ( - CoordinateParser, -) -from pygerber.gerberx3.tokenizer.tokens.macro.am_macro import MacroDefinition - - -class State(FrozenGeneralModel): - """GerberX3 interpreter state.""" - - current_position: Vector2D = Vector2D(x=Offset.NULL, y=Offset.NULL) - - # MO | Mode | Sets the unit to mm or inch | 4.2.1 - draw_units: Optional[Unit] = None - # FS | Format specification | Sets the coordinate format, | 4.2.2 - # | | e.g. the number of decimals - coordinate_parser: Optional[CoordinateParser] = None - # Dnn | (nn≥10) | Sets the current aperture to D code nn. | 4.6 - current_aperture: Optional[PublicApertureHandle] = None - # G01 | | Sets linear/circular mode to linear. | 4.7.1 - # G02 | | Sets linear/circular mode to clockwise circular | 4.7.2 - # G03 | | Sets linear/circular mode to counterclockwise circular | 4.7.3 - draw_mode: DrawMode = DrawMode.Linear - # LP | | Load polarity | Loads the polarity object transformation | 4.9.2 - # parameter. - polarity: Polarity = Polarity.Dark - # LM | | Load mirroring | Loads the mirror object transformation | 4.9.3 - # parameter. - mirroring: Mirroring = Mirroring.NoMirroring - # LR | Load rotation | Loads the rotation object transformation | 4.9.4 - # parameter. - rotation: Decimal = Decimal("0.0") - - region_boundary_points: List[Vector2D] = Field(default_factory=list) - """Points defining the shape of the region.""" - - # LS | Load scaling | Loads the scale object transformation | 4.9.5 - # parameter - scaling: Decimal = Decimal("1.0") - # G36 | | Starts a region statement which creates a region by | 4.10 - # | | defining its contours. - # G37 | | Ends the region statement. | 4.10 - is_region: bool = False - # AB | | Aperture blockOpens a block aperture statement and | 4.11 - # | | assigns its aperture number or closes a block aperture | - # | | statement. | - is_aperture_block: bool = False - # SR | | Step and repeatOpen or closes a step and repeat | 4.11 - # | | statement. | - is_step_and_repeat: bool = False - # TF | | Attribute on fileSet a file attribute. | 5.3 - # TD | | Attribute deleteDelete one or all attributes in the | 5.5 - # | | dictionary. | - file_attributes: Dict[str, str] = Field(default_factory=dict) - # G75 | | Sets multi quadrant mode - # G74 | | Sets single quadrant mode - is_multi_quadrant: bool = False - - is_output_image_negation_required: bool = False - """In Gerber specification deprecated IP command is mentioned. - It can set image polarity to either positive, the usual one, or to negative. - Under negative image polarity, image generation is different. Its purpose is to - create a negative image, clear areas in a dark background. The entire image plane - in the background is initially dark instead of clear. The effect of dark and clear - polarity is toggled. The entire image is simply reversed, dark becomes white and - vice versa. - This effect can be achieved by simply inverting colors of output image. - """ - - apertures: Dict[ApertureID, PublicApertureHandle] = Field(default_factory=dict) - """Collection of all apertures defined until given point in code.""" - - macros: Dict[str, MacroDefinition] = Field(default_factory=dict) - """Collection of all macros defined until given point in code.""" - - def get_units(self) -> Unit: - """Get drawing unit or raise UnitNotSetError.""" - if self.draw_units is None: - raise UnitNotSetError - return self.draw_units - - def get_coordinate_parser(self) -> CoordinateParser: - """Get coordinate parser or raise CoordinateFormatNotSetError.""" - if self.coordinate_parser is None: - raise CoordinateFormatNotSetError - return self.coordinate_parser - - def get_current_aperture(self) -> PublicApertureHandle: - """Get current aperture or raise ApertureNotSelectedError.""" - if self.current_aperture is None: - raise ApertureNotSelectedError - return self.current_aperture - - def parse_coordinate(self, coordinate: Coordinate) -> Offset: - """Parse, include substitution with current and conversion to Offset.""" - if coordinate.coordinate_type == CoordinateType.MISSING_X: - return self.current_position.x - - if coordinate.coordinate_type == CoordinateType.MISSING_Y: - return self.current_position.y - - if coordinate.coordinate_type == CoordinateType.MISSING_I: - return Offset.NULL - - if coordinate.coordinate_type == CoordinateType.MISSING_J: - return Offset.NULL - - return Offset.new( - self.get_coordinate_parser().parse(coordinate), - unit=self.get_units(), - ) diff --git a/test/gerberx3/test_parser/__init__.py b/test/gerberx3/test_parser/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/gerberx3/test_parser/test_pyparsing/__init__.py b/test/gerberx3/test_parser/test_pyparsing/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/gerberx3/test_parser/test_pyparsing/test_parser.py b/test/gerberx3/test_parser/test_pyparsing/test_parser.py new file mode 100644 index 00000000..247d1814 --- /dev/null +++ b/test/gerberx3/test_parser/test_pyparsing/test_parser.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from dataclasses import dataclass + +import pytest + +from pygerber.gerberx3.parser.pyparsing.parser import Parser +from test.gerberx3.common import ( + GERBER_ASSETS_INDEX, + Asset, + CaseGenerator, + ConfigBase, +) + + +@dataclass +class Config(ConfigBase): + """Configuration for the test.""" + + dpmm: int = 20 + as_expression: bool = False + compare_with_reference: bool = True + + +common_case_generator_config = { + "macro.*": Config(dpmm=100), + "incomplete.*": Config(skip=True), +} + + +parametrize = CaseGenerator( + GERBER_ASSETS_INDEX, + { + "A64-OLinuXino-rev-G.*": Config(dpmm=40), + "flashes.*": Config(dpmm=40), + "flashes.00_circle+h_4_tbh.grb": Config( + xfail=True, + xfail_message="Should warn, no mechanism implemented yet.", + ), + "ucamco.4.9.1.*": Config(dpmm=100), + "ucamco.4.9.6.*": Config(dpmm=300), + "ucamco.4.10.4.9.*": Config(dpmm=50), + "ucamco.4.11.4.*": Config(dpmm=1), + "expressions.*": Config(as_expression=True), + **common_case_generator_config, + }, + Config, +).parametrize + + +parametrize = CaseGenerator( + GERBER_ASSETS_INDEX, + { + "A64-OLinuXino-rev-G.*": Config(skip=True), + "A64-OLinuXino-rev-G.A64-OlinuXino_Rev_G-B_Cu.gbr": Config(skip=False), + "ATMEGA328-Motor-Board.*": Config(skip=True), + "ATMEGA328-Motor-Board.ATMEGA328_Motor_Board-B.Cu.gbl": Config(skip=False), + "expressions.*": Config(as_expression=True), + **common_case_generator_config, + }, + Config, +).parametrize + + +@parametrize +def test_pyparsing_parser_grammar(asset: Asset, config: Config) -> None: + if config.skip: + pytest.skip(reason=config.skip_reason) + + if config.xfail: + pytest.xfail(config.xfail_message) + + source = asset.absolute_path.read_text() + parser = Parser() + + parser.parse(source) From 98778980f7181b9cf8fe7737738659fa33f26d11 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 16 Jul 2024 13:03:11 +0200 Subject: [PATCH 04/91] Add assets for testing different token types --- test/assets/gerberx3/tokens/d_codes/D01.grb | 2 ++ test/assets/gerberx3/tokens/d_codes/D02.grb | 1 + test/assets/gerberx3/tokens/d_codes/D03.grb | 1 + test/assets/gerberx3/tokens/d_select/D11.grb | 2 ++ test/assets/gerberx3/tokens/d_select/D12.grb | 2 ++ test/assets/gerberx3/tokens/d_select/D301.grb | 2 ++ test/assets/gerberx3/tokens/d_select/D999.grb | 2 ++ test/assets/gerberx3/tokens/fs/FSLAX26Y26.grb | 1 + test/assets/gerberx3/tokens/fs/FSLAX66Y66.grb | 1 + test/assets/gerberx3/tokens/fs/FSLIX26Y26.grb | 1 + test/assets/gerberx3/tokens/fs/FSTIX26Y27.grb | 1 + test/assets/gerberx3/tokens/g_codes/G01.grb | 3 +++ test/assets/gerberx3/tokens/g_codes/G02.grb | 3 +++ test/assets/gerberx3/tokens/g_codes/G03.grb | 3 +++ test/assets/gerberx3/tokens/g_codes/G04.grb | 3 +++ test/assets/gerberx3/tokens/g_codes/G04_text.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G36.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G37.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G54.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G55.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G70.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G71.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G74.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G75.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G90.grb | 2 ++ test/assets/gerberx3/tokens/g_codes/G91.grb | 2 ++ test/assets/gerberx3/tokens/set_codes/as.grb | 2 ++ test/assets/gerberx3/tokens/set_codes/mo.grb | 2 ++ 28 files changed, 54 insertions(+) create mode 100644 test/assets/gerberx3/tokens/d_codes/D01.grb create mode 100644 test/assets/gerberx3/tokens/d_codes/D02.grb create mode 100644 test/assets/gerberx3/tokens/d_codes/D03.grb create mode 100644 test/assets/gerberx3/tokens/d_select/D11.grb create mode 100644 test/assets/gerberx3/tokens/d_select/D12.grb create mode 100644 test/assets/gerberx3/tokens/d_select/D301.grb create mode 100644 test/assets/gerberx3/tokens/d_select/D999.grb create mode 100644 test/assets/gerberx3/tokens/fs/FSLAX26Y26.grb create mode 100644 test/assets/gerberx3/tokens/fs/FSLAX66Y66.grb create mode 100644 test/assets/gerberx3/tokens/fs/FSLIX26Y26.grb create mode 100644 test/assets/gerberx3/tokens/fs/FSTIX26Y27.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G01.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G02.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G03.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G04.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G04_text.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G36.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G37.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G54.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G55.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G70.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G71.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G74.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G75.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G90.grb create mode 100644 test/assets/gerberx3/tokens/g_codes/G91.grb create mode 100644 test/assets/gerberx3/tokens/set_codes/as.grb create mode 100644 test/assets/gerberx3/tokens/set_codes/mo.grb diff --git a/test/assets/gerberx3/tokens/d_codes/D01.grb b/test/assets/gerberx3/tokens/d_codes/D01.grb new file mode 100644 index 00000000..e9a837c5 --- /dev/null +++ b/test/assets/gerberx3/tokens/d_codes/D01.grb @@ -0,0 +1,2 @@ +X0000000Y2000000D01* +X060000Y000000I000000J060000D01* diff --git a/test/assets/gerberx3/tokens/d_codes/D02.grb b/test/assets/gerberx3/tokens/d_codes/D02.grb new file mode 100644 index 00000000..d13d35f4 --- /dev/null +++ b/test/assets/gerberx3/tokens/d_codes/D02.grb @@ -0,0 +1 @@ +X0000000Y2000000D02* diff --git a/test/assets/gerberx3/tokens/d_codes/D03.grb b/test/assets/gerberx3/tokens/d_codes/D03.grb new file mode 100644 index 00000000..2fa0782c --- /dev/null +++ b/test/assets/gerberx3/tokens/d_codes/D03.grb @@ -0,0 +1 @@ +X1500000Y1500000D03* diff --git a/test/assets/gerberx3/tokens/d_select/D11.grb b/test/assets/gerberx3/tokens/d_select/D11.grb new file mode 100644 index 00000000..e529776d --- /dev/null +++ b/test/assets/gerberx3/tokens/d_select/D11.grb @@ -0,0 +1,2 @@ +D11* +D011* diff --git a/test/assets/gerberx3/tokens/d_select/D12.grb b/test/assets/gerberx3/tokens/d_select/D12.grb new file mode 100644 index 00000000..e4d7933a --- /dev/null +++ b/test/assets/gerberx3/tokens/d_select/D12.grb @@ -0,0 +1,2 @@ +D12* +D012* diff --git a/test/assets/gerberx3/tokens/d_select/D301.grb b/test/assets/gerberx3/tokens/d_select/D301.grb new file mode 100644 index 00000000..5429c1d3 --- /dev/null +++ b/test/assets/gerberx3/tokens/d_select/D301.grb @@ -0,0 +1,2 @@ +D301* +D0301* diff --git a/test/assets/gerberx3/tokens/d_select/D999.grb b/test/assets/gerberx3/tokens/d_select/D999.grb new file mode 100644 index 00000000..690601c0 --- /dev/null +++ b/test/assets/gerberx3/tokens/d_select/D999.grb @@ -0,0 +1,2 @@ +D999* +D0999* diff --git a/test/assets/gerberx3/tokens/fs/FSLAX26Y26.grb b/test/assets/gerberx3/tokens/fs/FSLAX26Y26.grb new file mode 100644 index 00000000..ba50de88 --- /dev/null +++ b/test/assets/gerberx3/tokens/fs/FSLAX26Y26.grb @@ -0,0 +1 @@ +%FSLAX26Y26*% diff --git a/test/assets/gerberx3/tokens/fs/FSLAX66Y66.grb b/test/assets/gerberx3/tokens/fs/FSLAX66Y66.grb new file mode 100644 index 00000000..e9f73c69 --- /dev/null +++ b/test/assets/gerberx3/tokens/fs/FSLAX66Y66.grb @@ -0,0 +1 @@ +%FSLIX66Y66*% diff --git a/test/assets/gerberx3/tokens/fs/FSLIX26Y26.grb b/test/assets/gerberx3/tokens/fs/FSLIX26Y26.grb new file mode 100644 index 00000000..9211cacd --- /dev/null +++ b/test/assets/gerberx3/tokens/fs/FSLIX26Y26.grb @@ -0,0 +1 @@ +%FSLAX66Y66*% diff --git a/test/assets/gerberx3/tokens/fs/FSTIX26Y27.grb b/test/assets/gerberx3/tokens/fs/FSTIX26Y27.grb new file mode 100644 index 00000000..e2b95967 --- /dev/null +++ b/test/assets/gerberx3/tokens/fs/FSTIX26Y27.grb @@ -0,0 +1 @@ +%FSTAX66Y66*% diff --git a/test/assets/gerberx3/tokens/g_codes/G01.grb b/test/assets/gerberx3/tokens/g_codes/G01.grb new file mode 100644 index 00000000..f06a0644 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G01.grb @@ -0,0 +1,3 @@ +G1* +G01* +G001* diff --git a/test/assets/gerberx3/tokens/g_codes/G02.grb b/test/assets/gerberx3/tokens/g_codes/G02.grb new file mode 100644 index 00000000..9d6ca973 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G02.grb @@ -0,0 +1,3 @@ +G2* +G02* +G002* diff --git a/test/assets/gerberx3/tokens/g_codes/G03.grb b/test/assets/gerberx3/tokens/g_codes/G03.grb new file mode 100644 index 00000000..c7bfeb5e --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G03.grb @@ -0,0 +1,3 @@ +G3* +G03* +G003* diff --git a/test/assets/gerberx3/tokens/g_codes/G04.grb b/test/assets/gerberx3/tokens/g_codes/G04.grb new file mode 100644 index 00000000..836f326c --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G04.grb @@ -0,0 +1,3 @@ +G4* +G04* +G004* diff --git a/test/assets/gerberx3/tokens/g_codes/G04_text.grb b/test/assets/gerberx3/tokens/g_codes/G04_text.grb new file mode 100644 index 00000000..5dde3b7a --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G04_text.grb @@ -0,0 +1,2 @@ +G04 Comment text :D * +G04Comment text :D* diff --git a/test/assets/gerberx3/tokens/g_codes/G36.grb b/test/assets/gerberx3/tokens/g_codes/G36.grb new file mode 100644 index 00000000..53b5748d --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G36.grb @@ -0,0 +1,2 @@ +G36* +G036* diff --git a/test/assets/gerberx3/tokens/g_codes/G37.grb b/test/assets/gerberx3/tokens/g_codes/G37.grb new file mode 100644 index 00000000..6b7cf47d --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G37.grb @@ -0,0 +1,2 @@ +G37* +G037* diff --git a/test/assets/gerberx3/tokens/g_codes/G54.grb b/test/assets/gerberx3/tokens/g_codes/G54.grb new file mode 100644 index 00000000..eacc17f9 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G54.grb @@ -0,0 +1,2 @@ +G54D11* +G054D11* diff --git a/test/assets/gerberx3/tokens/g_codes/G55.grb b/test/assets/gerberx3/tokens/g_codes/G55.grb new file mode 100644 index 00000000..975517c2 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G55.grb @@ -0,0 +1,2 @@ +G55X0Y0D03* +G055X0Y0D03* diff --git a/test/assets/gerberx3/tokens/g_codes/G70.grb b/test/assets/gerberx3/tokens/g_codes/G70.grb new file mode 100644 index 00000000..e11c6a97 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G70.grb @@ -0,0 +1,2 @@ +G70* +G070* diff --git a/test/assets/gerberx3/tokens/g_codes/G71.grb b/test/assets/gerberx3/tokens/g_codes/G71.grb new file mode 100644 index 00000000..f14637a2 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G71.grb @@ -0,0 +1,2 @@ +G71* +G071* diff --git a/test/assets/gerberx3/tokens/g_codes/G74.grb b/test/assets/gerberx3/tokens/g_codes/G74.grb new file mode 100644 index 00000000..d6cc6c2b --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G74.grb @@ -0,0 +1,2 @@ +G74* +G074* diff --git a/test/assets/gerberx3/tokens/g_codes/G75.grb b/test/assets/gerberx3/tokens/g_codes/G75.grb new file mode 100644 index 00000000..6eea0067 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G75.grb @@ -0,0 +1,2 @@ +G75* +G075* diff --git a/test/assets/gerberx3/tokens/g_codes/G90.grb b/test/assets/gerberx3/tokens/g_codes/G90.grb new file mode 100644 index 00000000..56d26994 --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G90.grb @@ -0,0 +1,2 @@ +G90* +G090* diff --git a/test/assets/gerberx3/tokens/g_codes/G91.grb b/test/assets/gerberx3/tokens/g_codes/G91.grb new file mode 100644 index 00000000..1f41ef3f --- /dev/null +++ b/test/assets/gerberx3/tokens/g_codes/G91.grb @@ -0,0 +1,2 @@ +G91* +G091* diff --git a/test/assets/gerberx3/tokens/set_codes/as.grb b/test/assets/gerberx3/tokens/set_codes/as.grb new file mode 100644 index 00000000..db7c052d --- /dev/null +++ b/test/assets/gerberx3/tokens/set_codes/as.grb @@ -0,0 +1,2 @@ +%ASAYBX*% +%ASAXBY*% diff --git a/test/assets/gerberx3/tokens/set_codes/mo.grb b/test/assets/gerberx3/tokens/set_codes/mo.grb new file mode 100644 index 00000000..a034bf6a --- /dev/null +++ b/test/assets/gerberx3/tokens/set_codes/mo.grb @@ -0,0 +1,2 @@ +%MOMM*% +%MOIN*% From fa46cd8ff237285abb8e419bf1930f7537e8a4cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 17 Jul 2024 00:28:24 +0200 Subject: [PATCH 05/91] Remove deprecated Parser implementation --- src/pygerber/console/commands.py | 92 +---- src/pygerber/gerberx3/api/__init__.py | 42 --- src/pygerber/gerberx3/api/_layers.py | 340 ------------------ .../language_server/_internals/document.py | 40 +-- .../tokenizer/tokens/ab_block_aperture.py | 29 +- .../tokenizer/tokens/ad_define_aperture.py | 237 +----------- .../tokenizer/tokens/as_axis_select.py | 15 +- .../gerberx3/tokenizer/tokens/bases/token.py | 48 +-- .../gerberx3/tokenizer/tokens/d01_draw.py | 180 +--------- .../gerberx3/tokenizer/tokens/d02_move.py | 42 +-- .../gerberx3/tokenizer/tokens/d03_flash.py | 59 +-- .../tokenizer/tokens/dnn_select_aperture.py | 23 +- .../tokenizer/tokens/fs_coordinate_format.py | 46 +-- .../tokenizer/tokens/g01_set_linear.py | 21 +- .../tokens/g02_set_clockwise_circular.py | 21 +- .../g03_set_counterclockwise_circular.py | 21 +- .../tokenizer/tokens/g36_begin_region.py | 25 +- .../tokenizer/tokens/g37_end_region.py | 34 +- .../tokenizer/tokens/g54_select_aperture.py | 15 +- .../tokenizer/tokens/g70_set_unit_inch.py | 35 +- .../tokenizer/tokens/g71_set_unit_mm.py | 30 +- .../tokenizer/tokens/g74_single_quadrant.py | 22 +- .../tokenizer/tokens/g75_multi_quadrant.py | 20 +- .../tokens/g90_set_coordinate_absolute.py | 25 +- .../tokens/g91_set_coordinate_incremental.py | 24 +- .../tokenizer/tokens/in_image_name.py | 15 +- .../tokenizer/tokens/ip_image_polarity.py | 24 +- .../tokenizer/tokens/lm_load_mirroring.py | 20 +- .../gerberx3/tokenizer/tokens/ln_load_name.py | 15 +- .../tokenizer/tokens/lp_load_polarity.py | 20 +- .../tokenizer/tokens/lr_load_rotation.py | 20 +- .../tokenizer/tokens/ls_load_scaling.py | 20 +- .../tokenizer/tokens/m00_program_stop.py | 14 +- .../tokenizer/tokens/m01_optional_stop.py | 14 +- .../tokenizer/tokens/m02_end_of_file.py | 14 +- .../tokenizer/tokens/macro/am_macro.py | 43 +-- .../tokens/macro/expressions/binary.py | 17 - .../macro/expressions/macro_expression.py | 12 - .../macro/expressions/numeric_constant.py | 7 - .../tokens/macro/expressions/unary.py | 16 - .../tokens/macro/expressions/variable_name.py | 7 - .../tokens/macro/statements/code_1_circle.py | 18 - .../tokens/macro/statements/statement.py | 16 - .../macro/statements/variable_assignment.py | 16 - .../gerberx3/tokenizer/tokens/mo_unit_mode.py | 32 +- .../tokenizer/tokens/of_image_offset.py | 15 +- test/examples/readme_example_1.py | 17 - test/examples/readme_example_2.py | 33 -- test/examples/render_copper_from_buffer.py | 42 --- .../render_copper_from_buffer_into_buffer.py | 47 --- test/examples/render_copper_from_path.py | 24 -- .../render_copper_from_path_into_buffer.py | 30 -- test/examples/render_copper_from_string.py | 29 -- .../render_copper_from_string_into_buffer.py | 36 -- test/examples/test_examples.py | 38 -- .../test_parser2/test_parser2hooks.py | 10 +- 56 files changed, 49 insertions(+), 2118 deletions(-) delete mode 100644 src/pygerber/gerberx3/api/_layers.py delete mode 100644 test/examples/readme_example_1.py delete mode 100644 test/examples/readme_example_2.py delete mode 100644 test/examples/render_copper_from_buffer.py delete mode 100644 test/examples/render_copper_from_buffer_into_buffer.py delete mode 100644 test/examples/render_copper_from_path.py delete mode 100644 test/examples/render_copper_from_path_into_buffer.py delete mode 100644 test/examples/render_copper_from_string.py delete mode 100644 test/examples/render_copper_from_string_into_buffer.py diff --git a/src/pygerber/console/commands.py b/src/pygerber/console/commands.py index d431da99..34680e16 100644 --- a/src/pygerber/console/commands.py +++ b/src/pygerber/console/commands.py @@ -2,17 +2,11 @@ from __future__ import annotations -from pathlib import Path -from typing import Generator, Optional, TextIO +from typing import Generator import click import pygerber -from pygerber.console.raster_2d_style import ( - STYLE_TO_COLOR_SCHEME, - get_color_scheme_from_style, -) -from pygerber.gerberx3.api import Rasterized2DLayer, Rasterized2DLayerParams from pygerber.gerberx3.api.v2 import ( DEFAULT_ALPHA_COLOR_MAP, DEFAULT_COLOR_MAP, @@ -32,90 +26,6 @@ def main() -> None: """ -@main.command("raster-2d") -@click.argument("source", type=click.File()) -@click.option( - "-s", - "--style", - default="copper", - type=click.Choice(list(STYLE_TO_COLOR_SCHEME.keys()), case_sensitive=False), - help="Color style of the rendered image. When style is 'custom' then option " - "`--custom` must also be provided. Default is 'copper'.", -) -@click.option( - "-o", - "--output", - type=Path, - default="output.png", - help="Path to output file. File format will be inferred from extension, unless " - "`--format` is given. Default is 'output.png'", -) -@click.option( - "-f", - "--format", - "format_", - type=str, - default=None, - help="Output image format. Can be omitted, then format will be inferred from file" - "extension or be one of formats supported by Pillow.", -) -@click.option( - "-c", - "--custom", - type=str, - default=None, - help="String representing custom set of colors for rendering.\n" - "Custom color should be a single string consisting of 5 or 7 valid hexadecimal " - "colors separated with commas. Any color which can be parsed by RGBA type is " - "accepted.\n" - "Colors are assigned in order:" - "\n\n" - "- background_color\n\n" - "- clear_color\n\n" - "- solid_color\n\n" - "- clear_region_color\n\n" - "- solid_region_color\n\n" - "- debug_1_color (optional, by default #ABABAB)\n\n" - "- debug_2_color (optional, by default #7D7D7D)\n\n" - "\n\n" - 'eg. `"000000,000000,FFFFFF,000000,FFFFFF"`', -) -@click.option( - "-d", - "--dpi", - type=int, - default=1000, - help="DPI of output image, by default 1000.", -) -def raster_2d( - source: TextIO, - style: str, - output: Path, - format_: Optional[str], - custom: Optional[str], - dpi: int, -) -> None: - """Render rasterized 2D image from Gerber X3/X2 SOURCE file. - - SOURCE - A path to the Gerber file to render. - - List of file formats supported by Pillow: - https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html - - \x08 - RGBA type documentation: - https://argmaster.github.io/pygerber/latest/reference/pygerber/gerberx3/api/__init__.html#pygerber.common.rgba.RGBA.from_hex - """ # noqa: D301 - gerber_code = source.read() - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_code=gerber_code, - colors=get_color_scheme_from_style(style, custom), - dpi=dpi, - ), - ).render().save(output, format=format_) - - @main.command("is-language-server-available") def _is_language_server_available() -> None: from pygerber.gerberx3.language_server import IS_LANGUAGE_SERVER_FEATURE_AVAILABLE diff --git a/src/pygerber/gerberx3/api/__init__.py b/src/pygerber/gerberx3/api/__init__.py index caebbe18..b127e946 100644 --- a/src/pygerber/gerberx3/api/__init__.py +++ b/src/pygerber/gerberx3/api/__init__.py @@ -11,53 +11,11 @@ MutuallyExclusiveViolationError, RenderingResultNotReadyError, ) -from pygerber.gerberx3.api._layers import ( - Layer, - LayerParams, - LayerProperties, - Rasterized2DLayer, - Rasterized2DLayerParams, - RenderingResult, -) -from pygerber.gerberx3.parser.errors import ( - ApertureNotDefinedError, - ApertureNotSelectedError, - CoordinateFormatNotSetError, - ExitParsingProcessInterrupt, - IncrementalCoordinatesNotSupportedError, - InvalidCoordinateLengthError, - OnUpdateDrawingStateError, - ParserError, - ParserFatalError, - UnitNotSetError, - UnsupportedCoordinateTypeError, - ZeroOmissionNotSupportedError, -) -from pygerber.gerberx3.parser.parser import ParserOnErrorAction __all__ = [ "RGBA", "ColorScheme", - "Layer", - "LayerParams", - "Rasterized2DLayer", - "Rasterized2DLayerParams", - "LayerProperties", - "RenderingResult", - "ParserOnErrorAction", "GerberX3APIError", "RenderingResultNotReadyError", "MutuallyExclusiveViolationError", - "ParserError", - "ZeroOmissionNotSupportedError", - "IncrementalCoordinatesNotSupportedError", - "UnsupportedCoordinateTypeError", - "InvalidCoordinateLengthError", - "ParserFatalError", - "OnUpdateDrawingStateError", - "UnitNotSetError", - "ApertureNotDefinedError", - "CoordinateFormatNotSetError", - "ApertureNotSelectedError", - "ExitParsingProcessInterrupt", ] diff --git a/src/pygerber/gerberx3/api/_layers.py b/src/pygerber/gerberx3/api/_layers.py deleted file mode 100644 index 149841da..00000000 --- a/src/pygerber/gerberx3/api/_layers.py +++ /dev/null @@ -1,340 +0,0 @@ -"""High level API for rendering multi layer gerber projects.""" - -from __future__ import annotations - -from abc import abstractmethod -from io import BytesIO, StringIO -from pathlib import Path -from typing import TYPE_CHECKING, Any, Callable, Optional, Union - -from pydantic import BaseModel, ConfigDict, model_validator - -from pygerber.backend.abstract.backend_cls import Backend -from pygerber.backend.abstract.result_handle import ResultHandle -from pygerber.backend.rasterized_2d.backend_cls import ( - Rasterized2DBackend, - Rasterized2DBackendOptions, -) -from pygerber.backend.rasterized_2d.color_scheme import ColorScheme -from pygerber.backend.rasterized_2d.result_handle import Rasterized2DResultHandle -from pygerber.gerberx3.api._errors import ( - MutuallyExclusiveViolationError, - RenderingResultNotReadyError, -) -from pygerber.gerberx3.math.bounding_box import BoundingBox -from pygerber.gerberx3.math.vector_2d import Vector2D -from pygerber.gerberx3.parser.parser import Parser, ParserOnErrorAction, ParserOptions -from pygerber.gerberx3.tokenizer.tokenizer import Tokenizer -from pygerber.gerberx3.tokenizer.tokens.bases.token import Token - -if TYPE_CHECKING: - from PIL import Image - from typing_extensions import Self - - -class LayerParams(BaseModel): - """Parameters for Layer object. - - `source_path`, `source_code` and `source_buffer` are mutually exclusive. - When more than one of them is provided to constructor, - MutuallyExclusiveViolationError will be raised. - """ - - model_config = ConfigDict(arbitrary_types_allowed=True, extra="forbid") - - source_path: Optional[Union[Path, str]] = None - """Path to source file containing Gerber code. It will be automatically loaded - from local storage, when provided. Mutually exclusive with `source_code` and - `source_buffer`. - """ - - source_code: Optional[Union[str, bytes]] = None - """Gerber source code. Mutually exclusive with `source_path` and `source_buffer`.""" - - source_buffer: Optional[Union[StringIO, BytesIO]] = None - """Buffer containing Gerber source code. Buffer pointer should be at the - beginning of the buffer. Mutually exclusive with `source_path` and - `source_code`.""" - - parser_error: Union[ - Callable[[Exception, Parser, Token], None], - ParserOnErrorAction, - ] = ParserOnErrorAction.Raise - """Callback function or rule describing how to treat errors during parsing.""" - - encoding: str = "utf-8" - """Encoding of code, used when loading from file, decoding `source_code` - provided as bytes and reading `source_buffer` provided as BytesIO.""" - - draw_region_outlines: bool = False - """When drawing regions, after filling region, draw also outline of region with - apertures used for region outlines. This behavior is not expected by KiCAD by - default but may be useful in some scenarios.""" - - @model_validator(mode="after") - def _load_source_code(self) -> Self: - """Load source code. - - Raises - ------ - MutuallyExclusiveViolationError - When more than one of mutually exclusive `source_path`, `source_code` and - `source_buffer` is provided to constructor. - - """ - if self.source_path: - if self.source_code or self.source_buffer: - msg = "'source_code' and 'source_buffer' provided at once." - raise MutuallyExclusiveViolationError(msg) - - self.source_code = ( - Path(self.source_path or "source.grb") - .expanduser() - .resolve() - .read_text(encoding=self.encoding) - ) - return self - - if self.source_code: - if self.source_path or self.source_buffer: - msg = "'source_path' and 'source_buffer' provided at once." - raise MutuallyExclusiveViolationError(msg) - - self.source_code = ( - self.source_code - if isinstance(self.source_code, str) - else self.source_code.decode(self.encoding) - ) - return self - - if self.source_buffer: - if self.source_path or self.source_code: - msg = "'source_path' and 'source_buffer' provided at once." - raise MutuallyExclusiveViolationError(msg) - - source_code = self.source_buffer.read() - if isinstance(source_code, bytes): - self.source_code = source_code.decode(encoding="utf-8") - else: - self.source_code = source_code - - return self - - def get_source_code(self) -> str: - """Return source code of layer.""" - if not isinstance(self.source_code, str): - msg = f"Expected {str} got {type(self.source_code)}." - raise TypeError(msg) - - return self.source_code - - -class Layer: - """Representation of Gerber X3 image layer. - - This is only abstract base class, please use one of its subclasses with rendering - system guarantees. - """ - - def __init__(self, options: LayerParams) -> None: - """Create PCB layer. - - Parameters - ---------- - options: LayerOptions - Configuration of layer. - - """ - self.options = options - - self.tokenizer = self._create_tokenizer() - self.backend = self._create_backend() - self.parser = self._create_parser() - - self._rendering_result: Optional[RenderingResult] = None - - def _create_tokenizer(self) -> Tokenizer: - return Tokenizer() - - @abstractmethod - def _create_backend(self) -> Backend: - pass - - def _create_parser(self) -> Parser: - return Parser( - ParserOptions( - backend=self.backend, - on_update_drawing_state_error=self.options.parser_error, - ), - ) - - def render(self) -> RenderingResult: - """Render layer image.""" - stack = self.tokenizer.tokenize(self.options.get_source_code()) - draw_commands = self.parser.parse(stack) - - result_handle = draw_commands.draw() - properties = LayerProperties( - target_bounding_box=self.backend.drawing_target.bounding_box, - target_coordinate_origin=self.backend.drawing_target.coordinate_origin, - gerber_bounding_box=self.backend.bounding_box, - gerber_coordinate_origin=self.backend.coordinate_origin, - ) - - self._rendering_result = self._get_rendering_result_cls()( - result_handle=result_handle, - properties=properties, - ) - return self._rendering_result - - def _get_rendering_result_cls(self) -> type[RenderingResult]: - return RenderingResult - - def get_rendering_result(self) -> RenderingResult: - """Return result of rendering Gerber file.""" - if self._rendering_result is None: - msg = "Use `render()` method to create result first." - raise RenderingResultNotReadyError(msg) - - return self._rendering_result - - -class LayerProperties: - """Properties of layer retrieved from Gerber source code.""" - - target_bounding_box: BoundingBox - """Bounding box of rendering target. May differ from coordinates used in Gerber - file as it uses rendering target coordinate space.""" - - target_coordinate_origin: Vector2D - """Offset of origin of coordinate system used by rendering target. Bottom left - corner of coordinate space of rendering target.""" - - gerber_bounding_box: BoundingBox - """Bounding box of drawing area in Gerber file coordinate space.""" - - gerber_coordinate_origin: Vector2D - """Origin of coordinate space of Gerber file. Equivalent to bottom left corner of - `gerber_bounding_box`. - - Can be useful to determine how to align multiple Gerber files by calculating - how their coordinate origins are positioned in relation to each other.""" - - def __init__( - self, - target_bounding_box: BoundingBox, - target_coordinate_origin: Vector2D, - gerber_bounding_box: BoundingBox, - gerber_coordinate_origin: Vector2D, - ) -> None: - """Initialize layer properties.""" - self.target_bounding_box = target_bounding_box - self.target_coordinate_origin = target_coordinate_origin - - self.gerber_bounding_box = gerber_bounding_box - self.gerber_coordinate_origin = gerber_coordinate_origin - - -class RenderingResult: - """Result of rendering of layer.""" - - def __init__( - self, - properties: LayerProperties, - result_handle: ResultHandle, - ) -> None: - """Initialize rendering result object.""" - self._properties = properties - self._result_handle = result_handle - - def save( - self, - dest: Path | str | BytesIO, - **options: Any, - ) -> None: - """Save result to specified file or buffer. - - Parameters - ---------- - dest : Path | str | BytesIO - Write target. - **options: Any - Extra parameters which will be passed to saving implementation. - When dest is BytesIO or alike, `format` option must be specified. - For Rasterized2D rendering options see [Pillow documentation](https://pillow.readthedocs.io/en/stable/reference/Image.html#PIL.Image.Image.save). - - """ - self._result_handle.save(dest, **options) - - def get_properties(self) -> LayerProperties: - """Get properties of layer.""" - return self._properties - - -class Rasterized2DLayerParams(LayerParams): - """Parameters for Layer with 2D rendering. - - `source_path`, `source_code` and `source_buffer` are mutually exclusive. - When more than one of them is provided to constructor, - MutuallyExclusiveViolationError will be raised. - """ - - colors: ColorScheme - """Colors to use for rendering of image.""" - - dpi: int = 1000 - """DPI of output image.""" - - debug_dump_apertures: Optional[Path] = None - """Debug option - dump aperture images to files in given directory.""" - - debug_include_extra_padding: bool = False - """Debug option - include large extra padding on all rendering targets to simplify - tracking of mispositioned draw commands.""" - - debug_include_bounding_boxes: bool = False - """Debug option - include bounding boxes as square outlines on drawing targets - to simplify tracking of miscalculated bounding boxes.""" - - -class Rasterized2DLayer(Layer): - """Representation of Gerber X3 rasterized 2D image layer. - - Rasterized images can be saved in any image format supported by Pillow library. - For full list of supported formats please refer to - [Pillow documentation](https://pillow.readthedocs.io/en/stable/handbook/image-file-formats.html). - """ - - options: Rasterized2DLayerParams - - def __init__(self, options: Rasterized2DLayerParams) -> None: - """Initialize Layer object.""" - if not isinstance(options, Rasterized2DLayerParams): - msg = f"Expected {Rasterized2DLayerParams} got {type(options)}." # type: ignore[unreachable] - raise TypeError(msg) - super().__init__(options) - - def _create_backend(self) -> Backend: - return Rasterized2DBackend( - Rasterized2DBackendOptions( - dpi=self.options.dpi, - color_scheme=self.options.colors, - dump_apertures=self.options.debug_dump_apertures, - include_debug_padding=self.options.debug_include_extra_padding, - include_bounding_boxes=self.options.debug_include_bounding_boxes, - draw_region_outlines=self.options.draw_region_outlines, - ), - ) - - def _get_rendering_result_cls(self) -> type[RenderingResult]: - return Rasterized2DRenderingResult - - -class Rasterized2DRenderingResult(RenderingResult): - """Result of rendering of layer.""" - - _result_handle: Rasterized2DResultHandle - - def get_image(self) -> Image.Image: - """Get rendered image object.""" - return self._result_handle.get_image() diff --git a/src/pygerber/gerberx3/language_server/_internals/document.py b/src/pygerber/gerberx3/language_server/_internals/document.py index 83adc84e..02c556ae 100644 --- a/src/pygerber/gerberx3/language_server/_internals/document.py +++ b/src/pygerber/gerberx3/language_server/_internals/document.py @@ -10,10 +10,7 @@ from pygerber.gerberx3.language_server._internals.errors import EmptyASTError from pygerber.gerberx3.linter import diagnostic from pygerber.gerberx3.linter.diagnostic import Diagnostic -from pygerber.gerberx3.parser.errors import ExitParsingProcessInterrupt, ParserError -from pygerber.gerberx3.parser.parser import Parser, ParserOptions, StatePreservingParser from pygerber.gerberx3.tokenizer.tokenizer import Tokenizer -from pygerber.gerberx3.tokenizer.tokens.bases.token import Token if TYPE_CHECKING: from pygls.server import LanguageServer @@ -38,10 +35,6 @@ def __init__(self, ls: LanguageServer, source: str, uri: str) -> None: ) self.parser_error_diagnostics: list[diagnostic.Diagnostic] = [] - self.options = ParserOptions( - on_update_drawing_state_error=self.on_update_drawing_state_error, - ) - self.parser = StatePreservingParser(self.options) self.ls.show_message_log( f"Created parser for {uri}", lspt.MessageType.Info, @@ -61,42 +54,11 @@ def __init__(self, ls: LanguageServer, source: str, uri: str) -> None: f"Started parsing {uri}", lspt.MessageType.Info, ) - self.parser.parse(self.ast) self.ls.show_message_log( f"Finished parsing {self.uri}", lspt.MessageType.Info, ) - def on_update_drawing_state_error( - self, - exc: Exception, - _parser: Parser, - token: Token, - ) -> None: - if isinstance(exc, ParserError): - message = exc.get_message() - else: - message = f"{exc.__class__.__qualname__}: {exc}" - - self.parser_error_diagnostics.append( - diagnostic.Diagnostic( - range=( - diagnostic.Range( - start=token.get_token_position(), - end=token.get_token_end_position(), - ) - ), - message=message, - severity=diagnostic.DiagnosticSeverity.Error, - ), - ) - self.ls.show_message_log( - message, - lspt.MessageType.Info, - ) - - raise ExitParsingProcessInterrupt - def text_document_hover(self, params: lspt.HoverParams) -> Optional[lspt.Hover]: try: ast = self.ast @@ -116,7 +78,7 @@ def text_document_hover(self, params: lspt.HoverParams) -> Optional[lspt.Hover]: return lspt.Hover( contents=lspt.MarkupContent( kind=lspt.MarkupKind.Markdown, - value=token.get_hover_message(self.parser.get_state_at(token)), + value="Not implemented", ), ) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/ab_block_aperture.py b/src/pygerber/gerberx3/tokenizer/tokens/ab_block_aperture.py index 206953a7..b62517b9 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/ab_block_aperture.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/ab_block_aperture.py @@ -2,11 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.backend.abstract.backend_cls import Backend -from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken from pygerber.gerberx3.tokenizer.tokens.dnn_select_aperture import ApertureID @@ -72,30 +69,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: """ return cls(string, location, ApertureID(tokens["aperture_identifier"])) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - handle = backend.create_aperture_handle(self.identifier) - with handle: - # Must be included to initialize drawing target. - pass - frozen_handle = handle.get_public_handle() - - new_aperture_dict = {**state.apertures} - new_aperture_dict[self.identifier] = frozen_handle - - return ( - state.model_copy( - update={ - "apertures": new_aperture_dict, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().begin_block_aperture.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/ad_define_aperture.py b/src/pygerber/gerberx3/tokenizer/tokens/ad_define_aperture.py index 75ca6a86..419d3e44 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/ad_define_aperture.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/ad_define_aperture.py @@ -19,11 +19,8 @@ from __future__ import annotations from decimal import Decimal -from typing import TYPE_CHECKING, Iterable, List, Optional, Tuple +from typing import TYPE_CHECKING, List, Optional -from pygerber.gerberx3.math.offset import Offset -from pygerber.gerberx3.math.vector_2d import Vector2D -from pygerber.gerberx3.state_enums import Polarity from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, ) @@ -33,9 +30,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -150,45 +144,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: hole_diameter=hole_diameter, ) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - handle = backend.create_aperture_handle(self.aperture_id) - with handle: - handle.add_draw( - backend.get_draw_circle_cls()( - backend=backend, - diameter=Offset.new(self.diameter, state.get_units()), - polarity=Polarity.Dark, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - if self.hole_diameter is not None: - handle.add_draw( - backend.get_draw_circle_cls()( - backend=backend, - diameter=Offset.new(self.hole_diameter, state.get_units()), - polarity=Polarity.Clear, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - frozen_handle = handle.get_public_handle() - - new_aperture_dict = {**state.apertures} - new_aperture_dict[self.aperture_id] = frozen_handle - - return ( - state.model_copy( - update={ - "apertures": new_aperture_dict, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().define_circle_aperture.pre_parser_visit_token(self, context) @@ -303,46 +258,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: hole_diameter=hole_diameter, ) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - handle = backend.create_aperture_handle(self.aperture_id) - with handle: - handle.add_draw( - backend.get_draw_rectangle_cls()( - backend=backend, - x_size=Offset.new(self.x_size, state.get_units()), - y_size=Offset.new(self.y_size, state.get_units()), - polarity=Polarity.Dark, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - if self.hole_diameter is not None: - handle.add_draw( - backend.get_draw_circle_cls()( - backend=backend, - diameter=Offset.new(self.hole_diameter, state.get_units()), - polarity=Polarity.Clear, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - frozen_handle = handle.get_public_handle() - - new_aperture_dict = {**state.apertures} - new_aperture_dict[self.aperture_id] = frozen_handle - - return ( - state.model_copy( - update={ - "apertures": new_aperture_dict, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().define_rectangle_aperture.pre_parser_visit_token( @@ -463,86 +378,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: hole_diameter=hole_diameter, ) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - handle = backend.create_aperture_handle(self.aperture_id) - - x_size = Offset.new(self.x_size, state.get_units()) - y_size = Offset.new(self.y_size, state.get_units()) - - if self.x_size < self.y_size: - # Obround is thin and tall. - circle_diameter = x_size - - middle_rectangle_x = x_size - middle_rectangle_y = y_size - x_size - - circle_positive = Vector2D(x=Offset.NULL, y=middle_rectangle_y / 2) - circle_negative = Vector2D(x=Offset.NULL, y=-middle_rectangle_y / 2) - - else: - # Obround is wide and short. - circle_diameter = y_size - - middle_rectangle_x = x_size - y_size - middle_rectangle_y = y_size - - circle_positive = Vector2D(x=middle_rectangle_x / 2, y=Offset.NULL) - circle_negative = Vector2D(x=-middle_rectangle_x / 2, y=Offset.NULL) - - with handle: - handle.add_draw( - backend.get_draw_rectangle_cls()( - backend=backend, - x_size=middle_rectangle_x, - y_size=middle_rectangle_y, - polarity=Polarity.Dark, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - handle.add_draw( - backend.get_draw_circle_cls()( - backend=backend, - diameter=circle_diameter, - polarity=Polarity.Dark, - center_position=circle_positive, - ), - ) - handle.add_draw( - backend.get_draw_circle_cls()( - backend=backend, - diameter=circle_diameter, - polarity=Polarity.Dark, - center_position=circle_negative, - ), - ) - if self.hole_diameter is not None: - handle.add_draw( - backend.get_draw_circle_cls()( - backend=backend, - diameter=Offset.new(self.hole_diameter, state.get_units()), - polarity=Polarity.Clear, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - frozen_handle = handle.get_public_handle() - - new_aperture_dict = {**state.apertures} - new_aperture_dict[self.aperture_id] = frozen_handle - - return ( - state.model_copy( - update={ - "apertures": new_aperture_dict, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().define_obround_aperture.pre_parser_visit_token( @@ -670,47 +505,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: hole_diameter=hole_diameter, ) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - handle = backend.create_aperture_handle(self.aperture_id) - with handle: - handle.add_draw( - backend.get_draw_polygon_cls()( - backend=backend, - outer_diameter=Offset.new(self.outer_diameter, state.get_units()), - number_of_vertices=self.number_of_vertices, - rotation=Decimal("0.0") if self.rotation is None else self.rotation, - polarity=Polarity.Dark, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - if self.hole_diameter is not None: - handle.add_draw( - backend.get_draw_circle_cls()( - backend=backend, - diameter=Offset.new(self.hole_diameter, state.get_units()), - polarity=Polarity.Clear, - center_position=Vector2D(x=Offset.NULL, y=Offset.NULL), - ), - ) - frozen_handle = handle.get_public_handle() - - new_aperture_dict = {**state.apertures} - new_aperture_dict[self.aperture_id] = frozen_handle - - return ( - state.model_copy( - update={ - "apertures": new_aperture_dict, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().define_polygon_aperture.pre_parser_visit_token( @@ -853,35 +647,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: am_param=am_param, ) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - handle = backend.create_aperture_handle(self.aperture_id) - with handle: - macro = state.macros[self.aperture_type] - parameters = { - f"${i + 1}": Offset.new(value, state.get_units()) - for i, value in enumerate(self.am_param) - } - macro.evaluate(state, handle, parameters) - - frozen_handle = handle.get_public_handle() - - new_aperture_dict = {**state.apertures} - new_aperture_dict[self.aperture_id] = frozen_handle - - return ( - state.model_copy( - update={ - "apertures": new_aperture_dict, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().define_macro_aperture.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/as_axis_select.py b/src/pygerber/gerberx3/tokenizer/tokens/as_axis_select.py index b47040f0..f07425dd 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/as_axis_select.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/as_axis_select.py @@ -17,16 +17,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.backend.abstract.backend_cls import Backend -from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.state_enums import AxisCorrespondence from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, ) -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: from pyparsing import ParseResults @@ -109,15 +105,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: correspondence=AxisCorrespondence(correspondence), ) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - warn_deprecated_code("AS", "8.1") - return super().update_drawing_state(state, _backend) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().axis_select.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/bases/token.py b/src/pygerber/gerberx3/tokenizer/tokens/bases/token.py index 22dcd871..48ab67ca 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/bases/token.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/bases/token.py @@ -3,22 +3,18 @@ from __future__ import annotations from functools import cached_property -from typing import TYPE_CHECKING, Any, Iterable, Iterator, Optional, Tuple +from typing import TYPE_CHECKING, Any, Iterable, Iterator from pyparsing import col, lineno from pygerber.common.position import Position from pygerber.gerberx3.linter import diagnostic from pygerber.gerberx3.tokenizer.tokens.bases.gerber_code import GerberCode -from pygerber.gerberx3.tokenizer.tokens.bases.token_accessor import TokenAccessor if TYPE_CHECKING: from pyparsing import ParserElement, ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -71,14 +67,6 @@ def parser2_visit_token( ) -> None: """Update drawing state for Gerber AST parser, version 2.""" - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - return state, () - def get_token_position(self) -> Position: """Get position of token.""" return self._token_position @@ -90,40 +78,6 @@ def _token_position(self) -> Position: col(self.location, self.string), ) - def get_hover_message(self, state: State) -> str: - """Return language server hover message.""" - ref_doc = "\n".join(s.strip() for s in str(self.__doc__).split("\n")) - op_specific_extra = self.get_state_based_hover_message(state) - return ( - "```gerber\n" - f"{self.get_gerber_code_one_line_pretty_display()}" - "\n" - "```" - "\n" - "---" - "\n" - f"{op_specific_extra}\n" - "\n" - "---" - "\n" - f"{ref_doc}" - ) - - def get_state_based_hover_message( - self, - state: State, # noqa: ARG002 - ) -> str: - """Return operation specific extra information about token.""" - return "" - - def find_closest_token( - self, - pos: Position, # noqa: ARG002 - parent: Optional[TokenAccessor] = None, - ) -> TokenAccessor: - """Find token closest to specified position.""" - return TokenAccessor(self, parent) - def get_gerber_code_one_line_pretty_display(self) -> str: """Get gerber code represented by this token.""" return self.get_gerber_code() diff --git a/src/pygerber/gerberx3/tokenizer/tokens/d01_draw.py b/src/pygerber/gerberx3/tokenizer/tokens/d01_draw.py index f7f4a868..9e35ff31 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/d01_draw.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/d01_draw.py @@ -2,11 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Generator, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.math.offset import Offset -from pygerber.gerberx3.math.vector_2d import Vector2D -from pygerber.gerberx3.state_enums import DrawMode, Polarity from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken from pygerber.gerberx3.tokenizer.tokens.coordinate import Coordinate, CoordinateType @@ -14,9 +11,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -124,153 +118,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: j=j, ) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set coordinate parser.""" - x = state.parse_coordinate(self.x) - y = state.parse_coordinate(self.y) - - end_position = Vector2D(x=x, y=y) - start_position = state.current_position - - draw_commands: list[DrawCommand] = [] - - if state.is_region: - polarity = state.polarity.to_region_variant() - else: - polarity = state.polarity - - if not state.is_region or backend.options.draw_region_outlines: - draw_commands.extend( - self._create_draw_commands( - state, - backend, - end_position, - start_position, - polarity, - ), - ) - - if state.is_region: - self._create_region_points( - state, - backend, - end_position, - start_position, - polarity, - ) - - return ( - state.model_copy( - update={ - "current_position": end_position, - }, - ), - draw_commands, - ) - - def _create_region_points( - self, - state: State, - backend: Backend, - end_position: Vector2D, - start_position: Vector2D, - polarity: Polarity, - ) -> None: - if state.draw_mode == DrawMode.Linear: - state.region_boundary_points.append(start_position) - state.region_boundary_points.append(end_position) - - elif state.draw_mode in ( - DrawMode.ClockwiseCircular, - DrawMode.CounterclockwiseCircular, - ): - i = state.parse_coordinate(self.i) - j = state.parse_coordinate(self.j) - - center_offset = Vector2D(x=i, y=j) - - state.region_boundary_points.extend( - backend.get_draw_arc_cls()( - backend=backend, - polarity=polarity, - start_position=start_position, - dx_dy_center=center_offset, - end_position=end_position, - width=Offset.NULL, - is_clockwise=(state.draw_mode == DrawMode.ClockwiseCircular), - # Will require tweaking if support for single quadrant mode - # will be desired. - is_multi_quadrant=True, - ).calculate_arc_points(), - ) - - else: - raise NotImplementedError(state.draw_mode) - - def _create_draw_commands( - self, - state: State, - backend: Backend, - end_position: Vector2D, - start_position: Vector2D, - polarity: Polarity, - ) -> Generator[DrawCommand, None, None]: - current_aperture = backend.get_private_aperture_handle( - state.get_current_aperture(), - ) - yield backend.get_draw_paste_cls()( - backend=backend, - polarity=polarity, - center_position=start_position, - other=current_aperture.drawing_target, - ) - - if state.draw_mode == DrawMode.Linear: - if not state.is_region or backend.options.draw_region_outlines: - yield backend.get_draw_vector_line_cls()( - backend=backend, - polarity=polarity, - start_position=start_position, - end_position=end_position, - width=current_aperture.get_line_width(), - ) - - elif state.draw_mode in ( - DrawMode.ClockwiseCircular, - DrawMode.CounterclockwiseCircular, - ): - i = state.parse_coordinate(self.i) - j = state.parse_coordinate(self.j) - - center_offset = Vector2D(x=i, y=j) - if not state.is_region or backend.options.draw_region_outlines: - yield backend.get_draw_arc_cls()( - backend=backend, - polarity=polarity, - start_position=start_position, - dx_dy_center=center_offset, - end_position=end_position, - width=current_aperture.get_line_width(), - is_clockwise=(state.draw_mode == DrawMode.ClockwiseCircular), - # Will require tweaking if support for single quadrant mode - # will be desired. - is_multi_quadrant=True, - ) - - else: - raise NotImplementedError(state.draw_mode) - - yield backend.get_draw_paste_cls()( - backend=backend, - polarity=polarity, - center_position=end_position, - other=current_aperture.drawing_target, - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().command_draw.pre_parser_visit_token(self, context) @@ -291,28 +138,3 @@ def get_gerber_code( f"{self.j.get_gerber_code(indent, endline)}" "D01" ) - - def get_state_based_hover_message( - self, - state: State, - ) -> str: - """Return operation specific extra information about token.""" - units = state.get_units() - - x0 = state.current_position.x.as_unit(units) - y0 = state.current_position.x.as_unit(units) - - x1 = state.parse_coordinate(self.x).as_unit(units) - y1 = state.parse_coordinate(self.y).as_unit(units) - - draw_mode = state.draw_mode.to_human_readable() - - aperture = state.get_current_aperture().aperture_id - - u = units.value.lower() - d = state.draw_mode.value - - return ( - f"Draw {draw_mode} (`{d}`) from (`{x0}`{u}, `{y0}`{u}) to " - f"(`{x1}`{u}, `{y1}`{u}) with aperture `{aperture}`" - ) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/d02_move.py b/src/pygerber/gerberx3/tokenizer/tokens/d02_move.py index 25e66748..a7f1478a 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/d02_move.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/d02_move.py @@ -2,9 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.math.vector_2d import Vector2D from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken from pygerber.gerberx3.tokenizer.tokens.coordinate import Coordinate, CoordinateType @@ -12,9 +11,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -82,25 +78,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: y=y, ) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set coordinate parser.""" - x = state.parse_coordinate(self.x) - y = state.parse_coordinate(self.y) - - position = Vector2D(x=x, y=y) - return ( - state.model_copy( - update={ - "current_position": position, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().command_move.pre_parser_visit_token(self, context) @@ -119,20 +96,3 @@ def get_gerber_code( f"{self.y.get_gerber_code(indent, endline)}" "D02" ) - - def get_state_based_hover_message( - self, - state: State, - ) -> str: - """Return operation specific extra information about token.""" - units = state.get_units() - - x0 = state.current_position.x.as_unit(units) - y0 = state.current_position.x.as_unit(units) - - x1 = state.parse_coordinate(self.x).as_unit(units) - y1 = state.parse_coordinate(self.y).as_unit(units) - - u = units.value.lower() - - return f"Move from (`{x0}`{u}, `{y0}`{u}) to (`{x1}`{u}, `{y1}`{u})" diff --git a/src/pygerber/gerberx3/tokenizer/tokens/d03_flash.py b/src/pygerber/gerberx3/tokenizer/tokens/d03_flash.py index a3e2613b..8c7e74a9 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/d03_flash.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/d03_flash.py @@ -2,9 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.math.vector_2d import Vector2D from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken from pygerber.gerberx3.tokenizer.tokens.coordinate import Coordinate, CoordinateType @@ -12,9 +11,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -62,43 +58,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: y=y, ) - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set coordinate parser.""" - x = state.parse_coordinate(self.x) - y = state.parse_coordinate(self.y) - - position = Vector2D(x=x, y=y) - draw_commands: list[DrawCommand] = [] - current_aperture = backend.get_private_aperture_handle( - state.get_current_aperture(), - ) - if state.is_region: - polarity = state.polarity.to_region_variant() - else: - polarity = state.polarity - - draw_commands.append( - backend.get_draw_paste_cls()( - backend=backend, - polarity=polarity, - center_position=position, - other=current_aperture.drawing_target, - ), - ) - - return ( - state.model_copy( - update={ - "current_position": position, - }, - ), - draw_commands, - ) - def get_gerber_code( self, indent: str = "", @@ -117,19 +76,3 @@ def parser2_visit_token(self, context: Parser2Context) -> None: context.get_hooks().command_flash.pre_parser_visit_token(self, context) context.get_hooks().command_flash.on_parser_visit_token(self, context) context.get_hooks().command_flash.post_parser_visit_token(self, context) - - def get_state_based_hover_message( - self, - state: State, - ) -> str: - """Return operation specific extra information about token.""" - units = state.get_units() - - x1 = state.parse_coordinate(self.x).as_unit(units) - y1 = state.parse_coordinate(self.y).as_unit(units) - - aperture = state.get_current_aperture().aperture_id - - u = units.value.lower() - - return f"Flash `{aperture}` on (`{x1}`{u}, `{y1}`{u})" diff --git a/src/pygerber/gerberx3/tokenizer/tokens/dnn_select_aperture.py b/src/pygerber/gerberx3/tokenizer/tokens/dnn_select_aperture.py index 82286a6d..25339dd2 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/dnn_select_aperture.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/dnn_select_aperture.py @@ -2,9 +2,8 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.parser.errors import ApertureNotDefinedError from pygerber.gerberx3.tokenizer.aperture_id import ApertureID from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken @@ -12,9 +11,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -63,23 +59,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: aperture_id=ApertureID(tokens["aperture_identifier"]), ) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set current aperture.""" - if self.aperture_id not in state.apertures: - raise ApertureNotDefinedError(self.aperture_id) - return ( - state.model_copy( - update={ - "current_aperture": state.apertures[self.aperture_id], - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().select_aperture.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/fs_coordinate_format.py b/src/pygerber/gerberx3/tokenizer/tokens/fs_coordinate_format.py index da779db3..6c93e61c 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/fs_coordinate_format.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/fs_coordinate_format.py @@ -4,14 +4,14 @@ import logging from decimal import Decimal -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.common.frozen_general_model import FrozenGeneralModel -from pygerber.gerberx3.parser.errors import ( - IncrementalCoordinatesNotSupportedError, - InvalidCoordinateLengthError, - UnsupportedCoordinateTypeError, - ZeroOmissionNotSupportedError, +from pygerber.gerberx3.parser2.errors2 import ( + IncrementalCoordinatesNotSupported2Error, + InvalidCoordinateLength2Error, + UnsupportedCoordinateType2Error, + ZeroOmissionNotSupported2Error, ) from pygerber.gerberx3.tokenizer.helpers.gerber_code_enum import GerberCodeEnum from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( @@ -28,9 +28,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -85,29 +82,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: y_format=y_format, ) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set coordinate parser.""" - if state.coordinate_parser is not None: - logging.warning( - "Overriding coordinate format is illegal." - "(See 4.2.2 in Gerber Layer Format Specification)", - ) - return ( - state.model_copy( - update={ - "coordinate_parser": CoordinateParser.new( - x_format=self.x_format, - y_format=self.y_format, - ), - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().coordinate_format.pre_parser_visit_token(self, context) @@ -188,10 +162,10 @@ def new( ) -> Self: """Update coordinate parser format configuration.""" if coordinate_mode != CoordinateMode.Absolute: - raise IncrementalCoordinatesNotSupportedError + raise IncrementalCoordinatesNotSupported2Error if zeros_mode != TrailingZerosMode.OmitLeading: - raise ZeroOmissionNotSupportedError + raise ZeroOmissionNotSupported2Error for axis, axis_format in (("X", x_format), ("Y", y_format)): if axis_format.decimal < RECOMMENDED_MINIMAL_DECIMAL_PLACES: @@ -213,7 +187,7 @@ def parse(self, coordinate: Coordinate) -> Decimal: if coordinate.coordinate_type in (CoordinateType.Y, CoordinateType.J): return self._parse(self.y_format, coordinate.offset, coordinate.sign) - raise UnsupportedCoordinateTypeError(coordinate.coordinate_type) + raise UnsupportedCoordinateType2Error(coordinate.coordinate_type) def _parse( self, @@ -225,7 +199,7 @@ def _parse( if len(offset) > total_length: msg = f"Got {offset!r} with length {len(offset)} expected {total_length}." - raise InvalidCoordinateLengthError(msg) + raise InvalidCoordinateLength2Error(msg) offset = offset.rjust(axis_format.total_length, "0") integer, decimal = offset[: axis_format.integer], offset[axis_format.integer :] diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g01_set_linear.py b/src/pygerber/gerberx3/tokenizer/tokens/g01_set_linear.py index 2c72305c..dd8ba281 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g01_set_linear.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g01_set_linear.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.state_enums import DrawMode from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -23,21 +19,6 @@ class SetLinear(CommandToken): - section 4.7 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing mode.""" - return ( - state.model_copy( - update={ - "draw_mode": DrawMode.Linear, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_linear.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g02_set_clockwise_circular.py b/src/pygerber/gerberx3/tokenizer/tokens/g02_set_clockwise_circular.py index 1b8e3574..0ccd0cb6 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g02_set_clockwise_circular.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g02_set_clockwise_circular.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.state_enums import DrawMode from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -24,21 +20,6 @@ class SetClockwiseCircular(CommandToken): - section 4.7 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing mode.""" - return ( - state.model_copy( - update={ - "draw_mode": DrawMode.ClockwiseCircular, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_clockwise_circular.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g03_set_counterclockwise_circular.py b/src/pygerber/gerberx3/tokenizer/tokens/g03_set_counterclockwise_circular.py index 3c510f80..2683690f 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g03_set_counterclockwise_circular.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g03_set_counterclockwise_circular.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.state_enums import DrawMode from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -24,21 +20,6 @@ class SetCounterclockwiseCircular(CommandToken): - section 4.7 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing mode.""" - return ( - state.model_copy( - update={ - "draw_mode": DrawMode.CounterclockwiseCircular, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_counter_clockwise_circular.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g36_begin_region.py b/src/pygerber/gerberx3/tokenizer/tokens/g36_begin_region.py index eaf785d0..c8df3b1b 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g36_begin_region.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g36_begin_region.py @@ -2,15 +2,11 @@ from __future__ import annotations -import logging -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -22,25 +18,6 @@ class BeginRegion(CommandToken): See section 4.10 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - if state.is_region: - logging.warning("Starting region within a region is not allowed.") - - return ( - state.model_copy( - update={ - "is_region": True, - "region_boundary_points": [], - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().begin_region.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g37_end_region.py b/src/pygerber/gerberx3/tokenizer/tokens/g37_end_region.py index 9fc0eecf..d9c31517 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g37_end_region.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g37_end_region.py @@ -2,15 +2,11 @@ from __future__ import annotations -import logging -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -22,34 +18,6 @@ class EndRegion(CommandToken): See section 4.10 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - if not state.is_region: - logging.warning("Ending region which was not started.") - - if len(state.region_boundary_points) == 0: - logging.warning("Created region with no boundaries.") - - draw_command = backend.get_draw_region_cls()( - backend, - state.polarity.to_region_variant(), - state.region_boundary_points, - ) - - return ( - state.model_copy( - update={ - "is_region": False, - "region_boundary_points": [], - }, - ), - (draw_command,), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().end_region.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g54_select_aperture.py b/src/pygerber/gerberx3/tokenizer/tokens/g54_select_aperture.py index 3b59b06f..ee4ae290 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g54_select_aperture.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g54_select_aperture.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.dnn_select_aperture import DNNSelectAperture -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -25,15 +21,6 @@ class G54SelectAperture(DNNSelectAperture): See section 8.1.1 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - warn_deprecated_code("G54", "8.1") - return super().update_drawing_state(state, _backend) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().prepare_select_aperture.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g70_set_unit_inch.py b/src/pygerber/gerberx3/tokenizer/tokens/g70_set_unit_inch.py index 23300106..0ea71823 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g70_set_unit_inch.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g70_set_unit_inch.py @@ -2,17 +2,11 @@ from __future__ import annotations -import logging -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.state_enums import Unit from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -27,33 +21,6 @@ class SetUnitInch(CommandToken): See section 8.1 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - warn_deprecated_code("G70", "8.1") - logging.warning( - "Detected use of imperial units. Using metric units is recommended. " - "Imperial units will be deprecated in future. " - "(See 4.2.1 in Gerber Layer Format Specification)", - ) - if state.draw_units is not None: - logging.warning( - "Overriding coordinate units is illegal. " - "(See section 4.2.2 of The Gerber Layer Format Specification " - "Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html)", - ) - return ( - state.model_copy( - update={ - "draw_units": Unit.Inches, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_unit_inch.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g71_set_unit_mm.py b/src/pygerber/gerberx3/tokenizer/tokens/g71_set_unit_mm.py index 51fc09e2..0ccc7921 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g71_set_unit_mm.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g71_set_unit_mm.py @@ -2,17 +2,11 @@ from __future__ import annotations -import logging -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.state_enums import Unit from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -27,28 +21,6 @@ class SetUnitMillimeters(CommandToken): See section 4.2.1 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - warn_deprecated_code("G71", "8.1") - if state.draw_units is not None: - logging.warning( - "Overriding coordinate units is illegal. " - "(See section 4.2.2 of The Gerber Layer Format Specification " - "Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html)", - ) - return ( - state.model_copy( - update={ - "draw_units": Unit.Millimeters, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_unit_millimeters.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g74_single_quadrant.py b/src/pygerber/gerberx3/tokenizer/tokens/g74_single_quadrant.py index 2ebfc57a..72c7b14a 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g74_single_quadrant.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g74_single_quadrant.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -27,22 +23,6 @@ class SetSingleQuadrantMode(CommandToken): - section 8.1.10 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - warn_deprecated_code("G74", "8.1.10") - return ( - state.model_copy( - update={ - "is_multi_quadrant": False, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_single_quadrant_mode.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g75_multi_quadrant.py b/src/pygerber/gerberx3/tokenizer/tokens/g75_multi_quadrant.py index 43c34389..bd6737bc 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g75_multi_quadrant.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g75_multi_quadrant.py @@ -2,14 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -36,21 +33,6 @@ class SetMultiQuadrantMode(CommandToken): - section 8.1.10 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - return ( - state.model_copy( - update={ - "is_multi_quadrant": True, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_multi_quadrant_mode.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g90_set_coordinate_absolute.py b/src/pygerber/gerberx3/tokenizer/tokens/g90_set_coordinate_absolute.py index 6f2f3a67..ff6aa9dd 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g90_set_coordinate_absolute.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g90_set_coordinate_absolute.py @@ -2,16 +2,11 @@ from __future__ import annotations -import logging -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -26,24 +21,6 @@ class SetAbsoluteNotation(CommandToken): See section 8.1 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - warn_deprecated_code("G90", "8.1") - if state.coordinate_parser is not None: - logging.warning( - "Overriding coordinate format is illegal. " - "(See section 4.2.2 of The Gerber Layer Format Specification " - "Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html)", - ) - return ( - state.model_copy(deep=True), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_coordinate_absolute.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/g91_set_coordinate_incremental.py b/src/pygerber/gerberx3/tokenizer/tokens/g91_set_coordinate_incremental.py index 725a3c1f..97a2596a 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/g91_set_coordinate_incremental.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/g91_set_coordinate_incremental.py @@ -2,16 +2,11 @@ from __future__ import annotations -import logging -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -26,23 +21,6 @@ class SetIncrementalNotation(CommandToken): See section 8.1 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - warn_deprecated_code("G91", "8.1") - if state.coordinate_parser is not None: - logging.warning( - "Overriding coordinate format is illegal. " - "(See section 4.2.2 of The Gerber Layer Format Specification " - "Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html)", - ) - - msg = "Incremental notation not supported." - raise NotImplementedError(msg) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().set_coordinate_incremental.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/in_image_name.py b/src/pygerber/gerberx3/tokenizer/tokens/in_image_name.py index daf09ef4..366fd9c3 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/in_image_name.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/in_image_name.py @@ -17,15 +17,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.backend.abstract.backend_cls import Backend -from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, ) -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: from pyparsing import ParseResults @@ -62,15 +58,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: content: str = str(tokens["string"]) return cls(string=string, location=location, content=content) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - warn_deprecated_code("IN", "8.1") - return super().update_drawing_state(state, _backend) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().image_name.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/ip_image_polarity.py b/src/pygerber/gerberx3/tokenizer/tokens/ip_image_polarity.py index eeac6029..2a09f79d 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/ip_image_polarity.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/ip_image_polarity.py @@ -2,21 +2,17 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.state_enums import ImagePolarityEnum from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, ) -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -53,24 +49,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: image_polarity=image_polarity, ) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - warn_deprecated_code("IP", "8.1.4") - return ( - state.model_copy( - update={ - "is_output_image_negation_required": ( - self.image_polarity == ImagePolarityEnum.NEGATIVE - ), - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().image_polarity.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/lm_load_mirroring.py b/src/pygerber/gerberx3/tokenizer/tokens/lm_load_mirroring.py index f174d854..acf88b01 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/lm_load_mirroring.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/lm_load_mirroring.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.state_enums import Mirroring from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( @@ -13,9 +13,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -45,21 +42,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: mirroring = Mirroring(tokens["mirroring"]) return cls(string=string, location=location, mirroring=mirroring) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - return ( - state.model_copy( - update={ - "mirroring": self.mirroring, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().load_mirroring.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/ln_load_name.py b/src/pygerber/gerberx3/tokenizer/tokens/ln_load_name.py index 5d5e5f4f..5a1fe15a 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/ln_load_name.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/ln_load_name.py @@ -17,15 +17,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.backend.abstract.backend_cls import Backend -from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, ) -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: from pyparsing import ParseResults @@ -67,15 +63,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: content: str = str(tokens["string"]) return cls(string=string, location=location, content=content) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - warn_deprecated_code("LN", "8.1") - return super().update_drawing_state(state, _backend) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().load_name.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/lp_load_polarity.py b/src/pygerber/gerberx3/tokenizer/tokens/lp_load_polarity.py index 98120343..1723873b 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/lp_load_polarity.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/lp_load_polarity.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.state_enums import Polarity from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( @@ -13,9 +13,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -45,21 +42,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: polarity = Polarity(tokens["polarity"]) return cls(string=string, location=location, polarity=polarity) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - return ( - state.model_copy( - update={ - "polarity": self.polarity, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().load_polarity.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/lr_load_rotation.py b/src/pygerber/gerberx3/tokenizer/tokens/lr_load_rotation.py index 4313cca2..e9057fac 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/lr_load_rotation.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/lr_load_rotation.py @@ -3,7 +3,7 @@ from __future__ import annotations from decimal import Decimal -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, @@ -13,9 +13,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -61,21 +58,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: rotation = Decimal(str(tokens["rotation"])) return cls(string=string, location=location, rotation=rotation) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - return ( - state.model_copy( - update={ - "rotation": self.rotation, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().load_rotation.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/ls_load_scaling.py b/src/pygerber/gerberx3/tokenizer/tokens/ls_load_scaling.py index 1687e348..be239fe8 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/ls_load_scaling.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/ls_load_scaling.py @@ -3,7 +3,7 @@ from __future__ import annotations from decimal import Decimal -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, @@ -13,9 +13,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -61,21 +58,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: scaling = Decimal(str(tokens["scaling"])) return cls(string=string, location=location, scaling=scaling) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Set drawing polarity.""" - return ( - state.model_copy( - update={ - "scaling": self.scaling, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().load_scaling.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/m00_program_stop.py b/src/pygerber/gerberx3/tokenizer/tokens/m00_program_stop.py index 3b1ee09f..dd53eed8 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/m00_program_stop.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/m00_program_stop.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.parser.errors import ExitParsingProcessInterrupt from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -20,14 +16,6 @@ class M00ProgramStop(CommandToken): See section 8.1.1 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - _state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Exit drawing process.""" - raise ExitParsingProcessInterrupt - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().program_stop.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/m01_optional_stop.py b/src/pygerber/gerberx3/tokenizer/tokens/m01_optional_stop.py index f6360fd8..b7fc76a9 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/m01_optional_stop.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/m01_optional_stop.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.parser.errors import ExitParsingProcessInterrupt from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -20,14 +16,6 @@ class M01OptionalStop(CommandToken): See section 8.1.1 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - _state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Exit drawing process.""" - raise ExitParsingProcessInterrupt - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().optional_stop.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/m02_end_of_file.py b/src/pygerber/gerberx3/tokenizer/tokens/m02_end_of_file.py index a5894737..fc4b3a7d 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/m02_end_of_file.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/m02_end_of_file.py @@ -2,15 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING -from pygerber.gerberx3.parser.errors import ExitParsingProcessInterrupt from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken if TYPE_CHECKING: - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -20,14 +16,6 @@ class M02EndOfFile(CommandToken): See section 4.10 of The Gerber Layer Format Specification Revision 2023.03 - https://argmaster.github.io/pygerber/latest/gerber_specification/revision_2023_03.html """ - def update_drawing_state( - self, - _state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Exit drawing process.""" - raise ExitParsingProcessInterrupt - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().end_of_file.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/am_macro.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/am_macro.py index 43d424e4..1eb7970b 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/am_macro.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/am_macro.py @@ -2,25 +2,16 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Iterable, Iterator, List, Tuple +from typing import TYPE_CHECKING, Iterator, List -from pygerber.backend.abstract.aperture_handle import PrivateApertureHandle -from pygerber.gerberx3.math.offset import Offset from pygerber.gerberx3.tokenizer.tokens.bases.group import TokenGroup from pygerber.gerberx3.tokenizer.tokens.bases.token import Token from pygerber.gerberx3.tokenizer.tokens.macro.macro_begin import MacroBegin -from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext -from pygerber.gerberx3.tokenizer.tokens.macro.statements.statement import ( - MacroStatementToken, -) if TYPE_CHECKING: from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -170,24 +161,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: tokens=macro_body_tokens, ) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Exit drawing process.""" - new_macros_dict = {**state.macros} - new_macros_dict[self.macro_name] = self - - return ( - state.model_copy( - update={ - "macros": new_macros_dict, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().macro_definition.pre_parser_visit_token(self, context) @@ -199,17 +172,3 @@ def __iter__(self) -> Iterator[Token]: for token in self.tokens: yield from token yield self - - def evaluate( - self, - state: State, - handle: PrivateApertureHandle, - parameters: dict[str, Offset], - ) -> None: - """Evaluate macro into series of DrawCommands.""" - context = MacroContext() - context.variables.update(parameters) - - for expression in self.tokens: - if isinstance(expression, MacroStatementToken): - expression.evaluate(context, state, handle) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/binary.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/binary.py index ac39e682..cb261140 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/binary.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/binary.py @@ -5,10 +5,6 @@ from operator import add, mul, sub, truediv from typing import TYPE_CHECKING, Any, Callable -from pygerber.gerberx3.math.offset import Offset -from pygerber.gerberx3.tokenizer.tokens.macro.expressions.errors import ( - InvalidArithmeticExpressionError, -) from pygerber.gerberx3.tokenizer.tokens.macro.expressions.macro_expression import ( MacroExpressionToken, ) @@ -17,10 +13,8 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context from pygerber.gerberx3.parser2.macro2.expressions2.expression2 import Expression2 - from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext class BinaryOperator(MacroExpressionToken): @@ -62,17 +56,6 @@ def to_parser2_expression(self, context: Parser2Context) -> Expression2: """Convert to `Expression2` descendant class.""" raise NotImplementedError - def evaluate_numeric(self, macro_context: MacroContext, state: State) -> Offset: - """Evaluate numeric value of this macro expression.""" - left = self.left.evaluate_numeric(macro_context, state) - right = self.right.evaluate_numeric(macro_context, state) - output = self.operator(left, right) - - if not isinstance(output, Offset): - raise InvalidArithmeticExpressionError - - return output - def get_gerber_code( self, indent: str = "", diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/macro_expression.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/macro_expression.py index 993c7202..0e3ab0ad 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/macro_expression.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/macro_expression.py @@ -4,14 +4,11 @@ from typing import TYPE_CHECKING -from pygerber.gerberx3.math.offset import Offset from pygerber.gerberx3.tokenizer.tokens.bases.token import Token if TYPE_CHECKING: - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context from pygerber.gerberx3.parser2.macro2.expressions2.expression2 import Expression2 - from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext class MacroExpressionToken(Token): @@ -45,12 +42,3 @@ class MacroExpressionToken(Token): def to_parser2_expression(self, context: Parser2Context) -> Expression2: """Convert to `Expression2` descendant class.""" raise NotImplementedError - - def evaluate_numeric( - self, - _macro_context: MacroContext, - state: State, - /, - ) -> Offset: - """Evaluate numeric value of this macro expression.""" - return Offset.new(value="0.0", unit=state.get_units()) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/numeric_constant.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/numeric_constant.py index 4fa142d6..9a43126d 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/numeric_constant.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/numeric_constant.py @@ -5,7 +5,6 @@ from decimal import Decimal from typing import TYPE_CHECKING -from pygerber.gerberx3.math.offset import Offset from pygerber.gerberx3.tokenizer.tokens.macro.expressions.macro_expression import ( MacroExpressionToken, ) @@ -14,10 +13,8 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context from pygerber.gerberx3.parser2.macro2.expressions2.expression2 import Expression2 - from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext class NumericConstant(MacroExpressionToken): @@ -52,10 +49,6 @@ def to_parser2_expression(self, context: Parser2Context) -> Expression2: value=self.value, ) - def evaluate_numeric(self, _macro_context: MacroContext, state: State) -> Offset: - """Evaluate numeric value of this macro expression.""" - return Offset.new(value=self.value, unit=state.get_units()) - def get_gerber_code( self, indent: str = "", # noqa: ARG002 diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/unary.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/unary.py index 41da30df..91f70887 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/unary.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/unary.py @@ -5,10 +5,6 @@ from operator import neg, pos from typing import TYPE_CHECKING, Any, Callable -from pygerber.gerberx3.math.offset import Offset -from pygerber.gerberx3.tokenizer.tokens.macro.expressions.errors import ( - InvalidArithmeticExpressionError, -) from pygerber.gerberx3.tokenizer.tokens.macro.expressions.macro_expression import ( MacroExpressionToken, ) @@ -17,10 +13,8 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context from pygerber.gerberx3.parser2.macro2.expressions2.expression2 import Expression2 - from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext class UnaryOperator(MacroExpressionToken): @@ -59,16 +53,6 @@ def to_parser2_expression(self, context: Parser2Context) -> Expression2: """Convert to `Expression2` descendant class.""" raise NotImplementedError - def evaluate_numeric(self, macro_context: MacroContext, state: State) -> Offset: - """Evaluate numeric value of this macro expression.""" - operand = self.operand.evaluate_numeric(macro_context, state) - output = self.operator(operand) - - if not isinstance(output, Offset): - raise InvalidArithmeticExpressionError - - return output - def get_gerber_code( self, indent: str = "", diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/variable_name.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/variable_name.py index 4032eeb3..7f15348f 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/variable_name.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/expressions/variable_name.py @@ -4,12 +4,9 @@ from typing import TYPE_CHECKING -from pygerber.gerberx3.math.offset import Offset -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.tokenizer.tokens.macro.expressions.macro_expression import ( MacroExpressionToken, ) -from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext if TYPE_CHECKING: from pyparsing import ParseResults @@ -69,10 +66,6 @@ def to_parser2_expression(self, context: Parser2Context) -> Expression2: """Convert to `Expression2` descendant class.""" return context.macro_expressions.variable_name(name=self.name) - def evaluate_numeric(self, macro_context: MacroContext, _state: State) -> Offset: - """Evaluate numeric value of this macro expression.""" - return macro_context.variables[self.name] - def get_gerber_code( self, indent: str = "", # noqa: ARG002 diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/code_1_circle.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/code_1_circle.py index 72aa8e2f..ce661498 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/code_1_circle.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/code_1_circle.py @@ -5,15 +5,12 @@ from decimal import Decimal from typing import TYPE_CHECKING, ClassVar, Optional -from pygerber.backend.abstract.aperture_handle import PrivateApertureHandle -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.tokenizer.tokens.macro.expressions.macro_expression import ( MacroExpressionToken, ) from pygerber.gerberx3.tokenizer.tokens.macro.expressions.numeric_constant import ( NumericConstant, ) -from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext from pygerber.gerberx3.tokenizer.tokens.macro.statements.primitive import ( MacroPrimitiveToken, ) @@ -92,21 +89,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: rotation=rotation, ) - def evaluate( - self, - macro_context: MacroContext, - state: State, - handle: PrivateApertureHandle, - ) -> None: - """Evaluate macro expression.""" - self.exposure.evaluate_numeric(macro_context, state) - self.diameter.evaluate_numeric(macro_context, state) - self.center_x.evaluate_numeric(macro_context, state) - self.center_y.evaluate_numeric(macro_context, state) - self.rotation.evaluate_numeric(macro_context, state) - - return super().evaluate(macro_context, state, handle) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().macro_code_1_circle.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/statement.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/statement.py index 69fa0283..bc923243 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/statement.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/statement.py @@ -2,28 +2,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from pygerber.backend.abstract.aperture_handle import PrivateApertureHandle from pygerber.gerberx3.tokenizer.tokens.bases.command import CommandToken -if TYPE_CHECKING: - from pygerber.gerberx3.parser.state import State - from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext - class MacroStatementToken(CommandToken): """Wrapper for in-macro expression.""" - def evaluate( - self, - macro_context: MacroContext, - state: State, - handle: PrivateApertureHandle, - /, - ) -> None: - """Evaluate macro statement.""" - def get_gerber_code( self, indent: str = "", diff --git a/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/variable_assignment.py b/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/variable_assignment.py index a17a5a53..cf536ed1 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/variable_assignment.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/macro/statements/variable_assignment.py @@ -4,15 +4,12 @@ from typing import TYPE_CHECKING -from pygerber.backend.abstract.aperture_handle import PrivateApertureHandle -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.tokenizer.tokens.macro.expressions.macro_expression import ( MacroExpressionToken, ) from pygerber.gerberx3.tokenizer.tokens.macro.expressions.variable_name import ( MacroVariableName, ) -from pygerber.gerberx3.tokenizer.tokens.macro.macro_context import MacroContext from pygerber.gerberx3.tokenizer.tokens.macro.statements.statement import ( MacroStatementToken, ) @@ -117,19 +114,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: value=value, ) - def evaluate( - self, - macro_context: MacroContext, - state: State, - handle: PrivateApertureHandle, - ) -> None: - """Evaluate macro expression.""" - name = self.variable.name - value = self.value.evaluate_numeric(macro_context, state) - macro_context.variables[name] = value - - return super().evaluate(macro_context, state, handle) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().macro_variable_assignment.pre_parser_visit_token( diff --git a/src/pygerber/gerberx3/tokenizer/tokens/mo_unit_mode.py b/src/pygerber/gerberx3/tokenizer/tokens/mo_unit_mode.py index e70889cd..ea599f5e 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/mo_unit_mode.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/mo_unit_mode.py @@ -2,8 +2,7 @@ from __future__ import annotations -import logging -from typing import TYPE_CHECKING, Iterable, Tuple +from typing import TYPE_CHECKING from pygerber.gerberx3.state_enums import Unit from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( @@ -14,9 +13,6 @@ from pyparsing import ParseResults from typing_extensions import Self - from pygerber.backend.abstract.backend_cls import Backend - from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand - from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.parser2.context2 import Parser2Context @@ -39,32 +35,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: unit: Unit = Unit(tokens["unit"]) return cls(string=string, location=location, unit=unit) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - if self.unit == Unit.Inches: - logging.warning( - "Detected use of imperial units. Using metric units is recommended. " - "Imperial units will be deprecated in future. " - "(See 4.2.1 in Gerber Layer Format Specification)", - ) - if state.draw_units is not None: - logging.warning( - "Overriding coordinate format is illegal. " - "(See 4.2.1 in Gerber Layer Format Specification)", - ) - return ( - state.model_copy( - update={ - "draw_units": self.unit, - }, - ), - (), - ) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().unit_mode.pre_parser_visit_token(self, context) diff --git a/src/pygerber/gerberx3/tokenizer/tokens/of_image_offset.py b/src/pygerber/gerberx3/tokenizer/tokens/of_image_offset.py index 9f09f30c..7971e831 100644 --- a/src/pygerber/gerberx3/tokenizer/tokens/of_image_offset.py +++ b/src/pygerber/gerberx3/tokenizer/tokens/of_image_offset.py @@ -18,15 +18,11 @@ from __future__ import annotations from decimal import Decimal -from typing import TYPE_CHECKING, Iterable, Optional, Tuple +from typing import TYPE_CHECKING, Optional -from pygerber.backend.abstract.backend_cls import Backend -from pygerber.backend.abstract.draw_commands.draw_command import DrawCommand -from pygerber.gerberx3.parser.state import State from pygerber.gerberx3.tokenizer.tokens.bases.extended_command import ( ExtendedCommandToken, ) -from pygerber.warnings import warn_deprecated_code if TYPE_CHECKING: from pyparsing import ParseResults @@ -72,15 +68,6 @@ def new(cls, string: str, location: int, tokens: ParseResults) -> Self: b = Decimal(str(tmp)) if (tmp := tokens.get("b")) is not None else None return cls(string=string, location=location, a=a, b=b) - def update_drawing_state( - self, - state: State, - _backend: Backend, - ) -> Tuple[State, Iterable[DrawCommand]]: - """Update drawing state.""" - warn_deprecated_code("IN", "8.1") - return super().update_drawing_state(state, _backend) - def parser2_visit_token(self, context: Parser2Context) -> None: """Perform actions on the context implicated by this token.""" context.get_hooks().image_offset.pre_parser_visit_token(self, context) diff --git a/test/examples/readme_example_1.py b/test/examples/readme_example_1.py deleted file mode 100644 index ef5620ae..00000000 --- a/test/examples/readme_example_1.py +++ /dev/null @@ -1,17 +0,0 @@ -from __future__ import annotations - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - -# Path to Gerber source file. -source_path = "test/examples/render_copper_from_path.grb" - -Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_path=source_path, - colors=ColorScheme.COPPER_ALPHA, - ), -).render().save("output.png") diff --git a/test/examples/readme_example_2.py b/test/examples/readme_example_2.py deleted file mode 100644 index 8d4934a0..00000000 --- a/test/examples/readme_example_2.py +++ /dev/null @@ -1,33 +0,0 @@ -from __future__ import annotations - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - -source_code = """ -%FSLAX26Y26*% -%MOMM*% -%ADD100R,1.5X1.0X0.5*% -%ADD200C,1.5X1.0*% -%ADD300O,1.5X1.0X0.6*% -%ADD400P,1.5X3X5.0*% -D100* -X0Y0D03* -D200* -X0Y2000000D03* -D300* -X2000000Y0D03* -D400* -X2000000Y2000000D03* -M02* -""" - -Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_code=source_code, - colors=ColorScheme.SILK, - dpi=3000, - ), -).render().save("output.png") diff --git a/test/examples/render_copper_from_buffer.py b/test/examples/render_copper_from_buffer.py deleted file mode 100644 index 5f38bb53..00000000 --- a/test/examples/render_copper_from_buffer.py +++ /dev/null @@ -1,42 +0,0 @@ -from __future__ import annotations - -from io import BytesIO - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - - -def render() -> None: - source_buffer = BytesIO( - b""" - %FSLAX26Y26*% - %MOMM*% - %ADD100R,1.5X1.0X0.5*% - %ADD200C,1.5X1.0*% - %ADD300O,1.5X1.0X0.6*% - %ADD400P,1.5X3X5.0*% - D100* - X0Y0D03* - D200* - X0Y2000000D03* - D300* - X2000000Y0D03* - D400* - X2000000Y2000000D03* - M02* - """, - ) - - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_buffer=source_buffer, - colors=ColorScheme.COPPER_ALPHA, - ), - ).render().save("output.png") - - -if __name__ == "__main__": - render() diff --git a/test/examples/render_copper_from_buffer_into_buffer.py b/test/examples/render_copper_from_buffer_into_buffer.py deleted file mode 100644 index 814f2036..00000000 --- a/test/examples/render_copper_from_buffer_into_buffer.py +++ /dev/null @@ -1,47 +0,0 @@ -from __future__ import annotations - -from io import BytesIO - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - - -def render() -> None: - source_buffer = BytesIO( - b""" - %FSLAX26Y26*% - %MOMM*% - %ADD100R,1.5X1.0X0.5*% - %ADD200C,1.5X1.0*% - %ADD300O,1.5X1.0X0.6*% - %ADD400P,1.5X3X5.0*% - D100* - X0Y0D03* - D200* - X0Y2000000D03* - D300* - X2000000Y0D03* - D400* - X2000000Y2000000D03* - M02* - """, - ) - - output = BytesIO() - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_buffer=source_buffer, - colors=ColorScheme.COPPER_ALPHA, - ), - ).render().save(output, format="png") - - output.seek(0) - content = output.read() - assert len(content) > 0 - - -if __name__ == "__main__": - render() diff --git a/test/examples/render_copper_from_path.py b/test/examples/render_copper_from_path.py deleted file mode 100644 index 098b4c33..00000000 --- a/test/examples/render_copper_from_path.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - - -def render() -> None: - source_path = Path(__file__).parent / "render_copper_from_path.grb" - - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_path=source_path, - colors=ColorScheme.COPPER_ALPHA, - ), - ).render().save("output.png") - - -if __name__ == "__main__": - render() diff --git a/test/examples/render_copper_from_path_into_buffer.py b/test/examples/render_copper_from_path_into_buffer.py deleted file mode 100644 index ceb48fb9..00000000 --- a/test/examples/render_copper_from_path_into_buffer.py +++ /dev/null @@ -1,30 +0,0 @@ -from __future__ import annotations - -from io import BytesIO -from pathlib import Path - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - - -def render() -> None: - source_path = Path(__file__).parent / "render_copper_from_path.grb" - - output = BytesIO() - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_path=source_path, - colors=ColorScheme.COPPER_ALPHA, - ), - ).render().save(output, format="png") - - output.seek(0) - content = output.read() - assert len(content) > 0 - - -if __name__ == "__main__": - render() diff --git a/test/examples/render_copper_from_string.py b/test/examples/render_copper_from_string.py deleted file mode 100644 index 6e06bc28..00000000 --- a/test/examples/render_copper_from_string.py +++ /dev/null @@ -1,29 +0,0 @@ -from __future__ import annotations - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - - -def render() -> None: - source_code = """ - %FSLAX26Y26*% - %MOMM*% - %ADD100C,1.5*% - D100* - X0Y0D03* - M02* - """ - - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_code=source_code, - colors=ColorScheme.COPPER_ALPHA, - ), - ).render().save("output.png") - - -if __name__ == "__main__": - render() diff --git a/test/examples/render_copper_from_string_into_buffer.py b/test/examples/render_copper_from_string_into_buffer.py deleted file mode 100644 index cf106510..00000000 --- a/test/examples/render_copper_from_string_into_buffer.py +++ /dev/null @@ -1,36 +0,0 @@ -from __future__ import annotations - -from io import BytesIO - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - - -def render() -> None: - source_code = """ - %FSLAX26Y26*% - %MOMM*% - %ADD100C,1.5*% - D100* - X0Y0D03* - M02* - """ - - output = BytesIO() - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_code=source_code, - colors=ColorScheme.COPPER_ALPHA, - ), - ).render().save(output, format="png") - - output.seek(0) - content = output.read() - assert len(content) > 0 - - -if __name__ == "__main__": - render() diff --git a/test/examples/test_examples.py b/test/examples/test_examples.py index 9fb51ee6..432e1116 100644 --- a/test/examples/test_examples.py +++ b/test/examples/test_examples.py @@ -9,12 +9,6 @@ from test.examples import ( introspect_minimal_example, introspect_mixed_inheritance, - render_copper_from_buffer, - render_copper_from_buffer_into_buffer, - render_copper_from_path, - render_copper_from_path_into_buffer, - render_copper_from_string, - render_copper_from_string_into_buffer, renderer_2_raster_render, renderer_2_svg_render, ) @@ -22,38 +16,6 @@ DIRECTORY = Path(__file__).parent -def test_readme_example_1() -> None: - exec((DIRECTORY / "readme_example_1.py").read_text()) # noqa: S102 - - -def test_readme_example_2() -> None: - exec((DIRECTORY / "readme_example_2.py").read_text()) # noqa: S102 - - -def test_render_copper_from_buffer() -> None: - render_copper_from_buffer.render() - - -def test_render_copper_from_string() -> None: - render_copper_from_string.render() - - -def test_render_copper_from_path() -> None: - render_copper_from_path.render() - - -def test_render_copper_from_buffer_into_buffer() -> None: - render_copper_from_buffer_into_buffer.render() - - -def test_render_copper_from_string_into_buffer() -> None: - render_copper_from_string_into_buffer.render() - - -def test_render_copper_from_path_into_buffer() -> None: - render_copper_from_path_into_buffer.render() - - def test_introspect_minimal_example() -> None: introspect_minimal_example.main() diff --git a/test/gerberx3/test_parser2/test_parser2hooks.py b/test/gerberx3/test_parser2/test_parser2hooks.py index 735e352d..e89a8cbb 100644 --- a/test/gerberx3/test_parser2/test_parser2hooks.py +++ b/test/gerberx3/test_parser2/test_parser2hooks.py @@ -8,10 +8,6 @@ from pygerber.gerberx3.math.offset import Offset from pygerber.gerberx3.math.vector_2d import Vector2D -from pygerber.gerberx3.parser.errors import ( - IncrementalCoordinatesNotSupportedError, - ZeroOmissionNotSupportedError, -) from pygerber.gerberx3.parser2.apertures2.circle2 import Circle2 from pygerber.gerberx3.parser2.apertures2.macro2 import Macro2 from pygerber.gerberx3.parser2.apertures2.obround2 import Obround2 @@ -29,6 +25,7 @@ from pygerber.gerberx3.parser2.context2 import Parser2Context from pygerber.gerberx3.parser2.errors2 import ( ApertureNotDefined2Error, + IncrementalCoordinatesNotSupported2Error, NoValidArcCenterFoundError, OnUpdateDrawingState2Error, ReferencedNotInitializedBlockBufferError, @@ -36,6 +33,7 @@ StepAndRepeatNotInitializedError, UnitNotSet2Error, UnnamedBlockApertureNotAllowedError, + ZeroOmissionNotSupported2Error, ) from pygerber.gerberx3.parser2.macro2.assignment2 import Assignment2 from pygerber.gerberx3.parser2.macro2.expressions2.binary2 import ( @@ -1146,7 +1144,7 @@ def test_coordinate_format_token_hooks_incremental_leading() -> None: except OnUpdateDrawingState2Error as e: assert isinstance( # noqa: PT017 e.__cause__, - IncrementalCoordinatesNotSupportedError, + IncrementalCoordinatesNotSupported2Error, ) else: pytest.fail("Not raised OnUpdateDrawingState2Error") @@ -1169,7 +1167,7 @@ def test_coordinate_format_token_hooks_incremental_trailing() -> None: except OnUpdateDrawingState2Error as e: assert isinstance( # noqa: PT017 e.__cause__, - ZeroOmissionNotSupportedError, + ZeroOmissionNotSupported2Error, ) else: pytest.fail("Not raised OnUpdateDrawingState2Error") From 869a8e7296aaf964a2888dac0f3eebad9d8d669c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 22 Jul 2024 21:02:25 +0200 Subject: [PATCH 06/91] Add more test assets for indivitual ast node types --- .../gerberx3/tokens/aperture/block/empty.grb | 2 + .../tokens/aperture/block/with_ops.grb | 8 ++++ .../gerberx3/tokens/aperture/macro/empty.grb | 1 + .../tokens/aperture/macro/instantiate.grb | 2 + .../tokens/aperture/step_repeat/0.grb | 2 + .../tokens/aperture/step_repeat/1.grb | 2 + .../tokens/aperture/step_repeat/2.grb | 6 +++ test/assets/gerberx3/tokens/attribute/TA.grb | 7 ++++ .../gerberx3/tokens/attribute/TA_custom.grb | 2 + test/assets/gerberx3/tokens/attribute/TD.grb | 4 ++ .../gerberx3/tokens/attribute/TD_custom.grb | 2 + test/assets/gerberx3/tokens/attribute/TF.grb | 38 +++++++++++++++++++ .../gerberx3/tokens/attribute/TF_custom.grb | 3 ++ test/assets/gerberx3/tokens/attribute/TO.grb | 9 +++++ .../tokens/attribute/comment_attribute.grb | 1 + .../tokens/{d_select => d_codes}/D11.grb | 0 .../tokens/{d_select => d_codes}/D12.grb | 0 .../tokens/{d_select => d_codes}/D301.grb | 0 .../tokens/{d_select => d_codes}/D999.grb | 0 .../gerberx3/tokens/g_codes/G04_text.grb | 4 ++ test/assets/gerberx3/tokens/load/LM.grb | 4 ++ test/assets/gerberx3/tokens/load/LN.grb | 1 + test/assets/gerberx3/tokens/load/LP.grb | 2 + test/assets/gerberx3/tokens/load/LR.grb | 6 +++ test/assets/gerberx3/tokens/load/LS.grb | 3 ++ test/assets/gerberx3/tokens/m_codes/M00.grb | 3 ++ test/assets/gerberx3/tokens/m_codes/M01.grb | 3 ++ test/assets/gerberx3/tokens/m_codes/M02.grb | 3 ++ .../tokens/math/assignment/constant.grb | 2 + .../tokens/math/assignment/expression.grb | 2 + .../tokens/math/assignment/variable.grb | 2 + .../gerberx3/tokens/math/binary/const/add.grb | 3 ++ .../gerberx3/tokens/math/binary/const/div.grb | 3 ++ .../gerberx3/tokens/math/binary/const/mul.grb | 4 ++ .../gerberx3/tokens/math/binary/const/sub.grb | 3 ++ .../tokens/math/binary/variable/add.grb | 3 ++ .../tokens/math/binary/variable/add_left.grb | 3 ++ .../tokens/math/binary/variable/add_right.grb | 3 ++ .../tokens/math/binary/variable/div.grb | 3 ++ .../tokens/math/binary/variable/div_left.grb | 3 ++ .../tokens/math/binary/variable/div_right.grb | 3 ++ .../tokens/math/binary/variable/mul.grb | 4 ++ .../tokens/math/binary/variable/mul_left.grb | 4 ++ .../tokens/math/binary/variable/mul_right.grb | 4 ++ .../tokens/math/binary/variable/sub.grb | 3 ++ .../tokens/math/binary/variable/sub_left.grb | 3 ++ .../tokens/math/binary/variable/sub_right.grb | 3 ++ .../gerberx3/tokens/math/order/add_div.grb | 3 ++ .../gerberx3/tokens/math/order/add_mul.grb | 3 ++ .../gerberx3/tokens/math/order/add_sub.grb | 3 ++ .../gerberx3/tokens/math/order/div_add.grb | 3 ++ .../gerberx3/tokens/math/order/div_mul.grb | 3 ++ .../gerberx3/tokens/math/order/div_sub.grb | 3 ++ .../gerberx3/tokens/math/order/mul_add.grb | 3 ++ .../gerberx3/tokens/math/order/mul_div.grb | 3 ++ .../gerberx3/tokens/math/order/mul_sub.grb | 3 ++ .../gerberx3/tokens/math/order/sub_add.grb | 3 ++ .../gerberx3/tokens/math/order/sub_div.grb | 3 ++ .../gerberx3/tokens/math/order/sub_mul.grb | 3 ++ .../gerberx3/tokens/math/order/sub_sub.grb | 3 ++ test/assets/gerberx3/tokens/math/parens/0.grb | 3 ++ test/assets/gerberx3/tokens/math/parens/1.grb | 3 ++ test/assets/gerberx3/tokens/math/parens/2.grb | 3 ++ test/assets/gerberx3/tokens/math/parens/3.grb | 3 ++ test/assets/gerberx3/tokens/math/parens/4.grb | 3 ++ .../gerberx3/tokens/math/unary/neg_const.grb | 3 ++ .../gerberx3/tokens/math/unary/neg_var.grb | 3 ++ .../gerberx3/tokens/math/unary/pos_const.grb | 3 ++ .../gerberx3/tokens/math/unary/pos_var.grb | 3 ++ .../gerberx3/tokens/primitives/code_0/0.grb | 9 +++++ .../tokens/primitives/code_1/0+variables.grb | 1 + .../gerberx3/tokens/primitives/code_1/0.grb | 1 + .../tokens/primitives/code_1/1+variables.grb | 3 ++ .../gerberx3/tokens/primitives/code_1/1.grb | 3 ++ .../tokens/primitives/code_1/2+math.grb | 7 ++++ .../tokens/primitives/code_1/2+variables.grb | 7 ++++ .../gerberx3/tokens/primitives/code_1/2.grb | 7 ++++ .../tokens/primitives/code_2/0+variables.grb | 3 ++ .../gerberx3/tokens/primitives/code_2/0.grb | 3 ++ .../tokens/primitives/code_2/2+math.grb | 10 +++++ .../tokens/primitives/code_20/0+variables.grb | 3 ++ .../gerberx3/tokens/primitives/code_20/0.grb | 3 ++ .../tokens/primitives/code_20/2+math.grb | 10 +++++ .../tokens/primitives/code_21/0+variables.grb | 3 ++ .../gerberx3/tokens/primitives/code_21/0.grb | 3 ++ .../tokens/primitives/code_21/2+math.grb | 9 +++++ .../tokens/primitives/code_22/0+variables.grb | 1 + .../gerberx3/tokens/primitives/code_22/0.grb | 1 + .../tokens/primitives/code_22/2+math.grb | 9 +++++ .../tokens/primitives/code_4/0+variables.grb | 1 + .../gerberx3/tokens/primitives/code_4/0.grb | 1 + .../tokens/primitives/code_4/1+variables.grb | 3 ++ .../gerberx3/tokens/primitives/code_4/1.grb | 3 ++ .../tokens/primitives/code_4/2+math.grb | 14 +++++++ .../tokens/primitives/code_5/0+variables.grb | 1 + .../gerberx3/tokens/primitives/code_5/0.grb | 1 + .../tokens/primitives/code_5/2+math.grb | 9 +++++ .../tokens/primitives/code_6/0+variables.grb | 1 + .../gerberx3/tokens/primitives/code_6/0.grb | 1 + .../tokens/primitives/code_6/2+math.grb | 12 ++++++ .../tokens/primitives/code_7/0+variables.grb | 1 + .../gerberx3/tokens/primitives/code_7/0.grb | 1 + .../tokens/primitives/code_7/1+variables.grb | 3 ++ .../gerberx3/tokens/primitives/code_7/1.grb | 3 ++ .../tokens/primitives/code_7/2+math.grb | 9 +++++ .../tokens/primitives/code_7/2+variables.grb | 9 +++++ .../gerberx3/tokens/primitives/code_7/2.grb | 9 +++++ .../tokens/{fs => properties}/FSLAX26Y26.grb | 0 .../gerberx3/tokens/properties/FSLAX34Y43.grb | 1 + .../tokens/{fs => properties}/FSLAX66Y66.grb | 0 .../tokens/{fs => properties}/FSLIX26Y26.grb | 0 .../tokens/{fs => properties}/FSTIX26Y27.grb | 0 .../tokens/{set_codes => properties}/as.grb | 0 test/assets/gerberx3/tokens/properties/in.grb | 1 + test/assets/gerberx3/tokens/properties/ip.grb | 2 + test/assets/gerberx3/tokens/properties/ir.grb | 4 ++ test/assets/gerberx3/tokens/properties/mi.grb | 6 +++ .../tokens/{set_codes => properties}/mo.grb | 0 test/assets/gerberx3/tokens/properties/of.grb | 3 ++ test/assets/gerberx3/tokens/properties/sf.grb | 2 + 120 files changed, 439 insertions(+) create mode 100644 test/assets/gerberx3/tokens/aperture/block/empty.grb create mode 100644 test/assets/gerberx3/tokens/aperture/block/with_ops.grb create mode 100644 test/assets/gerberx3/tokens/aperture/macro/empty.grb create mode 100644 test/assets/gerberx3/tokens/aperture/macro/instantiate.grb create mode 100644 test/assets/gerberx3/tokens/aperture/step_repeat/0.grb create mode 100644 test/assets/gerberx3/tokens/aperture/step_repeat/1.grb create mode 100644 test/assets/gerberx3/tokens/aperture/step_repeat/2.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TA.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TA_custom.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TD.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TD_custom.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TF.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TF_custom.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO.grb create mode 100644 test/assets/gerberx3/tokens/attribute/comment_attribute.grb rename test/assets/gerberx3/tokens/{d_select => d_codes}/D11.grb (100%) rename test/assets/gerberx3/tokens/{d_select => d_codes}/D12.grb (100%) rename test/assets/gerberx3/tokens/{d_select => d_codes}/D301.grb (100%) rename test/assets/gerberx3/tokens/{d_select => d_codes}/D999.grb (100%) create mode 100644 test/assets/gerberx3/tokens/load/LM.grb create mode 100644 test/assets/gerberx3/tokens/load/LN.grb create mode 100644 test/assets/gerberx3/tokens/load/LP.grb create mode 100644 test/assets/gerberx3/tokens/load/LR.grb create mode 100644 test/assets/gerberx3/tokens/load/LS.grb create mode 100644 test/assets/gerberx3/tokens/m_codes/M00.grb create mode 100644 test/assets/gerberx3/tokens/m_codes/M01.grb create mode 100644 test/assets/gerberx3/tokens/m_codes/M02.grb create mode 100644 test/assets/gerberx3/tokens/math/assignment/constant.grb create mode 100644 test/assets/gerberx3/tokens/math/assignment/expression.grb create mode 100644 test/assets/gerberx3/tokens/math/assignment/variable.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/const/add.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/const/div.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/const/mul.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/const/sub.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/add.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/add_left.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/add_right.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/div.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/div_left.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/div_right.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/mul.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/mul_left.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/mul_right.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/sub.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/sub_left.grb create mode 100644 test/assets/gerberx3/tokens/math/binary/variable/sub_right.grb create mode 100644 test/assets/gerberx3/tokens/math/order/add_div.grb create mode 100644 test/assets/gerberx3/tokens/math/order/add_mul.grb create mode 100644 test/assets/gerberx3/tokens/math/order/add_sub.grb create mode 100644 test/assets/gerberx3/tokens/math/order/div_add.grb create mode 100644 test/assets/gerberx3/tokens/math/order/div_mul.grb create mode 100644 test/assets/gerberx3/tokens/math/order/div_sub.grb create mode 100644 test/assets/gerberx3/tokens/math/order/mul_add.grb create mode 100644 test/assets/gerberx3/tokens/math/order/mul_div.grb create mode 100644 test/assets/gerberx3/tokens/math/order/mul_sub.grb create mode 100644 test/assets/gerberx3/tokens/math/order/sub_add.grb create mode 100644 test/assets/gerberx3/tokens/math/order/sub_div.grb create mode 100644 test/assets/gerberx3/tokens/math/order/sub_mul.grb create mode 100644 test/assets/gerberx3/tokens/math/order/sub_sub.grb create mode 100644 test/assets/gerberx3/tokens/math/parens/0.grb create mode 100644 test/assets/gerberx3/tokens/math/parens/1.grb create mode 100644 test/assets/gerberx3/tokens/math/parens/2.grb create mode 100644 test/assets/gerberx3/tokens/math/parens/3.grb create mode 100644 test/assets/gerberx3/tokens/math/parens/4.grb create mode 100644 test/assets/gerberx3/tokens/math/unary/neg_const.grb create mode 100644 test/assets/gerberx3/tokens/math/unary/neg_var.grb create mode 100644 test/assets/gerberx3/tokens/math/unary/pos_const.grb create mode 100644 test/assets/gerberx3/tokens/math/unary/pos_var.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_0/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/1+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/1.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/2+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/2.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_2/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_2/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_2/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_20/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_20/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_20/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_21/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_21/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_21/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_22/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_22/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_22/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_4/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_4/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_4/1+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_4/1.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_4/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_5/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_5/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_5/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_6/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_6/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_6/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_7/0+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_7/0.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_7/1+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_7/1.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_7/2+math.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_7/2+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_7/2.grb rename test/assets/gerberx3/tokens/{fs => properties}/FSLAX26Y26.grb (100%) create mode 100644 test/assets/gerberx3/tokens/properties/FSLAX34Y43.grb rename test/assets/gerberx3/tokens/{fs => properties}/FSLAX66Y66.grb (100%) rename test/assets/gerberx3/tokens/{fs => properties}/FSLIX26Y26.grb (100%) rename test/assets/gerberx3/tokens/{fs => properties}/FSTIX26Y27.grb (100%) rename test/assets/gerberx3/tokens/{set_codes => properties}/as.grb (100%) create mode 100644 test/assets/gerberx3/tokens/properties/in.grb create mode 100644 test/assets/gerberx3/tokens/properties/ip.grb create mode 100644 test/assets/gerberx3/tokens/properties/ir.grb create mode 100644 test/assets/gerberx3/tokens/properties/mi.grb rename test/assets/gerberx3/tokens/{set_codes => properties}/mo.grb (100%) create mode 100644 test/assets/gerberx3/tokens/properties/of.grb create mode 100644 test/assets/gerberx3/tokens/properties/sf.grb diff --git a/test/assets/gerberx3/tokens/aperture/block/empty.grb b/test/assets/gerberx3/tokens/aperture/block/empty.grb new file mode 100644 index 00000000..94b69862 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/block/empty.grb @@ -0,0 +1,2 @@ +%ABD12*% +%AB*% diff --git a/test/assets/gerberx3/tokens/aperture/block/with_ops.grb b/test/assets/gerberx3/tokens/aperture/block/with_ops.grb new file mode 100644 index 00000000..4245af42 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/block/with_ops.grb @@ -0,0 +1,8 @@ +%ABD100*% +D10* +X65532000Y17605375D02* +Y65865375D01* +X-3556000D01* +D11* +X-3556000Y17605375D03* +%AB*% diff --git a/test/assets/gerberx3/tokens/aperture/macro/empty.grb b/test/assets/gerberx3/tokens/aperture/macro/empty.grb new file mode 100644 index 00000000..5b10a438 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/macro/empty.grb @@ -0,0 +1 @@ +%AMTHERMAL80*% diff --git a/test/assets/gerberx3/tokens/aperture/macro/instantiate.grb b/test/assets/gerberx3/tokens/aperture/macro/instantiate.grb new file mode 100644 index 00000000..62789008 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/macro/instantiate.grb @@ -0,0 +1,2 @@ +%AMDONUTVAR*1,1,$1,$2,$3*1,0,$4,$2,$3*% +%ADD34DONUTVAR,0.100X0X0X0.080*% diff --git a/test/assets/gerberx3/tokens/aperture/step_repeat/0.grb b/test/assets/gerberx3/tokens/aperture/step_repeat/0.grb new file mode 100644 index 00000000..650f3f27 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/step_repeat/0.grb @@ -0,0 +1,2 @@ +%SRX2Y3I2.0J3.0*% +%SR*% diff --git a/test/assets/gerberx3/tokens/aperture/step_repeat/1.grb b/test/assets/gerberx3/tokens/aperture/step_repeat/1.grb new file mode 100644 index 00000000..a68885e7 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/step_repeat/1.grb @@ -0,0 +1,2 @@ +%SRX4Y1I5.0J0*% +%SR*% diff --git a/test/assets/gerberx3/tokens/aperture/step_repeat/2.grb b/test/assets/gerberx3/tokens/aperture/step_repeat/2.grb new file mode 100644 index 00000000..6b31c1e0 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/step_repeat/2.grb @@ -0,0 +1,6 @@ +%SRX3Y2I5.0J4.0*% +D13* +X123456Y789012D03* +D14* +X456789Y012345D03* +%SR*% diff --git a/test/assets/gerberx3/tokens/attribute/TA.grb b/test/assets/gerberx3/tokens/attribute/TA.grb new file mode 100644 index 00000000..0be31a80 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TA.grb @@ -0,0 +1,7 @@ +%TA.AperFunction,AntiPad*% +%TA.AperFunction,ViaPad*% +%TA.DrillTolerance,0.02,0.01*% +%TA.AperFunction,ComponentDrill*% +%TA.AperFunction,Other,Special*% +%TA.AperFunction,MechanicalDrill*% +%TA.DrillTolerance,0.15,0.15*% diff --git a/test/assets/gerberx3/tokens/attribute/TA_custom.grb b/test/assets/gerberx3/tokens/attribute/TA_custom.grb new file mode 100644 index 00000000..e2e46cc2 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TA_custom.grb @@ -0,0 +1,2 @@ +%TAMyApertureAttributeWithValue,value*% +%TAMyApertureAttributeWithoutValue*% diff --git a/test/assets/gerberx3/tokens/attribute/TD.grb b/test/assets/gerberx3/tokens/attribute/TD.grb new file mode 100644 index 00000000..b2815991 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TD.grb @@ -0,0 +1,4 @@ +%TD.AperFunction*% +%TD.FlashText*% +%TD.DrillTolerance*% +%TD*% diff --git a/test/assets/gerberx3/tokens/attribute/TD_custom.grb b/test/assets/gerberx3/tokens/attribute/TD_custom.grb new file mode 100644 index 00000000..669fbe89 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TD_custom.grb @@ -0,0 +1,2 @@ +%TDIAmATA*% +%TDCustAttr *% diff --git a/test/assets/gerberx3/tokens/attribute/TF.grb b/test/assets/gerberx3/tokens/attribute/TF.grb new file mode 100644 index 00000000..10c27a3c --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF.grb @@ -0,0 +1,38 @@ +%TF.FileFunction,Soldermask,Top*% +%TF.Part,Other,example*% +%TF.FileFunction,Other,Sample*% +%TF.Part,Array*% +%TF.GenerationSoftware,Ucamco,UcamX,2016.04-160425*% +%TF.CreationDate,2016-04-25T00:00;00+01:00*% +%TF.Part,Other,Testfile*% +%TF.FileFunction,Copper,L1,Top*% + +%TF.FileFunction,Legend,Top*% +%TF.FileFunction,Soldermask,Top*% +%TF.FileFunction,Copper,L1,Top*% +%TF.FileFunction,Copper,L2,Inr,Plane*% +%TF.FileFunction,Copper,L3,Inr,Plane*% +%TF.FileFunction,Copper,L4,Bot*% +%TF.FileFunction,Soldermask,Bot*% +%TF.FileFunction,NonPlated,1,4,NPTH,Drill*% +%TF.FileFunction,NonPlated,1,4,NPTH,Rout*% +%TF.FileFunction,Plated,1,4,PTH*% +%TF.FileFunction,Profile,NP*% +%TF.FileFunction,Drillmap*% + +%TF.FileFunction,Copper,L2,Inr,Plane*% +%TF.FilePolarity,Positive*% + +%TF.FileFunction,Soldermask,Top*% +%TF.FilePolarity,Negative*% + +%TF.SameCoordinates*% +%TF.SameCoordinates,f81d4fae-7dec-11d0-a765-00a0c91e6bf6*% + +%TF.ProjectId,My PCB,f81d4fae-7dec-11d0-a765-00a0c91e6bf6,2*% +%TF.ProjectId,project#8,68753a44-4D6F-1226-9C60-0050E4C00067,/main/18*% + +%TF.MD5,6ab9e892830469cdff7e3e346331d404*% + +%TF.FileFunction,Plated,1,8,PTH*% +%TF.Part,Single*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_custom.grb b/test/assets/gerberx3/tokens/attribute/TF_custom.grb new file mode 100644 index 00000000..b6a0fde4 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_custom.grb @@ -0,0 +1,3 @@ +%TFMyAttribute,Yes*% +%TFZap*% +%TFZonk*% diff --git a/test/assets/gerberx3/tokens/attribute/TO.grb b/test/assets/gerberx3/tokens/attribute/TO.grb new file mode 100644 index 00000000..d6b762b5 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO.grb @@ -0,0 +1,9 @@ +%TO.C,R6*% +%TO.P,IC12,2,TRIG*% +%TO.P,IC12,1,GND*% +%TO.P,R5,1*% +%TO.P,R5,2*% +%TO.C,R2*% +%TO.P,U1,4*% +%TO.N,Clk3*% +%TO.C,R301*% diff --git a/test/assets/gerberx3/tokens/attribute/comment_attribute.grb b/test/assets/gerberx3/tokens/attribute/comment_attribute.grb new file mode 100644 index 00000000..160006e8 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/comment_attribute.grb @@ -0,0 +1 @@ +G04 #@! TA.AperFunction,SMDPad,CuDef* diff --git a/test/assets/gerberx3/tokens/d_select/D11.grb b/test/assets/gerberx3/tokens/d_codes/D11.grb similarity index 100% rename from test/assets/gerberx3/tokens/d_select/D11.grb rename to test/assets/gerberx3/tokens/d_codes/D11.grb diff --git a/test/assets/gerberx3/tokens/d_select/D12.grb b/test/assets/gerberx3/tokens/d_codes/D12.grb similarity index 100% rename from test/assets/gerberx3/tokens/d_select/D12.grb rename to test/assets/gerberx3/tokens/d_codes/D12.grb diff --git a/test/assets/gerberx3/tokens/d_select/D301.grb b/test/assets/gerberx3/tokens/d_codes/D301.grb similarity index 100% rename from test/assets/gerberx3/tokens/d_select/D301.grb rename to test/assets/gerberx3/tokens/d_codes/D301.grb diff --git a/test/assets/gerberx3/tokens/d_select/D999.grb b/test/assets/gerberx3/tokens/d_codes/D999.grb similarity index 100% rename from test/assets/gerberx3/tokens/d_select/D999.grb rename to test/assets/gerberx3/tokens/d_codes/D999.grb diff --git a/test/assets/gerberx3/tokens/g_codes/G04_text.grb b/test/assets/gerberx3/tokens/g_codes/G04_text.grb index 5dde3b7a..9cd09e26 100644 --- a/test/assets/gerberx3/tokens/g_codes/G04_text.grb +++ b/test/assets/gerberx3/tokens/g_codes/G04_text.grb @@ -1,2 +1,6 @@ G04 Comment text :D * G04Comment text :D* +G4 Comment text :D * +G4Comment text :D * +G004 Comment text :D * +G004Comment text :D* diff --git a/test/assets/gerberx3/tokens/load/LM.grb b/test/assets/gerberx3/tokens/load/LM.grb new file mode 100644 index 00000000..bfc36083 --- /dev/null +++ b/test/assets/gerberx3/tokens/load/LM.grb @@ -0,0 +1,4 @@ +%LMN*% +%LMX*% +%LMY*% +%LMXY*% diff --git a/test/assets/gerberx3/tokens/load/LN.grb b/test/assets/gerberx3/tokens/load/LN.grb new file mode 100644 index 00000000..186718fc --- /dev/null +++ b/test/assets/gerberx3/tokens/load/LN.grb @@ -0,0 +1 @@ +%LNVia_anti-pads*% diff --git a/test/assets/gerberx3/tokens/load/LP.grb b/test/assets/gerberx3/tokens/load/LP.grb new file mode 100644 index 00000000..ee9e17b0 --- /dev/null +++ b/test/assets/gerberx3/tokens/load/LP.grb @@ -0,0 +1,2 @@ +%LPD*% +%LPC*% diff --git a/test/assets/gerberx3/tokens/load/LR.grb b/test/assets/gerberx3/tokens/load/LR.grb new file mode 100644 index 00000000..045c0ce5 --- /dev/null +++ b/test/assets/gerberx3/tokens/load/LR.grb @@ -0,0 +1,6 @@ +%LR90*% +%LR90.0*% +%LR180*% +%LR180.0*% +%LR270*% +%LR270.0*% diff --git a/test/assets/gerberx3/tokens/load/LS.grb b/test/assets/gerberx3/tokens/load/LS.grb new file mode 100644 index 00000000..6d33afd2 --- /dev/null +++ b/test/assets/gerberx3/tokens/load/LS.grb @@ -0,0 +1,3 @@ +%LS0.8*% +%LS.8*% +%LS1.3*% diff --git a/test/assets/gerberx3/tokens/m_codes/M00.grb b/test/assets/gerberx3/tokens/m_codes/M00.grb new file mode 100644 index 00000000..21ec4feb --- /dev/null +++ b/test/assets/gerberx3/tokens/m_codes/M00.grb @@ -0,0 +1,3 @@ +M0* +M00* +M000* diff --git a/test/assets/gerberx3/tokens/m_codes/M01.grb b/test/assets/gerberx3/tokens/m_codes/M01.grb new file mode 100644 index 00000000..93c3f664 --- /dev/null +++ b/test/assets/gerberx3/tokens/m_codes/M01.grb @@ -0,0 +1,3 @@ +M1* +M01* +M001* diff --git a/test/assets/gerberx3/tokens/m_codes/M02.grb b/test/assets/gerberx3/tokens/m_codes/M02.grb new file mode 100644 index 00000000..5392e2cd --- /dev/null +++ b/test/assets/gerberx3/tokens/m_codes/M02.grb @@ -0,0 +1,3 @@ +M2* +M02* +M002* diff --git a/test/assets/gerberx3/tokens/math/assignment/constant.grb b/test/assets/gerberx3/tokens/math/assignment/constant.grb new file mode 100644 index 00000000..5769c110 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/assignment/constant.grb @@ -0,0 +1,2 @@ +%AMDONUTCAL* +$2=1.0*% diff --git a/test/assets/gerberx3/tokens/math/assignment/expression.grb b/test/assets/gerberx3/tokens/math/assignment/expression.grb new file mode 100644 index 00000000..57ca4de4 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/assignment/expression.grb @@ -0,0 +1,2 @@ +%AMDONUTCAL* +$2=$1x0.25*% diff --git a/test/assets/gerberx3/tokens/math/assignment/variable.grb b/test/assets/gerberx3/tokens/math/assignment/variable.grb new file mode 100644 index 00000000..043269aa --- /dev/null +++ b/test/assets/gerberx3/tokens/math/assignment/variable.grb @@ -0,0 +1,2 @@ +%AMDONUTCAL* +$2=$1*% diff --git a/test/assets/gerberx3/tokens/math/binary/const/add.grb b/test/assets/gerberx3/tokens/math/binary/const/add.grb new file mode 100644 index 00000000..eb894c29 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/const/add.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$1=1.0+0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/const/div.grb b/test/assets/gerberx3/tokens/math/binary/const/div.grb new file mode 100644 index 00000000..94667770 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/const/div.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$1=1.0/0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/const/mul.grb b/test/assets/gerberx3/tokens/math/binary/const/mul.grb new file mode 100644 index 00000000..3130c7d6 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/const/mul.grb @@ -0,0 +1,4 @@ +%AMTEST1* +$1=1.0x0.75* +$1=1.0X0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/const/sub.grb b/test/assets/gerberx3/tokens/math/binary/const/sub.grb new file mode 100644 index 00000000..fbd5a45e --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/const/sub.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=1.0-0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/add.grb b/test/assets/gerberx3/tokens/math/binary/variable/add.grb new file mode 100644 index 00000000..f325a51e --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/add.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=$1+$2* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/add_left.grb b/test/assets/gerberx3/tokens/math/binary/variable/add_left.grb new file mode 100644 index 00000000..3e9f8e5c --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/add_left.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=$1+0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/add_right.grb b/test/assets/gerberx3/tokens/math/binary/variable/add_right.grb new file mode 100644 index 00000000..8b7674f8 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/add_right.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=0.75+$1* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/div.grb b/test/assets/gerberx3/tokens/math/binary/variable/div.grb new file mode 100644 index 00000000..cbc0f19e --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/div.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=$2/$1* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/div_left.grb b/test/assets/gerberx3/tokens/math/binary/variable/div_left.grb new file mode 100644 index 00000000..a14b0553 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/div_left.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=$1/0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/div_right.grb b/test/assets/gerberx3/tokens/math/binary/variable/div_right.grb new file mode 100644 index 00000000..2d450cbf --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/div_right.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=0.75/$1* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/mul.grb b/test/assets/gerberx3/tokens/math/binary/variable/mul.grb new file mode 100644 index 00000000..ab25115f --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/mul.grb @@ -0,0 +1,4 @@ +%AMTEST1* +$4=$1x$2* +$4=$1X$2* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/mul_left.grb b/test/assets/gerberx3/tokens/math/binary/variable/mul_left.grb new file mode 100644 index 00000000..1e744972 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/mul_left.grb @@ -0,0 +1,4 @@ +%AMTEST1* +$4=$1x0.75* +$4=$1X0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/mul_right.grb b/test/assets/gerberx3/tokens/math/binary/variable/mul_right.grb new file mode 100644 index 00000000..8e44c8ee --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/mul_right.grb @@ -0,0 +1,4 @@ +%AMTEST1* +$4=0.75x$1* +$4=0.75X$1* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/sub.grb b/test/assets/gerberx3/tokens/math/binary/variable/sub.grb new file mode 100644 index 00000000..42f47a42 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/sub.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=$2-$1* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/sub_left.grb b/test/assets/gerberx3/tokens/math/binary/variable/sub_left.grb new file mode 100644 index 00000000..41e4e5cf --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/sub_left.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=$1-0.75* +% diff --git a/test/assets/gerberx3/tokens/math/binary/variable/sub_right.grb b/test/assets/gerberx3/tokens/math/binary/variable/sub_right.grb new file mode 100644 index 00000000..879bbe96 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/binary/variable/sub_right.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$4=0.75-$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/add_div.grb b/test/assets/gerberx3/tokens/math/order/add_div.grb new file mode 100644 index 00000000..a7afbef0 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/add_div.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100-$1/1.75* +% diff --git a/test/assets/gerberx3/tokens/math/order/add_mul.grb b/test/assets/gerberx3/tokens/math/order/add_mul.grb new file mode 100644 index 00000000..68f64a85 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/add_mul.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100+$1x1.75* +% diff --git a/test/assets/gerberx3/tokens/math/order/add_sub.grb b/test/assets/gerberx3/tokens/math/order/add_sub.grb new file mode 100644 index 00000000..ddaea733 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/add_sub.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100+$1-1.75* +% diff --git a/test/assets/gerberx3/tokens/math/order/div_add.grb b/test/assets/gerberx3/tokens/math/order/div_add.grb new file mode 100644 index 00000000..3cd4e684 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/div_add.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75/100+$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/div_mul.grb b/test/assets/gerberx3/tokens/math/order/div_mul.grb new file mode 100644 index 00000000..651993ef --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/div_mul.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75/100x$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/div_sub.grb b/test/assets/gerberx3/tokens/math/order/div_sub.grb new file mode 100644 index 00000000..98637455 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/div_sub.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75/100-$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/mul_add.grb b/test/assets/gerberx3/tokens/math/order/mul_add.grb new file mode 100644 index 00000000..1ada99a0 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/mul_add.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75x100+$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/mul_div.grb b/test/assets/gerberx3/tokens/math/order/mul_div.grb new file mode 100644 index 00000000..0fa271d5 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/mul_div.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75x100/$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/mul_sub.grb b/test/assets/gerberx3/tokens/math/order/mul_sub.grb new file mode 100644 index 00000000..b818c895 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/mul_sub.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75x100-$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/sub_add.grb b/test/assets/gerberx3/tokens/math/order/sub_add.grb new file mode 100644 index 00000000..7f5ee6b5 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/sub_add.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100-$1+1.75* +% diff --git a/test/assets/gerberx3/tokens/math/order/sub_div.grb b/test/assets/gerberx3/tokens/math/order/sub_div.grb new file mode 100644 index 00000000..e9ea7386 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/sub_div.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100+$1/1.75* +% diff --git a/test/assets/gerberx3/tokens/math/order/sub_mul.grb b/test/assets/gerberx3/tokens/math/order/sub_mul.grb new file mode 100644 index 00000000..f8fa0a0f --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/sub_mul.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100-$1x1.75* +% diff --git a/test/assets/gerberx3/tokens/math/order/sub_sub.grb b/test/assets/gerberx3/tokens/math/order/sub_sub.grb new file mode 100644 index 00000000..112ae195 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/sub_sub.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100-$1-1.75* +% diff --git a/test/assets/gerberx3/tokens/math/parens/0.grb b/test/assets/gerberx3/tokens/math/parens/0.grb new file mode 100644 index 00000000..712b89b2 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/parens/0.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=($1+100)x1.75* +% diff --git a/test/assets/gerberx3/tokens/math/parens/1.grb b/test/assets/gerberx3/tokens/math/parens/1.grb new file mode 100644 index 00000000..0fd53bc4 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/parens/1.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=(100+$1)x1.75* +% diff --git a/test/assets/gerberx3/tokens/math/parens/2.grb b/test/assets/gerberx3/tokens/math/parens/2.grb new file mode 100644 index 00000000..773fe0f0 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/parens/2.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=(100+$1)x(1.75+2.0)* +% diff --git a/test/assets/gerberx3/tokens/math/parens/3.grb b/test/assets/gerberx3/tokens/math/parens/3.grb new file mode 100644 index 00000000..128d27c3 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/parens/3.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=((100+$1)+1.75)x2.0* +% diff --git a/test/assets/gerberx3/tokens/math/parens/4.grb b/test/assets/gerberx3/tokens/math/parens/4.grb new file mode 100644 index 00000000..87956d9a --- /dev/null +++ b/test/assets/gerberx3/tokens/math/parens/4.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100+($1+(1.75x2.0))* +% diff --git a/test/assets/gerberx3/tokens/math/unary/neg_const.grb b/test/assets/gerberx3/tokens/math/unary/neg_const.grb new file mode 100644 index 00000000..9a446fc0 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/unary/neg_const.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$1=-0.75* +% diff --git a/test/assets/gerberx3/tokens/math/unary/neg_var.grb b/test/assets/gerberx3/tokens/math/unary/neg_var.grb new file mode 100644 index 00000000..6c876143 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/unary/neg_var.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=-$1* +% diff --git a/test/assets/gerberx3/tokens/math/unary/pos_const.grb b/test/assets/gerberx3/tokens/math/unary/pos_const.grb new file mode 100644 index 00000000..cc717f79 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/unary/pos_const.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$1=+0.75* +% diff --git a/test/assets/gerberx3/tokens/math/unary/pos_var.grb b/test/assets/gerberx3/tokens/math/unary/pos_var.grb new file mode 100644 index 00000000..c512679b --- /dev/null +++ b/test/assets/gerberx3/tokens/math/unary/pos_var.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=+$1* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_0/0.grb b/test/assets/gerberx3/tokens/primitives/code_0/0.grb new file mode 100644 index 00000000..522c2e56 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_0/0.grb @@ -0,0 +1,9 @@ +%AMBox* +0 Rectangle with rounded corners, with rotation* +0 The origin of the aperture is its center* +0 $1 X-size* +0 $2 Y-size* +0 $3 Rounding radius* +0 $4 Rotation angle, in degrees counterclockwise* +0 Add two overlapping rectangle primitives as box body* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_1/0+variables.grb new file mode 100644 index 00000000..6ed0e7fe --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/0+variables.grb @@ -0,0 +1 @@ +%AMDonut*1,$1,$2,$3,$4*% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/0.grb b/test/assets/gerberx3/tokens/primitives/code_1/0.grb new file mode 100644 index 00000000..a47e1083 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/0.grb @@ -0,0 +1 @@ +%AMDonut*1,1,0.3,0,0.0*% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/1+variables.grb b/test/assets/gerberx3/tokens/primitives/code_1/1+variables.grb new file mode 100644 index 00000000..844601b3 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/1+variables.grb @@ -0,0 +1,3 @@ +%AMDonut* +1,$1,$2,$3,$4* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/1.grb b/test/assets/gerberx3/tokens/primitives/code_1/1.grb new file mode 100644 index 00000000..e5e622de --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/1.grb @@ -0,0 +1,3 @@ +%AMDonut* +1,1,0.3,0,0.0* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_1/2+math.grb new file mode 100644 index 00000000..0330b531 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/2+math.grb @@ -0,0 +1,7 @@ +%AMDonut* +1, +((10+$1)+1.75)x2.0, +((10+$2)+1.75)x2.0, +((10+$3)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/2+variables.grb b/test/assets/gerberx3/tokens/primitives/code_1/2+variables.grb new file mode 100644 index 00000000..c2f5dc95 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/2+variables.grb @@ -0,0 +1,7 @@ +%AMDonut* +1, +$1, +$2, +$3, +$4 +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/2.grb b/test/assets/gerberx3/tokens/primitives/code_1/2.grb new file mode 100644 index 00000000..2832ec2d --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/2.grb @@ -0,0 +1,7 @@ +%AMDonut* +1, +1, +0.3, +0, +0.0 +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_2/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_2/0+variables.grb new file mode 100644 index 00000000..036d3775 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_2/0+variables.grb @@ -0,0 +1,3 @@ +%AMVectorLine* +2,$1,$2,$3,$4,$5,$6,$7* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_2/0.grb b/test/assets/gerberx3/tokens/primitives/code_2/0.grb new file mode 100644 index 00000000..44bbe74b --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_2/0.grb @@ -0,0 +1,3 @@ +%AMVectorLine* +2,1,0.9,0,0.45,12,0.45,0* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_2/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_2/2+math.grb new file mode 100644 index 00000000..cae91145 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_2/2+math.grb @@ -0,0 +1,10 @@ +%AMVectorLine* +2, +((10+$1)+1.75)x2.0, +((10+$2)+1.75)x2.0, +((10+$3)+1.75)x2.0, +((10+$4)+1.75)x2.0, +((10+$5)+1.75)x2.0, +((10+$6)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_20/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_20/0+variables.grb new file mode 100644 index 00000000..7e83b52f --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_20/0+variables.grb @@ -0,0 +1,3 @@ +%AMLine* +20,$1,$2,$3,$4,$5,$6,$7* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_20/0.grb b/test/assets/gerberx3/tokens/primitives/code_20/0.grb new file mode 100644 index 00000000..794a1100 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_20/0.grb @@ -0,0 +1,3 @@ +%AMLine* +20,1,0.9,0,0.45,12,0.45,0* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_20/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_20/2+math.grb new file mode 100644 index 00000000..f115e9e0 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_20/2+math.grb @@ -0,0 +1,10 @@ +%AMLine* +20, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_21/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_21/0+variables.grb new file mode 100644 index 00000000..8196a2da --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_21/0+variables.grb @@ -0,0 +1,3 @@ +%AMRECTANGLE* +21,$1,$2,$3,$4,$5,$6* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_21/0.grb b/test/assets/gerberx3/tokens/primitives/code_21/0.grb new file mode 100644 index 00000000..49f762cc --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_21/0.grb @@ -0,0 +1,3 @@ +%AMRECTANGLE* +21,1,6.8,1.2,3.4,0.6,30* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_21/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_21/2+math.grb new file mode 100644 index 00000000..555b5874 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_21/2+math.grb @@ -0,0 +1,9 @@ +%AMRECTANGLE* +21, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_22/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_22/0+variables.grb new file mode 100644 index 00000000..17d01aa5 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_22/0+variables.grb @@ -0,0 +1 @@ +%AMLINE2*22,$1,$2,$3,$4,$5,$6*% diff --git a/test/assets/gerberx3/tokens/primitives/code_22/0.grb b/test/assets/gerberx3/tokens/primitives/code_22/0.grb new file mode 100644 index 00000000..ec9a89d0 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_22/0.grb @@ -0,0 +1 @@ +%AMLINE2*22,1,6.8,1.2,0,0,0*% diff --git a/test/assets/gerberx3/tokens/primitives/code_22/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_22/2+math.grb new file mode 100644 index 00000000..8e7576dc --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_22/2+math.grb @@ -0,0 +1,9 @@ +%AMLINE2* +22, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_4/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_4/0+variables.grb new file mode 100644 index 00000000..b9858585 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_4/0+variables.grb @@ -0,0 +1 @@ +%AMTriangle_30*4,$1,$2,$3,$4,$5,$6,$7,$8,$9,$11,$12*% diff --git a/test/assets/gerberx3/tokens/primitives/code_4/0.grb b/test/assets/gerberx3/tokens/primitives/code_4/0.grb new file mode 100644 index 00000000..96c1ed3b --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_4/0.grb @@ -0,0 +1 @@ +%AMTriangle_30*4,1,3,1,-1,1,1,2,1,1,-1,30*% diff --git a/test/assets/gerberx3/tokens/primitives/code_4/1+variables.grb b/test/assets/gerberx3/tokens/primitives/code_4/1+variables.grb new file mode 100644 index 00000000..264970bf --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_4/1+variables.grb @@ -0,0 +1,3 @@ +%AMTriangle_30* +4,$1,$2,$3,$4,$5,$6,$7,$8,$9,$11,$12* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_4/1.grb b/test/assets/gerberx3/tokens/primitives/code_4/1.grb new file mode 100644 index 00000000..a2dd0451 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_4/1.grb @@ -0,0 +1,3 @@ +%AMTriangle_30* +4,1,3,1,-1,1,1,2,1,1,-1,30* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_4/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_4/2+math.grb new file mode 100644 index 00000000..3c6b7cd5 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_4/2+math.grb @@ -0,0 +1,14 @@ +%AMTriangle_30* +4, +((10+$1)+1.75)x2.0, +((10+$2)+1.75)x2.0, +((10+$3)+1.75)x2.0, +((10+$4)+1.75)x2.0, +((10+$5)+1.75)x2.0, +((10+$6)+1.75)x2.0, +((10+$7)+1.75)x2.0, +((10+$8)+1.75)x2.0, +((10+$9)+1.75)x2.0, +((10+$10)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_5/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_5/0+variables.grb new file mode 100644 index 00000000..35cb57dd --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_5/0+variables.grb @@ -0,0 +1 @@ +%AMPolygon*5,$1,$2,$3,$4,$5,$6*% diff --git a/test/assets/gerberx3/tokens/primitives/code_5/0.grb b/test/assets/gerberx3/tokens/primitives/code_5/0.grb new file mode 100644 index 00000000..5bceddff --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_5/0.grb @@ -0,0 +1 @@ +%AMPolygon*5,1,8,0,0,8,0*% diff --git a/test/assets/gerberx3/tokens/primitives/code_5/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_5/2+math.grb new file mode 100644 index 00000000..c0a10155 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_5/2+math.grb @@ -0,0 +1,9 @@ +%AMPolygon* +5, +((10+$1)+1.75)x2.0, +((10+$2)+1.75)x2.0, +((10+$3)+1.75)x2.0, +((10+$4)+1.75)x2.0, +((10+$5)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_6/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_6/0+variables.grb new file mode 100644 index 00000000..3969e486 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_6/0+variables.grb @@ -0,0 +1 @@ +%AMMOIRE*6,$1,$2,$3,$4,$5,$6,$7,$8,$9*% diff --git a/test/assets/gerberx3/tokens/primitives/code_6/0.grb b/test/assets/gerberx3/tokens/primitives/code_6/0.grb new file mode 100644 index 00000000..283fec55 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_6/0.grb @@ -0,0 +1 @@ +%AMMOIRE*6,0,0,5,0.5,0.5,2,0.1,6,0*% diff --git a/test/assets/gerberx3/tokens/primitives/code_6/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_6/2+math.grb new file mode 100644 index 00000000..5e326c52 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_6/2+math.grb @@ -0,0 +1,12 @@ +%AMMOIRE* +6, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_7/0+variables.grb b/test/assets/gerberx3/tokens/primitives/code_7/0+variables.grb new file mode 100644 index 00000000..833b3764 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_7/0+variables.grb @@ -0,0 +1 @@ +%AMTHERMAL80*7,$1,$2,$3,$4,$5,$6*% diff --git a/test/assets/gerberx3/tokens/primitives/code_7/0.grb b/test/assets/gerberx3/tokens/primitives/code_7/0.grb new file mode 100644 index 00000000..741fc7a6 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_7/0.grb @@ -0,0 +1 @@ +%AMTHERMAL80*7,0,0,0.800,0.550,0.125,45*% diff --git a/test/assets/gerberx3/tokens/primitives/code_7/1+variables.grb b/test/assets/gerberx3/tokens/primitives/code_7/1+variables.grb new file mode 100644 index 00000000..2521c1ac --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_7/1+variables.grb @@ -0,0 +1,3 @@ +%AMTHERMAL80* +7,$1,$2,$3,$4,$5,$6* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_7/1.grb b/test/assets/gerberx3/tokens/primitives/code_7/1.grb new file mode 100644 index 00000000..d617e111 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_7/1.grb @@ -0,0 +1,3 @@ +%AMTHERMAL80* +7,0,0,0.800,0.550,0.125,45* +% diff --git a/test/assets/gerberx3/tokens/primitives/code_7/2+math.grb b/test/assets/gerberx3/tokens/primitives/code_7/2+math.grb new file mode 100644 index 00000000..0335df95 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_7/2+math.grb @@ -0,0 +1,9 @@ +%AMTHERMAL80* +7, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +((10+$1)+1.75)x2.0, +100+($1+(1.75x2.0)) +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_7/2+variables.grb b/test/assets/gerberx3/tokens/primitives/code_7/2+variables.grb new file mode 100644 index 00000000..c30d7bbc --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_7/2+variables.grb @@ -0,0 +1,9 @@ +%AMTHERMAL80* +7, +$1, +$2, +$3, +$4, +$5, +$6 +*% diff --git a/test/assets/gerberx3/tokens/primitives/code_7/2.grb b/test/assets/gerberx3/tokens/primitives/code_7/2.grb new file mode 100644 index 00000000..d6944e2c --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_7/2.grb @@ -0,0 +1,9 @@ +%AMTHERMAL80* +7, +0, +0, +0.800, +0.550, +0.125, +45 +*% diff --git a/test/assets/gerberx3/tokens/fs/FSLAX26Y26.grb b/test/assets/gerberx3/tokens/properties/FSLAX26Y26.grb similarity index 100% rename from test/assets/gerberx3/tokens/fs/FSLAX26Y26.grb rename to test/assets/gerberx3/tokens/properties/FSLAX26Y26.grb diff --git a/test/assets/gerberx3/tokens/properties/FSLAX34Y43.grb b/test/assets/gerberx3/tokens/properties/FSLAX34Y43.grb new file mode 100644 index 00000000..1619148b --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/FSLAX34Y43.grb @@ -0,0 +1 @@ +%FSLAX34Y43*% diff --git a/test/assets/gerberx3/tokens/fs/FSLAX66Y66.grb b/test/assets/gerberx3/tokens/properties/FSLAX66Y66.grb similarity index 100% rename from test/assets/gerberx3/tokens/fs/FSLAX66Y66.grb rename to test/assets/gerberx3/tokens/properties/FSLAX66Y66.grb diff --git a/test/assets/gerberx3/tokens/fs/FSLIX26Y26.grb b/test/assets/gerberx3/tokens/properties/FSLIX26Y26.grb similarity index 100% rename from test/assets/gerberx3/tokens/fs/FSLIX26Y26.grb rename to test/assets/gerberx3/tokens/properties/FSLIX26Y26.grb diff --git a/test/assets/gerberx3/tokens/fs/FSTIX26Y27.grb b/test/assets/gerberx3/tokens/properties/FSTIX26Y27.grb similarity index 100% rename from test/assets/gerberx3/tokens/fs/FSTIX26Y27.grb rename to test/assets/gerberx3/tokens/properties/FSTIX26Y27.grb diff --git a/test/assets/gerberx3/tokens/set_codes/as.grb b/test/assets/gerberx3/tokens/properties/as.grb similarity index 100% rename from test/assets/gerberx3/tokens/set_codes/as.grb rename to test/assets/gerberx3/tokens/properties/as.grb diff --git a/test/assets/gerberx3/tokens/properties/in.grb b/test/assets/gerberx3/tokens/properties/in.grb new file mode 100644 index 00000000..f38ba51b --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/in.grb @@ -0,0 +1 @@ +%INPANEL_1*% diff --git a/test/assets/gerberx3/tokens/properties/ip.grb b/test/assets/gerberx3/tokens/properties/ip.grb new file mode 100644 index 00000000..71e69c24 --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/ip.grb @@ -0,0 +1,2 @@ +%IPPOS*% +%IPNEG*% diff --git a/test/assets/gerberx3/tokens/properties/ir.grb b/test/assets/gerberx3/tokens/properties/ir.grb new file mode 100644 index 00000000..78ba5140 --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/ir.grb @@ -0,0 +1,4 @@ +%IR0*% +%IR90*% +%IR180*% +%IR270*% diff --git a/test/assets/gerberx3/tokens/properties/mi.grb b/test/assets/gerberx3/tokens/properties/mi.grb new file mode 100644 index 00000000..1f844310 --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/mi.grb @@ -0,0 +1,6 @@ +%MIA0B0*% +%MIA0*% +%MIB0*% +%MIA0B1*% +%MIB1*% +%MIA1*% diff --git a/test/assets/gerberx3/tokens/set_codes/mo.grb b/test/assets/gerberx3/tokens/properties/mo.grb similarity index 100% rename from test/assets/gerberx3/tokens/set_codes/mo.grb rename to test/assets/gerberx3/tokens/properties/mo.grb diff --git a/test/assets/gerberx3/tokens/properties/of.grb b/test/assets/gerberx3/tokens/properties/of.grb new file mode 100644 index 00000000..768b6d91 --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/of.grb @@ -0,0 +1,3 @@ +%OFA0B0*% +%OFA1.0B-1.5*% +%OFB5.0*% diff --git a/test/assets/gerberx3/tokens/properties/sf.grb b/test/assets/gerberx3/tokens/properties/sf.grb new file mode 100644 index 00000000..210c2e9d --- /dev/null +++ b/test/assets/gerberx3/tokens/properties/sf.grb @@ -0,0 +1,2 @@ +%SFA1B1*% +%SFA.5B3*% From 33bd9cffc3768ddb6ec2a62ff5d3d8dfbb61ba1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 22 Jul 2024 22:02:58 +0200 Subject: [PATCH 07/91] Change AST root from list to File class --- src/pygerber/gerberx3/ast/nodes/base.py | 3 ++ src/pygerber/gerberx3/ast/nodes/file.py | 20 +++++++++++++ .../gerberx3/ast/nodes/properties/AS.py | 18 ++++++++++++ .../gerberx3/ast/nodes/properties/IR.py | 18 ++++++++++++ .../gerberx3/ast/nodes/properties/MI.py | 18 ++++++++++++ .../gerberx3/ast/nodes/properties/SF.py | 18 ++++++++++++ src/pygerber/gerberx3/ast/visitor.py | 24 +++++++++++++++ .../gerberx3/parser/pyparsing/grammar.py | 29 +++++++++++++++---- .../gerberx3/parser/pyparsing/parser.py | 10 ++++--- test/benchmark/a64_olinuxino_rev_g_b_cu.py | 27 ----------------- 10 files changed, 148 insertions(+), 37 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/file.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/AS.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/IR.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/MI.py create mode 100644 src/pygerber/gerberx3/ast/nodes/properties/SF.py delete mode 100644 test/benchmark/a64_olinuxino_rev_g_b_cu.py diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py index 4e34b912..38f865a8 100644 --- a/src/pygerber/gerberx3/ast/nodes/base.py +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -14,6 +14,9 @@ class Node(ModelType): """Base class for all nodes.""" + source: str + location: int + @abstractmethod def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/file.py b/src/pygerber/gerberx3/ast/nodes/file.py new file mode 100644 index 00000000..67dcd112 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/file.py @@ -0,0 +1,20 @@ +"""`pygerber.gerberx3.ast.nodes.file` module contains definition of `File` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class File(Node): + """AST node representing Gerber file.""" + + commands: list[Node] + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_file(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/AS.py b/src/pygerber/gerberx3/ast/nodes/properties/AS.py new file mode 100644 index 00000000..b479dfae --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/AS.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.AS` module contains definition of `AS` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class AS(Node): + """Represents AS Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_as(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IR.py b/src/pygerber/gerberx3/ast/nodes/properties/IR.py new file mode 100644 index 00000000..2ae54353 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/IR.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.IR` module contains definition of `IR` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class IR(Node): + """Represents IR Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ir(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MI.py b/src/pygerber/gerberx3/ast/nodes/properties/MI.py new file mode 100644 index 00000000..fa837bef --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/MI.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.MI` module contains definition of `MI` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class MI(Node): + """Represents MI Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_mi(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/SF.py b/src/pygerber/gerberx3/ast/nodes/properties/SF.py new file mode 100644 index 00000000..3125d816 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/properties/SF.py @@ -0,0 +1,18 @@ +"""`pygerber.nodes.properties.SF` module contains definition of `SF` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class SF(Node): + """Represents SF Gerber extended command.""" + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_sf(self) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 4aa3712e..cbebcd9a 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -20,6 +20,7 @@ from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn + from pygerber.gerberx3.ast.nodes.file import File from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 @@ -70,11 +71,15 @@ from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 + from pygerber.gerberx3.ast.nodes.properties.AS import AS from pygerber.gerberx3.ast.nodes.properties.FS import FS from pygerber.gerberx3.ast.nodes.properties.IN import IN from pygerber.gerberx3.ast.nodes.properties.IP import IP + from pygerber.gerberx3.ast.nodes.properties.IR import IR + from pygerber.gerberx3.ast.nodes.properties.MI import MI from pygerber.gerberx3.ast.nodes.properties.MO import MO from pygerber.gerberx3.ast.nodes.properties.OF import OF + from pygerber.gerberx3.ast.nodes.properties.SF import SF class AstVisitor: @@ -292,6 +297,9 @@ def on_code_22(self, node: Code22) -> None: # Properties + def on_as(self, node: AS) -> None: + """Handle `AS` node.""" + def on_fs(self, node: FS) -> None: """Handle `FS` node.""" @@ -301,8 +309,24 @@ def on_in(self, node: IN) -> None: def on_ip(self, node: IP) -> None: """Handle `IP` node.""" + def on_ir(self, node: IR) -> None: + """Handle `IR` node.""" + + def on_mi(self, node: MI) -> None: + """Handle `MI` node.""" + def on_mo(self, node: MO) -> None: """Handle `MO` node.""" def on_of(self, node: OF) -> None: """Handle `OF` node.""" + + def on_sf(self, node: SF) -> None: + """Handle `SF` node.""" + + # Root node + + def on_file(self, node: File) -> None: + """Handle `File` node.""" + for command in node.commands: + command.visit(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 6a9d4bd6..6a5264ae 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -14,6 +14,7 @@ from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn +from pygerber.gerberx3.ast.nodes.file import File from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 @@ -37,13 +38,13 @@ def g(value: int, cls: Type[Node]) -> pp.ParserElement: """Create a parser element capable of parsing particular G-code.""" element = pp.Regex(r"G0*" + str(value)).set_name(f"G{value}") - return element.setParseAction(lambda: cls()) + return element.setParseAction(lambda s, loc, _tokens: cls(source=s, location=loc)) def m(value: int, cls: Type[Node]) -> pp.ParserElement: """Create a parser element capable of parsing particular D-code.""" element = pp.Regex(r"M0*" + str(value)).set_name(f"M{value}") - return element.setParseAction(lambda: cls()) + return element.setParseAction(lambda s, loc, _tokens: cls(source=s, location=loc)) T = TypeVar("T", bound=Node) @@ -59,7 +60,15 @@ def __init__(self, ast_node_class_overrides: dict[str, Type[Node]]) -> None: def build(self) -> pp.ParserElement: """Build the grammar.""" - return pp.OneOrMore(self.g_codes() | self.m_codes() | self.d_codes()) + + def _(s: str, loc: int, tokens: pp.ParseResults) -> File: + return self.get_cls(File)(source=s, location=loc, commands=tokens.as_list()) + + return ( + pp.OneOrMore(self.g_codes() | self.m_codes() | self.d_codes()) + .set_results_name("root_node") + .set_parse_action(_) + ) def d_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing D-codes.""" @@ -69,6 +78,8 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> D01: token_list = cast(list[Coordinate], parse_result_token_list) try: return self.get_cls(D01)( + source=s, + location=loc, x=token_list[0], y=token_list[1], i=token_list[2] if len(token_list) >= 4 else None, # noqa: PLR2004 @@ -94,6 +105,8 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> D02: token_list = cast(list[Coordinate], parse_result_token_list) try: return self.get_cls(D02)( + source=s, + location=loc, x=token_list[0], y=token_list[1], ) @@ -111,6 +124,8 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> D03: token_list = cast(list[Coordinate], parse_result_token_list) try: return self.get_cls(D03)( + source=s, + location=loc, x=token_list[0], y=token_list[1], ) @@ -132,7 +147,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Dnn: assert len(value) > 1 try: - return self.get_cls(Dnn)(value=value) + return self.get_cls(Dnn)(source=s, location=loc, value=value) except ValidationError as e: raise pp.ParseFatalException(s, loc, "Invalid Dnn") from e @@ -148,13 +163,15 @@ def get_cls(self, node_cls: Type[T]) -> Type[T]: def coordinate(self) -> pp.ParserElement: """Create a parser element capable of parsing coordinates.""" - def _(tokens: pp.ParseResults) -> Coordinate: + def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: type_, value = tokens.as_list() assert isinstance(type_, str), type(type_) assert type_ in ("X", "Y", "I", "J") assert isinstance(value, str) - return self.get_cls(Coordinate)(type=type_, value=value) + return self.get_cls(Coordinate)( + source=s, location=loc, type=type_, value=value + ) return ( ( diff --git a/src/pygerber/gerberx3/parser/pyparsing/parser.py b/src/pygerber/gerberx3/parser/pyparsing/parser.py index 762f26c8..431c3b63 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/parser.py +++ b/src/pygerber/gerberx3/parser/pyparsing/parser.py @@ -4,9 +4,10 @@ from __future__ import annotations -from typing import Optional, Type, cast +from typing import Optional, Type from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.file import File from pygerber.gerberx3.parser.pyparsing.grammar import Grammar @@ -21,7 +22,8 @@ def __init__( else: self.grammar = Grammar.DEFAULT - def parse(self, code: str) -> list[Node]: + def parse(self, code: str) -> File: """Parse the input.""" - parse_result = self.grammar.parseString(code) - return cast(list[Node], parse_result.as_list()) + parse_result = self.grammar.parseString(code).get("root_node") + assert isinstance(parse_result, File) + return parse_result diff --git a/test/benchmark/a64_olinuxino_rev_g_b_cu.py b/test/benchmark/a64_olinuxino_rev_g_b_cu.py deleted file mode 100644 index 0ae1be72..00000000 --- a/test/benchmark/a64_olinuxino_rev_g_b_cu.py +++ /dev/null @@ -1,27 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from pygerber.gerberx3.api import ( - ColorScheme, - Rasterized2DLayer, - Rasterized2DLayerParams, -) - - -def render() -> None: - source_path = ( - Path.cwd() - / "test/assets/gerberx3/A64-OLinuXino-rev-G/A64-OlinuXino_Rev_G-B_Cu.gbr" - ) - - Rasterized2DLayer( - options=Rasterized2DLayerParams( - source_path=source_path, - colors=ColorScheme.COPPER_ALPHA, - ), - ).render().save("output.png") - - -if __name__ == "__main__": - render() From 09e283f57fd43da6c9a506d0b93fa9e6fb54adc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 23 Jul 2024 23:11:17 +0200 Subject: [PATCH 08/91] Add test assets for testing primitive 1 with rotation --- test/assets/gerberx3/tokens/primitives/code_1/3+variables.grb | 1 + test/assets/gerberx3/tokens/primitives/code_1/3.grb | 1 + 2 files changed, 2 insertions(+) create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/3+variables.grb create mode 100644 test/assets/gerberx3/tokens/primitives/code_1/3.grb diff --git a/test/assets/gerberx3/tokens/primitives/code_1/3+variables.grb b/test/assets/gerberx3/tokens/primitives/code_1/3+variables.grb new file mode 100644 index 00000000..57e0b0f0 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/3+variables.grb @@ -0,0 +1 @@ +%AMDonut*1,$1,$2,$3,$4,$5*% diff --git a/test/assets/gerberx3/tokens/primitives/code_1/3.grb b/test/assets/gerberx3/tokens/primitives/code_1/3.grb new file mode 100644 index 00000000..3f256317 --- /dev/null +++ b/test/assets/gerberx3/tokens/primitives/code_1/3.grb @@ -0,0 +1 @@ +%AMDonut*1,1,0.3,0,0.0,90.0*% From ef6aaf8cead1799e8f5ad327026df18b40ed0379 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 23 Jul 2024 23:23:36 +0200 Subject: [PATCH 09/91] Add macro definition parsing --- .../gerberx3/ast/nodes/aperture/AM_open.py | 2 + src/pygerber/gerberx3/ast/nodes/base.py | 8 +- .../gerberx3/ast/nodes/g_codes/G04.py | 4 + .../gerberx3/ast/nodes/math/assignment.py | 7 +- .../gerberx3/ast/nodes/math/constant.py | 8 +- .../gerberx3/ast/nodes/math/expression.py | 2 +- .../ast/nodes/math/operators/binary/add.py | 10 +- .../ast/nodes/math/operators/binary/div.py | 10 +- .../ast/nodes/math/operators/binary/mul.py | 10 +- .../ast/nodes/math/operators/binary/sub.py | 10 +- .../ast/nodes/math/operators/unary/neg.py | 6 +- .../ast/nodes/math/operators/unary/pos.py | 6 +- src/pygerber/gerberx3/ast/nodes/math/point.py | 24 + .../gerberx3/ast/nodes/math/variable.py | 8 +- src/pygerber/gerberx3/ast/nodes/model.py | 6 +- .../gerberx3/ast/nodes/primitives/code_0.py | 2 + src/pygerber/gerberx3/ast/visitor.py | 4 + .../gerberx3/parser/pyparsing/grammar.py | 515 ++++++++++++++++-- src/pygerber/vm/types/model.py | 4 +- 19 files changed, 574 insertions(+), 72 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/math/point.py diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py index cc32ab67..ff31a834 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py @@ -13,6 +13,8 @@ class AMopen(Node): """Represents AM Gerber extended command.""" + name: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_am_open(self) diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py index 38f865a8..25ec51f9 100644 --- a/src/pygerber/gerberx3/ast/nodes/base.py +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -5,7 +5,9 @@ from abc import abstractmethod from typing import TYPE_CHECKING -from pygerber.vm.types.model import ModelType +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.model import ModelType if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -14,8 +16,8 @@ class Node(ModelType): """Base class for all nodes.""" - source: str - location: int + source: str = Field(repr=False) + location: int = Field(repr=False) @abstractmethod def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py index 2c7e5a78..7e5c45c8 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING +from pydantic import Field + from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: @@ -13,6 +15,8 @@ class G04(Node): """Represents G04 Gerber command.""" + content: str = Field(default="") + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g04(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/assignment.py b/src/pygerber/gerberx3/ast/nodes/math/assignment.py index 26224b36..058f6881 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/assignment.py +++ b/src/pygerber/gerberx3/ast/nodes/math/assignment.py @@ -1,10 +1,12 @@ -"""`pygerber.nodes.math.Variable` module contains definition of `Variable` class.""" +"""`pygerber.nodes.math.assignment` module contains definition of `Assignment` class.""" from __future__ import annotations from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression +from pygerber.gerberx3.ast.nodes.math.variable import Variable if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +15,9 @@ class Assignment(Node): """Represents math expression variable.""" + variable: Variable + expression: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_assignment(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/constant.py b/src/pygerber/gerberx3/ast/nodes/math/constant.py index 390fbfdb..2d91f090 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/constant.py +++ b/src/pygerber/gerberx3/ast/nodes/math/constant.py @@ -1,18 +1,20 @@ -"""`pygerber.nodes.math.Constant` module contains definition of `Constant` class.""" +"""`pygerber.nodes.math.constant` module contains definition of `Constant` class.""" from __future__ import annotations from typing import TYPE_CHECKING -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Constant(Node): +class Constant(Expression): """Represents math expression constant.""" + constant: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_constant(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/expression.py b/src/pygerber/gerberx3/ast/nodes/math/expression.py index 69779861..0fdc429e 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/expression.py +++ b/src/pygerber/gerberx3/ast/nodes/math/expression.py @@ -1,4 +1,4 @@ -"""`pygerber.nodes.math.Expression` module contains definition of `Expression` +"""`pygerber.nodes.math.expression` module contains definition of `Expression` class. """ diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py index 5db06add..fb4b4316 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py @@ -4,17 +4,21 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List -from pygerber.gerberx3.ast.nodes.base import Node +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Add(Node): +class Add(Expression): """Represents math expression addition operator.""" + operands: List[Expression] = Field(min_length=2) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_add(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py index 351395d4..fafa2567 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py @@ -4,17 +4,21 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List -from pygerber.gerberx3.ast.nodes.base import Node +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Div(Node): +class Div(Expression): """Represents math expression division operator.""" + operands: List[Expression] = Field(min_length=2) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_div(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py index 154200ac..ff785e4f 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py @@ -4,17 +4,21 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List -from pygerber.gerberx3.ast.nodes.base import Node +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Mul(Node): +class Mul(Expression): """Represents math expression multiplication operator.""" + operands: List[Expression] = Field(min_length=2) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_mul(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py index 33e3a38c..ca2625f3 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py @@ -4,17 +4,21 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List -from pygerber.gerberx3.ast.nodes.base import Node +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Sub(Node): +class Sub(Expression): """Represents math expression subtraction operator.""" + operands: List[Expression] = Field(min_length=2) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_sub(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py index 354675c8..c4a8b036 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py @@ -6,15 +6,17 @@ from typing import TYPE_CHECKING -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Neg(Node): +class Neg(Expression): """Represents math expression neg.""" + operand: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_neg(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py index 45e21d4d..c2938a06 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py @@ -6,15 +6,17 @@ from typing import TYPE_CHECKING -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Pos(Node): +class Pos(Expression): """Represents math expression pos.""" + operand: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_pos(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/point.py b/src/pygerber/gerberx3/ast/nodes/math/point.py new file mode 100644 index 00000000..55214ee2 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/point.py @@ -0,0 +1,24 @@ +"""`pygerber.nodes.math.point` module contains definition of `Point` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Point(Node): + """Represents math point point.""" + + x: Expression + y: Expression + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_point(self) diff --git a/src/pygerber/gerberx3/ast/nodes/math/variable.py b/src/pygerber/gerberx3/ast/nodes/math/variable.py index 1b3ebd6c..7965799a 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/variable.py +++ b/src/pygerber/gerberx3/ast/nodes/math/variable.py @@ -1,18 +1,20 @@ -"""`pygerber.nodes.math.Variable` module contains definition of `Variable` class.""" +"""`pygerber.nodes.math.variable` module contains definition of `Variable` class.""" from __future__ import annotations from typing import TYPE_CHECKING -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor -class Variable(Node): +class Variable(Expression): """Represents math expression variable.""" + variable: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_variable(self) diff --git a/src/pygerber/gerberx3/ast/nodes/model.py b/src/pygerber/gerberx3/ast/nodes/model.py index fffd8899..51e671ac 100644 --- a/src/pygerber/gerberx3/ast/nodes/model.py +++ b/src/pygerber/gerberx3/ast/nodes/model.py @@ -8,15 +8,15 @@ class ModelType(BaseModel): - """Common base class for all node model types.""" + """Common base class for all VM model types.""" model_config = ConfigDict( - extra="forbid", + extra="ignore", frozen=True, arbitrary_types_allowed=True, ) - @computed_field # type: ignore[misc] + @computed_field(repr=False) # type: ignore[misc] @property def __class_qualname__(self) -> str: """Name of class.""" diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py index f9819121..9f6031f1 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py @@ -13,6 +13,8 @@ class Code0(Node): """Represents code 0 macro primitive.""" + string: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_0(self) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index cbebcd9a..5240410f 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -52,6 +52,7 @@ from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos + from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable from pygerber.gerberx3.ast.nodes.other.command_end import CommandEnd from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate @@ -246,6 +247,9 @@ def on_constant(self, node: Constant) -> None: def on_expression(self, node: Expression) -> None: """Handle `Expression` node.""" + def on_point(self, node: Point) -> None: + """Handle `Point` node.""" + def on_variable(self, node: Variable) -> None: """Handle `Variable` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 6a5264ae..fa6f299a 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import ClassVar, Type, TypeVar, cast +from typing import ClassVar, List, Type, TypeVar, cast import pyparsing as pp from pydantic import ValidationError +from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose +from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 @@ -32,20 +34,33 @@ from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 +from pygerber.gerberx3.ast.nodes.math.assignment import Assignment +from pygerber.gerberx3.ast.nodes.math.constant import Constant +from pygerber.gerberx3.ast.nodes.math.expression import Expression +from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add +from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div +from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul +from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub +from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg +from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos +from pygerber.gerberx3.ast.nodes.math.point import Point +from pygerber.gerberx3.ast.nodes.math.variable import Variable +from pygerber.gerberx3.ast.nodes.other.command_end import CommandEnd from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate - - -def g(value: int, cls: Type[Node]) -> pp.ParserElement: - """Create a parser element capable of parsing particular G-code.""" - element = pp.Regex(r"G0*" + str(value)).set_name(f"G{value}") - return element.setParseAction(lambda s, loc, _tokens: cls(source=s, location=loc)) - - -def m(value: int, cls: Type[Node]) -> pp.ParserElement: - """Create a parser element capable of parsing particular D-code.""" - element = pp.Regex(r"M0*" + str(value)).set_name(f"M{value}") - return element.setParseAction(lambda s, loc, _tokens: cls(source=s, location=loc)) - +from pygerber.gerberx3.ast.nodes.other.extended_command_close import ( + ExtendedCommandClose, +) +from pygerber.gerberx3.ast.nodes.other.extended_command_open import ExtendedCommandOpen +from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 +from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 +from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 +from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 +from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 +from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 +from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 +from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 +from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 +from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 T = TypeVar("T", bound=Node) @@ -55,8 +70,14 @@ class Grammar: DEFAULT: ClassVar[pp.ParserElement] - def __init__(self, ast_node_class_overrides: dict[str, Type[Node]]) -> None: + def __init__( + self, + ast_node_class_overrides: dict[str, Type[Node]], + *, + enable_packrat: bool = True, + ) -> None: self.ast_node_class_overrides = ast_node_class_overrides + self.enable_packrat = enable_packrat def build(self) -> pp.ParserElement: """Build the grammar.""" @@ -64,11 +85,24 @@ def build(self) -> pp.ParserElement: def _(s: str, loc: int, tokens: pp.ParseResults) -> File: return self.get_cls(File)(source=s, location=loc, commands=tokens.as_list()) - return ( - pp.OneOrMore(self.g_codes() | self.m_codes() | self.d_codes()) + root = ( + pp.OneOrMore( + pp.MatchFirst( + [ + self.g_codes(), + self.m_codes(), + self.d_codes(), + self.macro(), + self.command_end, + ] + ) + ) .set_results_name("root_node") .set_parse_action(_) + .set_debug() ) + root.enable_packrat() + return root def d_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing D-codes.""" @@ -142,8 +176,8 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Dnn: parse_result_token_list = tokens.as_list() token_list = cast(list[Coordinate], parse_result_token_list) value = token_list[0] - assert isinstance(value, str) - assert value.startswith("D") + assert isinstance(value, str) # type: ignore[unreachable] + assert value.startswith("D") # type: ignore[unreachable] assert len(value) > 1 try: @@ -153,11 +187,11 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Dnn: dnn = pp.Regex(r"D0*[0-9]*").set_parse_action(_).set_name("Dnn") - return d01 | d02 | d03 | dnn + return pp.MatchFirst([d01, d02, d03, dnn]) def get_cls(self, node_cls: Type[T]) -> Type[T]: """Get the class of the node.""" - return self.ast_node_class_overrides.get(node_cls.__qualname__, node_cls) + return self.ast_node_class_overrides.get(node_cls.__qualname__, node_cls) # type: ignore[return-value] @pp.cached_property def coordinate(self) -> pp.ParserElement: @@ -170,7 +204,10 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: assert isinstance(value, str) return self.get_cls(Coordinate)( - source=s, location=loc, type=type_, value=value + source=s, + location=loc, + type=type_, # type: ignore[arg-type] + value=value, ) return ( @@ -182,35 +219,77 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: .set_name("coordinate") ) + @pp.cached_property + def command_end(self) -> pp.ParserElement: + """Create a parser element capable of parsing the command end.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> CommandEnd: + return self.get_cls(CommandEnd)(source=s, location=loc) + + return pp.Literal("*").set_name("*").set_parse_action(_) + def g_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing G-codes.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> G04: + content = tokens.as_dict().get("string") + assert isinstance(content, str) + return self.get_cls(G04)(source=s, location=loc, content=content) + + g04_comment = ( + (pp.Regex(r"G0*4") + self.string).set_name("G04").set_parse_action(_) + ) + return pp.MatchFirst( [ - g(value, self.get_cls(cls)) - for (value, cls) in ( - (1, G01), - (2, G02), - (3, G03), - (4, G04), - (36, G36), - (37, G37), - (54, G54), - (55, G55), - (70, G70), - (71, G71), - (74, G74), - (75, G75), - (90, G90), - (91, G91), - ) + g04_comment, + *( + self.g( + value, + self.get_cls(cls), # type: ignore[arg-type] + ) + for (value, cls) in reversed( + ( + (1, G01), + (2, G02), + (3, G03), + (4, G04), + (36, G36), + (37, G37), + (54, G54), + (55, G55), + (70, G70), + (71, G71), + (74, G74), + (75, G75), + (90, G90), + (91, G91), + ) + ) + ), ] ) + @pp.cached_property + def string(self) -> pp.ParserElement: + """Create a parser element capable of parsing strings.""" + return pp.CharsNotIn("%*").set_results_name("string") + + def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: + """Create a parser element capable of parsing particular G-code.""" + element = pp.Regex(r"G0*" + str(value)).set_name(f"G{value}") + return element.setParseAction( + lambda s, loc, _tokens: self.get_cls(cls)(source=s, location=loc) + ) + def m_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing M-codes.""" return pp.MatchFirst( [ - m(value, self.get_cls(cls)) + self.m( + value, + self.get_cls(cls), # type: ignore[arg-type, type-abstract] + ) for (value, cls) in ( (0, M00), (1, M01), @@ -219,5 +298,361 @@ def m_codes(self) -> pp.ParserElement: ] ) + def m(self, value: int, cls: Type[Node]) -> pp.ParserElement: + """Create a parser element capable of parsing particular D-code.""" + element = pp.Regex(r"M0*" + str(value)).set_name(f"M{value}") + return element.setParseAction( + lambda s, loc, _tokens: cls(source=s, location=loc) + ) + + @pp.cached_property + def extended_command_open(self) -> pp.ParserElement: + """Create a parser element capable of parsing the extended command open.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(ExtendedCommandOpen)(source=s, location=loc) + + return pp.Regex(r"%").set_name("%").set_parse_action(_) + + @pp.cached_property + def extended_command_close(self) -> pp.ParserElement: + """Create a parser element capable of parsing the extended command close.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(ExtendedCommandClose)(source=s, location=loc) + + return pp.Regex(r"%").set_name("%").set_parse_action(_) + + def macro(self) -> pp.ParserElement: + """Create a parser element capable of parsing macros.""" + + def _am_open(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(AMopen)(source=s, location=loc, **tokens.as_dict()) + + def _am_close(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(AMclose)(source=s, location=loc, **tokens.as_dict()) + + cs = pp.Suppress(pp.Literal(",").set_name("comma")) + + def _point(s: str, loc: int, tokens: pp.ParseResults) -> Point: + return self.get_cls(Point)(source=s, location=loc, **tokens.as_dict()) + + return ( + self.extended_command_open + + (pp.Literal("AM") + self.name) + .set_name("AMopen") + .set_parse_action(_am_open) + + self.command_end + + pp.ZeroOrMore( + pp.MatchFirst( + [ + self.assignment, + self.primitive(Code0, 0, self.string), + self.primitive( + Code1, + 1, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("diameter") + + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + pp.Opt(cs + self.expression.set_results_name("rotation")), + ), + self.primitive( + Code2, + 2, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("start_x") + + cs + + self.expression.set_results_name("start_y") + + cs + + self.expression.set_results_name("end_x") + + cs + + self.expression.set_results_name("end_y") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code4, + 4, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("number_of_vertices") + + cs + + self.expression.set_results_name("start_x") + + cs + + self.expression.set_results_name("start_y") + + pp.OneOrMore( + ( + cs + + self.expression.set_results_name("x") + + cs + + self.expression.set_results_name("y") + ) + .set_results_name("point") + .set_parse_action(_point), + ) + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code5, + 5, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("number_of_vertices") + + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("diameter") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code6, + 6, + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("outer_diameter") + + cs + + self.expression.set_results_name("ring_thickness") + + cs + + self.expression.set_results_name("gap_between_rings") + + cs + + self.expression.set_results_name("max_ring_count") + + cs + + self.expression.set_results_name("crosshair_thickness") + + cs + + self.expression.set_results_name("crosshair_length") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code7, + 7, + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("outer_diameter") + + cs + + self.expression.set_results_name("inner_diameter") + + cs + + self.expression.set_results_name("gap_thickness") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code20, + 20, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("start_x") + + cs + + self.expression.set_results_name("start_y") + + cs + + self.expression.set_results_name("end_x") + + cs + + self.expression.set_results_name("end_y") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code21, + 21, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("height") + + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code22, + 22, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("height") + + cs + + self.expression.set_results_name("x_lower_left") + + cs + + self.expression.set_results_name("y_lower_left") + + cs + + self.expression.set_results_name("rotation"), + ), + ] + ) + ) + + (pp.Literal("").set_name("AM_close").set_parse_action(_am_close)) + + self.extended_command_close + ) + + @pp.cached_property + def name(self) -> pp.ParserElement: + """Create a parser element capable of parsing names.""" + return pp.Regex(r"[._$a-zA-Z][._$a-zA-Z0-9]{0,126}").set_results_name("name") + + def primitive( + self, cls: Type[Node], code: int, fields: pp.ParserElement + ) -> pp.ParserElement: + """Create a parser element capable of parsing a primitive.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(cls)(source=s, location=loc, **_tokens.as_dict()) + + return (pp.Literal(str(code)) + fields).set_name( + f"primitive-{code}" + ).set_parse_action(_) + self.command_end + + @pp.cached_property + def expression(self) -> pp.ParserElement: + """Create a parser element capable of parsing expressions.""" + factor = self.constant | self.variable + + def _neg(s: str, loc: int, tokens: pp.ParseResults) -> Expression: + token_list = cast(List[List[Expression]], tokens.as_list())[0] + assert len(token_list) == 1 + return self.get_cls(Neg)(source=s, location=loc, operand=token_list[0]) + + def _pos(s: str, loc: int, tokens: pp.ParseResults) -> Expression: + token_list = cast(List[List[Expression]], tokens.as_list())[0] + assert len(token_list) == 1 + return self.get_cls(Pos)(source=s, location=loc, operand=token_list[0]) + + def _sub(s: str, loc: int, tokens: pp.ParseResults) -> Expression: + nested_token_list = cast(List[List[Expression]], tokens.as_list()) + assert len(nested_token_list) == 1 + token_list = nested_token_list[0] + assert len(token_list) > 1 + return self.get_cls(Sub)(source=s, location=loc, operands=token_list) + + def _add(s: str, loc: int, tokens: pp.ParseResults) -> Expression: + nested_token_list = cast(List[List[Expression]], tokens.as_list()) + assert len(nested_token_list) == 1 + token_list = nested_token_list[0] + assert len(token_list) > 1 + return self.get_cls(Add)(source=s, location=loc, operands=token_list) + + def _div(s: str, loc: int, tokens: pp.ParseResults) -> Expression: + nested_token_list = cast(List[List[Expression]], tokens.as_list()) + assert len(nested_token_list) == 1 + token_list = nested_token_list[0] + assert len(token_list) > 1 + return self.get_cls(Div)(source=s, location=loc, operands=token_list) + + def _mul(s: str, loc: int, tokens: pp.ParseResults) -> Expression: + nested_token_list = cast(List[List[Expression]], tokens.as_list()) + assert len(nested_token_list) == 1 + token_list = nested_token_list[0] + assert len(token_list) > 1 + return self.get_cls(Mul)(source=s, location=loc, operands=token_list) + + return pp.infix_notation( + factor, + [ + ( + pp.Suppress("-"), + 1, + pp.OpAssoc.RIGHT, + _neg, + ), + ( + pp.Suppress("+"), + 1, + pp.OpAssoc.RIGHT, + _pos, + ), + ( + pp.Suppress("/"), + 2, + pp.OpAssoc.LEFT, + _div, + ), + ( + pp.Suppress(pp.oneOf("x X")), + 2, + pp.OpAssoc.LEFT, + _mul, + ), + ( + pp.Suppress("-"), + 2, + pp.OpAssoc.LEFT, + _sub, + ), + ( + pp.Suppress("+"), + 2, + pp.OpAssoc.LEFT, + _add, + ), + ], + ).set_name("expression") + + @pp.cached_property + def constant(self) -> pp.ParserElement: + """Create a parser element capable of parsing constants.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(Constant)(source=s, location=loc, **_tokens.as_dict()) + + return self.double.set_results_name("constant").set_parse_action(_) + + @pp.cached_property + def double(self) -> pp.ParserElement: + """Create a parser element capable of parsing doubles.""" + return pp.Regex(r"[+-]?(([0-9]+(\.[0-9]+)?)|(\.[0-9]+))").set_results_name( + "double" + ) + + @pp.cached_property + def variable(self) -> pp.ParserElement: + """Create a parser element capable of parsing variables.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(Variable)(source=s, location=loc, **_tokens.as_dict()) + + return pp.Regex(r"\$[0-9]+").set_results_name("variable").set_parse_action(_) + + @pp.cached_property + def assignment(self) -> pp.ParserElement: + """Create a parser element capable of parsing assignments.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(Assignment)(source=s, location=loc, **tokens.as_dict()) + + return ( + self.variable + + pp.Suppress("=") + + self.expression.set_results_name("expression") + ).set_results_name("assignment").set_parse_action(_) + self.command_end + Grammar.DEFAULT = Grammar({}).build() diff --git a/src/pygerber/vm/types/model.py b/src/pygerber/vm/types/model.py index c7acd45b..51e671ac 100644 --- a/src/pygerber/vm/types/model.py +++ b/src/pygerber/vm/types/model.py @@ -11,12 +11,12 @@ class ModelType(BaseModel): """Common base class for all VM model types.""" model_config = ConfigDict( - extra="forbid", + extra="ignore", frozen=True, arbitrary_types_allowed=True, ) - @computed_field # type: ignore[misc] + @computed_field(repr=False) # type: ignore[misc] @property def __class_qualname__(self) -> str: """Name of class.""" From c1c95bc0be46393b88c649993a757635340a0f99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 24 Jul 2024 00:29:23 +0200 Subject: [PATCH 10/91] Add fields to primitive nodes --- src/pygerber/gerberx3/ast/nodes/primitives/code_1.py | 11 ++++++++++- src/pygerber/gerberx3/ast/nodes/primitives/code_2.py | 9 +++++++++ src/pygerber/gerberx3/ast/nodes/primitives/code_20.py | 9 +++++++++ src/pygerber/gerberx3/ast/nodes/primitives/code_21.py | 8 ++++++++ src/pygerber/gerberx3/ast/nodes/primitives/code_22.py | 8 ++++++++ src/pygerber/gerberx3/ast/nodes/primitives/code_4.py | 11 ++++++++++- src/pygerber/gerberx3/ast/nodes/primitives/code_5.py | 8 ++++++++ src/pygerber/gerberx3/ast/nodes/primitives/code_6.py | 11 +++++++++++ src/pygerber/gerberx3/ast/nodes/primitives/code_7.py | 8 ++++++++ src/pygerber/gerberx3/parser/pyparsing/grammar.py | 4 ++-- 10 files changed, 83 insertions(+), 4 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py index 9fe41a42..a2cd74c4 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py @@ -2,9 +2,12 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +16,12 @@ class Code1(Node): """Represents code 1 macro primitive.""" + exposure: Expression + diameter: Expression + center_x: Expression + center_y: Expression + rotation: Optional[Expression] = Field(default=None) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_1(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py index e847412c..2670c01a 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,14 @@ class Code2(Node): """Represents code 2 macro primitive.""" + exposure: Expression + width: Expression + start_x: Expression + start_y: Expression + end_x: Expression + end_y: Expression + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_2(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py index 5174ba02..e396854d 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,14 @@ class Code20(Node): """Represents code 20 macro primitive.""" + exposure: Expression + width: Expression + start_x: Expression + start_y: Expression + end_x: Expression + end_y: Expression + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_20(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py index 3861feff..ae7c0968 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,13 @@ class Code21(Node): """Represents code 21 macro primitive.""" + exposure: Expression + width: Expression + height: Expression + center_x: Expression + center_y: Expression + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_21(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py index a97b2cd9..5e7d095e 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,13 @@ class Code22(Node): """Represents code 22 macro primitive.""" + exposure: Expression + width: Expression + height: Expression + x_lower_left: Expression + y_lower_left: Expression + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_22(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py index 4e0dd4fe..2e2deb54 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression +from pygerber.gerberx3.ast.nodes.math.point import Point if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +15,13 @@ class Code4(Node): """Represents code 4 macro primitive.""" + exposure: Expression + number_of_points: Expression + start_x: Expression + start_y: Expression + points: List[Point] + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_4(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py index 6f0c2209..a3c9ad0f 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,13 @@ class Code5(Node): """Represents code 5 macro primitive.""" + exposure: Expression + number_of_vertices: Expression + center_x: Expression + center_y: Expression + diameter: Expression + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_5(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py index 7596b04d..b44e92db 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,16 @@ class Code6(Node): """Represents code 6 macro primitive.""" + center_x: Expression + center_y: Expression + outer_diameter: Expression + ring_thickness: Expression + gap_between_rings: Expression + max_ring_count: Expression + crosshair_thickness: Expression + crosshair_length: Expression + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_6(self) diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py index 75e054df..e77a4073 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,13 @@ class Code7(Node): """Represents code 7 macro primitive.""" + center_x: Expression + center_y: Expression + outer_diameter: Expression + inner_diameter: Expression + gap_thickness: Expression + rotation: Expression + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_7(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index fa6f299a..63eb433b 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -385,7 +385,7 @@ def _point(s: str, loc: int, tokens: pp.ParseResults) -> Point: cs + self.expression.set_results_name("exposure") + cs - + self.expression.set_results_name("number_of_vertices") + + self.expression.set_results_name("number_of_points") + cs + self.expression.set_results_name("start_x") + cs @@ -397,7 +397,7 @@ def _point(s: str, loc: int, tokens: pp.ParseResults) -> Point: + cs + self.expression.set_results_name("y") ) - .set_results_name("point") + .set_results_name("points", list_all_matches=True) .set_parse_action(_point), ) + cs From 168f82dd1cb1aea748b3b83b8858cf91b9f95880 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 24 Jul 2024 00:29:42 +0200 Subject: [PATCH 11/91] Add test assets for macros --- .../gerberx3/tokens/aperture/macro/box.grb | 18 ++++++++++++++++++ .../gerberx3/tokens/aperture/macro/donut.grb | 5 +++++ 2 files changed, 23 insertions(+) create mode 100644 test/assets/gerberx3/tokens/aperture/macro/box.grb create mode 100644 test/assets/gerberx3/tokens/aperture/macro/donut.grb diff --git a/test/assets/gerberx3/tokens/aperture/macro/box.grb b/test/assets/gerberx3/tokens/aperture/macro/box.grb new file mode 100644 index 00000000..11e103de --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/macro/box.grb @@ -0,0 +1,18 @@ +%AMBox* +0 Rectangle with rounded corners, with rotation* +0 The origin of the aperture is its center* +0 $1 X-size* +0 $2 Y-size* +0 $3 Rounding radius* +0 $4 Rotation angle, in degrees counterclockwise* +0 Add two overlapping rectangle primitives as box body* +21,1,$1,$2-$3-$3,0,0,$4* +21,1,$1-$3-$3,$2,0,0,$4* +0 Add four circle primitives for the rounded corners* +$5=$1/2* +$6=$2/2* +$7=2x$3* +1,1,$7,$5-$3,$6-$3,$4* +1,1,$7,-$5+$3,$6-$3,$4* +1,1,$7,-$5+$3,-$6+$3,$4* +1,1,$7,$5-$3,-$6+$3,$4*% diff --git a/test/assets/gerberx3/tokens/aperture/macro/donut.grb b/test/assets/gerberx3/tokens/aperture/macro/donut.grb new file mode 100644 index 00000000..b69742a1 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/macro/donut.grb @@ -0,0 +1,5 @@ +%AMDonut* +1,1,$1,$2,$3* +$4=$1x0.75* +1,0,$4,$2,$3* +% From ff92e4ba88c7e0a8db33003ff05e839cda1960ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 24 Jul 2024 11:57:49 +0200 Subject: [PATCH 12/91] Reorder grammar definitions in grammar.py --- src/pygerber/gerberx3/ast/nodes/base.py | 2 +- .../gerberx3/parser/pyparsing/grammar.py | 643 ++++++++++-------- 2 files changed, 367 insertions(+), 278 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py index 25ec51f9..07db1e7a 100644 --- a/src/pygerber/gerberx3/ast/nodes/base.py +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -16,7 +16,7 @@ class Node(ModelType): """Base class for all nodes.""" - source: str = Field(repr=False) + source: str = Field(repr=False, exclude=True) location: int = Field(repr=False) @abstractmethod diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 63eb433b..0082fb95 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -75,9 +75,11 @@ def __init__( ast_node_class_overrides: dict[str, Type[Node]], *, enable_packrat: bool = True, + enable_debug: bool = False, ) -> None: self.ast_node_class_overrides = ast_node_class_overrides self.enable_packrat = enable_packrat + self.enable_debug = enable_debug def build(self) -> pp.ParserElement: """Build the grammar.""" @@ -99,11 +101,91 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: ) .set_results_name("root_node") .set_parse_action(_) - .set_debug() ) - root.enable_packrat() + + if self.enable_packrat: + root.enable_packrat() + + if self.enable_debug: + root.set_debug() + return root + def get_cls(self, node_cls: Type[T]) -> Type[T]: + """Get the class of the node.""" + return self.ast_node_class_overrides.get(node_cls.__qualname__, node_cls) # type: ignore[return-value] + + @pp.cached_property + def string(self) -> pp.ParserElement: + """Create a parser element capable of parsing strings.""" + return pp.CharsNotIn("%*").set_results_name("string") + + @pp.cached_property + def comma(self) -> pp.ParserElement: + """Create a parser element capable of parsing commas.""" + return pp.Suppress(pp.Literal(",").set_name("comma")) + + @pp.cached_property + def name(self) -> pp.ParserElement: + """Create a parser element capable of parsing names.""" + return pp.Regex(r"[._$a-zA-Z][._$a-zA-Z0-9]{0,126}").set_results_name("name") + + @pp.cached_property + def double(self) -> pp.ParserElement: + """Create a parser element capable of parsing doubles.""" + return pp.Regex(r"[+-]?(([0-9]+(\.[0-9]+)?)|(\.[0-9]+))").set_results_name( + "double" + ) + + # █████ ██████ ███████ ██████ ████████ ██ ██ ██████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ███████ ██████ █████ ██████ ██ ██ ██ ██████ █████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ ██ ███████ ██ ██ ██ ██████ ██ ██ ███████ + + def macro(self) -> pp.ParserElement: + """Create a parser element capable of parsing macros.""" + return ( + self.extended_command_open + + self.am_open + + self.command_end + + self.primitives + + self.am_close + + self.extended_command_close + ) + + @pp.cached_property + def am_open(self) -> pp.ParserElement: + """Create a parser element capable of parsing AM-open.""" + + def _am_open(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(AMopen)(source=s, location=loc, **tokens.as_dict()) + + return ( + (pp.Literal("AM") + self.name).set_name("AMopen").set_parse_action(_am_open) + ) + + @pp.cached_property + def am_close(self) -> pp.ParserElement: + """Create a parser element capable of parsing AM-close.""" + + def _am_close(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(AMclose)(source=s, location=loc, **tokens.as_dict()) + + return pp.Literal("").set_name("AM_close").set_parse_action(_am_close) + + # █████ ████████ ████████ ██████ ██ ██████ ██ ██ ████████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ███████ ██ ██ ██████ ██ ██████ ██ ██ ██ █████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ ██ ██ ██ ██ ██ ██████ ██████ ██ ███████ + + # ██████ █████ ███████ ██████ ███████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ ██ ██ ██ ██ ██ █████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██████ █████ ███████ ██████ ███████ ███████ + def d_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing D-codes.""" @@ -189,44 +271,11 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Dnn: return pp.MatchFirst([d01, d02, d03, dnn]) - def get_cls(self, node_cls: Type[T]) -> Type[T]: - """Get the class of the node.""" - return self.ast_node_class_overrides.get(node_cls.__qualname__, node_cls) # type: ignore[return-value] - - @pp.cached_property - def coordinate(self) -> pp.ParserElement: - """Create a parser element capable of parsing coordinates.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: - type_, value = tokens.as_list() - assert isinstance(type_, str), type(type_) - assert type_ in ("X", "Y", "I", "J") - assert isinstance(value, str) - - return self.get_cls(Coordinate)( - source=s, - location=loc, - type=type_, # type: ignore[arg-type] - value=value, - ) - - return ( - ( - pp.oneOf(("X", "Y", "I", "J")).set_name("coordinate_type") - + pp.Regex(r"[+-]?[0-9]+").set_name("coordinate_value") - ) - .set_parse_action(_) - .set_name("coordinate") - ) - - @pp.cached_property - def command_end(self) -> pp.ParserElement: - """Create a parser element capable of parsing the command end.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> CommandEnd: - return self.get_cls(CommandEnd)(source=s, location=loc) - - return pp.Literal("*").set_name("*").set_parse_action(_) + # ██████ █████ ███████ ██████ ███████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ███ ██ ██ ██ ██ ██ █████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██████ █████ ███████ ██████ ███████ ███████ def g_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing G-codes.""" @@ -270,11 +319,6 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> G04: ] ) - @pp.cached_property - def string(self) -> pp.ParserElement: - """Create a parser element capable of parsing strings.""" - return pp.CharsNotIn("%*").set_results_name("string") - def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: """Create a parser element capable of parsing particular G-code.""" element = pp.Regex(r"G0*" + str(value)).set_name(f"G{value}") @@ -282,6 +326,18 @@ def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: lambda s, loc, _tokens: self.get_cls(cls)(source=s, location=loc) ) + # ██ ██████ █████ ██████ █████ ███████ ███ ███ ███ ███ █████ ███ ██ ██████ ███████ # noqa: E501 + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ ████ ██ ██ ██ ██ # noqa: E501 + # ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ███████ ██ ██ ██ ██ ██ ███████ # noqa: E501 + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # noqa: E501 + # ██████ ███████ ██ ██ ██████ █████ ███████ ██ ██ ██ ██ ██ ██ ██ ████ ██████ ███████ # noqa: E501 + + # ███ ███ █████ ███████ ██████ ███████ ███████ + # ████ ████ ██ ██ ██ ██ ██ ██ ██ + # ██ ████ ██ ██ ██ ██ ██ ██ █████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ █████ ███████ ██████ ███████ ███████ + def m_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing M-codes.""" return pp.MatchFirst( @@ -305,231 +361,11 @@ def m(self, value: int, cls: Type[Node]) -> pp.ParserElement: lambda s, loc, _tokens: cls(source=s, location=loc) ) - @pp.cached_property - def extended_command_open(self) -> pp.ParserElement: - """Create a parser element capable of parsing the extended command open.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(ExtendedCommandOpen)(source=s, location=loc) - - return pp.Regex(r"%").set_name("%").set_parse_action(_) - - @pp.cached_property - def extended_command_close(self) -> pp.ParserElement: - """Create a parser element capable of parsing the extended command close.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(ExtendedCommandClose)(source=s, location=loc) - - return pp.Regex(r"%").set_name("%").set_parse_action(_) - - def macro(self) -> pp.ParserElement: - """Create a parser element capable of parsing macros.""" - - def _am_open(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(AMopen)(source=s, location=loc, **tokens.as_dict()) - - def _am_close(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(AMclose)(source=s, location=loc, **tokens.as_dict()) - - cs = pp.Suppress(pp.Literal(",").set_name("comma")) - - def _point(s: str, loc: int, tokens: pp.ParseResults) -> Point: - return self.get_cls(Point)(source=s, location=loc, **tokens.as_dict()) - - return ( - self.extended_command_open - + (pp.Literal("AM") + self.name) - .set_name("AMopen") - .set_parse_action(_am_open) - + self.command_end - + pp.ZeroOrMore( - pp.MatchFirst( - [ - self.assignment, - self.primitive(Code0, 0, self.string), - self.primitive( - Code1, - 1, - cs - + self.expression.set_results_name("exposure") - + cs - + self.expression.set_results_name("diameter") - + cs - + self.expression.set_results_name("center_x") - + cs - + self.expression.set_results_name("center_y") - + pp.Opt(cs + self.expression.set_results_name("rotation")), - ), - self.primitive( - Code2, - 2, - cs - + self.expression.set_results_name("exposure") - + cs - + self.expression.set_results_name("width") - + cs - + self.expression.set_results_name("start_x") - + cs - + self.expression.set_results_name("start_y") - + cs - + self.expression.set_results_name("end_x") - + cs - + self.expression.set_results_name("end_y") - + cs - + self.expression.set_results_name("rotation"), - ), - self.primitive( - Code4, - 4, - cs - + self.expression.set_results_name("exposure") - + cs - + self.expression.set_results_name("number_of_points") - + cs - + self.expression.set_results_name("start_x") - + cs - + self.expression.set_results_name("start_y") - + pp.OneOrMore( - ( - cs - + self.expression.set_results_name("x") - + cs - + self.expression.set_results_name("y") - ) - .set_results_name("points", list_all_matches=True) - .set_parse_action(_point), - ) - + cs - + self.expression.set_results_name("rotation"), - ), - self.primitive( - Code5, - 5, - cs - + self.expression.set_results_name("exposure") - + cs - + self.expression.set_results_name("number_of_vertices") - + cs - + self.expression.set_results_name("center_x") - + cs - + self.expression.set_results_name("center_y") - + cs - + self.expression.set_results_name("diameter") - + cs - + self.expression.set_results_name("rotation"), - ), - self.primitive( - Code6, - 6, - cs - + self.expression.set_results_name("center_x") - + cs - + self.expression.set_results_name("center_y") - + cs - + self.expression.set_results_name("outer_diameter") - + cs - + self.expression.set_results_name("ring_thickness") - + cs - + self.expression.set_results_name("gap_between_rings") - + cs - + self.expression.set_results_name("max_ring_count") - + cs - + self.expression.set_results_name("crosshair_thickness") - + cs - + self.expression.set_results_name("crosshair_length") - + cs - + self.expression.set_results_name("rotation"), - ), - self.primitive( - Code7, - 7, - cs - + self.expression.set_results_name("center_x") - + cs - + self.expression.set_results_name("center_y") - + cs - + self.expression.set_results_name("outer_diameter") - + cs - + self.expression.set_results_name("inner_diameter") - + cs - + self.expression.set_results_name("gap_thickness") - + cs - + self.expression.set_results_name("rotation"), - ), - self.primitive( - Code20, - 20, - cs - + self.expression.set_results_name("exposure") - + cs - + self.expression.set_results_name("width") - + cs - + self.expression.set_results_name("start_x") - + cs - + self.expression.set_results_name("start_y") - + cs - + self.expression.set_results_name("end_x") - + cs - + self.expression.set_results_name("end_y") - + cs - + self.expression.set_results_name("rotation"), - ), - self.primitive( - Code21, - 21, - cs - + self.expression.set_results_name("exposure") - + cs - + self.expression.set_results_name("width") - + cs - + self.expression.set_results_name("height") - + cs - + self.expression.set_results_name("center_x") - + cs - + self.expression.set_results_name("center_y") - + cs - + self.expression.set_results_name("rotation"), - ), - self.primitive( - Code22, - 22, - cs - + self.expression.set_results_name("exposure") - + cs - + self.expression.set_results_name("width") - + cs - + self.expression.set_results_name("height") - + cs - + self.expression.set_results_name("x_lower_left") - + cs - + self.expression.set_results_name("y_lower_left") - + cs - + self.expression.set_results_name("rotation"), - ), - ] - ) - ) - + (pp.Literal("").set_name("AM_close").set_parse_action(_am_close)) - + self.extended_command_close - ) - - @pp.cached_property - def name(self) -> pp.ParserElement: - """Create a parser element capable of parsing names.""" - return pp.Regex(r"[._$a-zA-Z][._$a-zA-Z0-9]{0,126}").set_results_name("name") - - def primitive( - self, cls: Type[Node], code: int, fields: pp.ParserElement - ) -> pp.ParserElement: - """Create a parser element capable of parsing a primitive.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(cls)(source=s, location=loc, **_tokens.as_dict()) - - return (pp.Literal(str(code)) + fields).set_name( - f"primitive-{code}" - ).set_parse_action(_) + self.command_end + # ███ ███ █████ ████████ ██ ██ + # ████ ████ ██ ██ ██ ██ ██ + # ██ ████ ██ ███████ ██ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ ██ ██ ██ ██ ██ @pp.cached_property def expression(self) -> pp.ParserElement: @@ -625,13 +461,6 @@ def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: return self.double.set_results_name("constant").set_parse_action(_) - @pp.cached_property - def double(self) -> pp.ParserElement: - """Create a parser element capable of parsing doubles.""" - return pp.Regex(r"[+-]?(([0-9]+(\.[0-9]+)?)|(\.[0-9]+))").set_results_name( - "double" - ) - @pp.cached_property def variable(self) -> pp.ParserElement: """Create a parser element capable of parsing variables.""" @@ -654,5 +483,265 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + self.expression.set_results_name("expression") ).set_results_name("assignment").set_parse_action(_) + self.command_end + # ██████ ████████ ██ ██ ███████ ██████ + # ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ ██ ███████ █████ ██████ + # ██ ██ ██ ██ ██ ██ ██ ██ + # ██████ ██ ██ ██ ███████ ██ ██ + + @pp.cached_property + def command_end(self) -> pp.ParserElement: + """Create a parser element capable of parsing the command end.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> CommandEnd: + return self.get_cls(CommandEnd)(source=s, location=loc) + + return pp.Literal("*").set_name("*").set_parse_action(_) + + @pp.cached_property + def coordinate(self) -> pp.ParserElement: + """Create a parser element capable of parsing coordinates.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: + type_, value = tokens.as_list() + assert isinstance(type_, str), type(type_) + assert type_ in ("X", "Y", "I", "J") + assert isinstance(value, str) + + return self.get_cls(Coordinate)( + source=s, + location=loc, + type=type_, # type: ignore[arg-type] + value=value, + ) + + return ( + ( + pp.oneOf(("X", "Y", "I", "J")).set_name("coordinate_type") + + pp.Regex(r"[+-]?[0-9]+").set_name("coordinate_value") + ) + .set_parse_action(_) + .set_name("coordinate") + ) + + @pp.cached_property + def extended_command_open(self) -> pp.ParserElement: + """Create a parser element capable of parsing the extended command open.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(ExtendedCommandOpen)(source=s, location=loc) + + return pp.Regex(r"%").set_name("%").set_parse_action(_) + + @pp.cached_property + def extended_command_close(self) -> pp.ParserElement: + """Create a parser element capable of parsing the extended command close.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(ExtendedCommandClose)(source=s, location=loc) + + return pp.Regex(r"%").set_name("%").set_parse_action(_) + + # ██████ ██████ ██ ███ ███ ██ ████████ ██ ██ ██ ███████ ███████ + # ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ + # ██████ ██████ ██ ██ ████ ██ ██ ██ ██ ██ ██ █████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ███████ ███████ + + @pp.cached_property + def primitives(self) -> pp.ParserElement: + """Create a parser element capable of parsing macro primitives.""" + + def _point(s: str, loc: int, tokens: pp.ParseResults) -> Point: + return self.get_cls(Point)(source=s, location=loc, **tokens.as_dict()) + + cs = self.comma + + return pp.ZeroOrMore( + pp.MatchFirst( + [ + self.assignment, + self.primitive(Code0, 0, self.string), + self.primitive( + Code1, + 1, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("diameter") + + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + pp.Opt(cs + self.expression.set_results_name("rotation")), + ), + self.primitive( + Code2, + 2, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("start_x") + + cs + + self.expression.set_results_name("start_y") + + cs + + self.expression.set_results_name("end_x") + + cs + + self.expression.set_results_name("end_y") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code4, + 4, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("number_of_points") + + cs + + self.expression.set_results_name("start_x") + + cs + + self.expression.set_results_name("start_y") + + pp.OneOrMore( + ( + cs + + self.expression.set_results_name("x") + + cs + + self.expression.set_results_name("y") + ) + .set_results_name("points", list_all_matches=True) + .set_parse_action(_point), + ) + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code5, + 5, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("number_of_vertices") + + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("diameter") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code6, + 6, + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("outer_diameter") + + cs + + self.expression.set_results_name("ring_thickness") + + cs + + self.expression.set_results_name("gap_between_rings") + + cs + + self.expression.set_results_name("max_ring_count") + + cs + + self.expression.set_results_name("crosshair_thickness") + + cs + + self.expression.set_results_name("crosshair_length") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code7, + 7, + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("outer_diameter") + + cs + + self.expression.set_results_name("inner_diameter") + + cs + + self.expression.set_results_name("gap_thickness") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code20, + 20, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("start_x") + + cs + + self.expression.set_results_name("start_y") + + cs + + self.expression.set_results_name("end_x") + + cs + + self.expression.set_results_name("end_y") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code21, + 21, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("height") + + cs + + self.expression.set_results_name("center_x") + + cs + + self.expression.set_results_name("center_y") + + cs + + self.expression.set_results_name("rotation"), + ), + self.primitive( + Code22, + 22, + cs + + self.expression.set_results_name("exposure") + + cs + + self.expression.set_results_name("width") + + cs + + self.expression.set_results_name("height") + + cs + + self.expression.set_results_name("x_lower_left") + + cs + + self.expression.set_results_name("y_lower_left") + + cs + + self.expression.set_results_name("rotation"), + ), + ] + ) + ) + + def primitive( + self, cls: Type[Node], code: int, fields: pp.ParserElement + ) -> pp.ParserElement: + """Create a parser element capable of parsing a primitive.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(cls)(source=s, location=loc, **_tokens.as_dict()) + + return (pp.Literal(str(code)) + fields).set_name( + f"primitive-{code}" + ).set_parse_action(_) + self.command_end + + # ██████ ██████ ██████ ██████ ███████ ███████ ███████ ██ ███████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██████ ██████ ██ ██ ██████ █████ ██████ ██ ██ █████ ███████ + # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ + # ██ ██ ██ ██████ ██ ███████ ██ ██ ██ ██ ███████ ███████ + Grammar.DEFAULT = Grammar({}).build() From aa64afd23f5e5e38f80e71c8538a976e118854ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 24 Jul 2024 17:38:30 +0200 Subject: [PATCH 13/91] Add aperture block and step repeat support --- .../gerberx3/ast/nodes/aperture/AB_open.py | 2 + .../gerberx3/ast/nodes/aperture/SR_open.py | 9 +- .../gerberx3/ast/nodes/d_codes/D01.py | 6 +- .../gerberx3/ast/nodes/d_codes/D03.py | 8 +- .../gerberx3/parser/pyparsing/grammar.py | 150 +++++++++++++++--- test/assets/gerberx3/tokens/d_codes/D01.grb | 2 + test/assets/gerberx3/tokens/d_codes/D03.grb | 1 + 7 files changed, 150 insertions(+), 28 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py index 54578c40..50f66a9c 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py @@ -13,6 +13,8 @@ class ABopen(Node): """Represents AB Gerber extended command.""" + aperture_identifier: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ab_open(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py index cbc3754a..3dd981fc 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py @@ -2,7 +2,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node @@ -13,6 +15,11 @@ class SRopen(Node): """Represents SR Gerber extended command.""" + x: Optional[str] = Field(default=None) + y: Optional[str] = Field(default=None) + i: Optional[str] = Field(default=None) + j: Optional[str] = Field(default=None) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_sr_open(self) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py index 6edfbfdc..adc7de25 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Optional +from pydantic import Field + from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate @@ -16,8 +18,8 @@ class D01(Node): x: Coordinate y: Coordinate - i: Optional[Coordinate] - j: Optional[Coordinate] + i: Optional[Coordinate] = Field(default=None) + j: Optional[Coordinate] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py index 7e0fdf7c..68b20b2c 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py @@ -2,7 +2,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate @@ -14,8 +16,8 @@ class D03(Node): """Represents D03 Gerber command.""" - x: Coordinate - y: Coordinate + x: Optional[Coordinate] = Field(default=None) + y: Optional[Coordinate] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 0082fb95..40330952 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -9,8 +9,11 @@ import pyparsing as pp from pydantic import ValidationError +from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose +from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen +from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 @@ -94,7 +97,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: self.g_codes(), self.m_codes(), self.d_codes(), - self.macro(), + self.aperture(), self.command_end, ] ) @@ -137,12 +140,71 @@ def double(self) -> pp.ParserElement: "double" ) + @pp.cached_property + def integer(self) -> pp.ParserElement: + """Create a parser element capable of parsing integers.""" + return pp.Regex(r"[+-]?[0-9]+").set_results_name("integer") + + @pp.cached_property + def aperture_identifier(self) -> pp.ParserElement: + """Create a parser element capable of parsing aperture identifiers.""" + return pp.Regex(r"D[0]*[1-9][0-9]+").set_results_name("aperture_identifier") + # █████ ██████ ███████ ██████ ████████ ██ ██ ██████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ███████ ██████ █████ ██████ ██ ██ ██ ██████ █████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ███████ ██ ██ ██ ██████ ██ ██ ███████ + def aperture(self) -> pp.ParserElement: + """Create a parser element capable of parsing apertures.""" + return pp.MatchFirst( + [ + self.aperture_block(), + self.macro(), + self.step_repeat(), + ] + ) + + def aperture_block(self) -> pp.ParserElement: + """Create a parser element capable of parsing aperture blocks.""" + return pp.MatchFirst( + [ + self.ab_open, + self.ab_close, + ] + ) + + @pp.cached_property + def ab_open(self) -> pp.ParserElement: + """Create a parser element capable of parsing AB-open.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(ABopen)(source=s, location=loc, **tokens.as_dict()) + + return ( + self.extended_command_open + + (pp.Literal("AB") + self.aperture_identifier) + .set_name("ABopen") + .set_parse_action(_) + + self.command_end + + self.extended_command_close + ) + + @pp.cached_property + def ab_close(self) -> pp.ParserElement: + """Create a parser element capable of parsing AB-close.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(ABclose)(source=s, location=loc) + + return ( + self.extended_command_open + + pp.Literal("AB").set_name("ABclose").set_parse_action(_) + + self.command_end + + self.extended_command_close + ) + def macro(self) -> pp.ParserElement: """Create a parser element capable of parsing macros.""" return ( @@ -174,6 +236,53 @@ def _am_close(s: str, loc: int, tokens: pp.ParseResults) -> Node: return pp.Literal("").set_name("AM_close").set_parse_action(_am_close) + def step_repeat(self) -> pp.ParserElement: + """Create a parser element capable of parsing step repeats.""" + return pp.MatchFirst( + [ + self.sr_open, + self.sr_close, + ] + ) + + @pp.cached_property + def sr_open(self) -> pp.ParserElement: + """Create a parser element capable of parsing SR-open.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(SRopen)(source=s, location=loc, **tokens.as_dict()) + + return ( + self.extended_command_open + + ( + ( + pp.Literal("SR") + + pp.Opt(pp.Literal("X") + self.double.set_results_name("x")) + + pp.Opt(pp.Literal("Y") + self.double.set_results_name("y")) + + pp.Opt(pp.Literal("I") + self.double.set_results_name("i")) + + pp.Opt(pp.Literal("J") + self.double.set_results_name("j")) + ) + .set_name("SRopen") + .set_parse_action(_) + ) + + self.command_end + + self.extended_command_close + ) + + @pp.cached_property + def sr_close(self) -> pp.ParserElement: + """Create a parser element capable of parsing SR-close.""" + + def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + return self.get_cls(ABclose)(source=s, location=loc) + + return ( + self.extended_command_open + + pp.Literal("SR").set_name("SRclose").set_parse_action(_) + + self.command_end + + self.extended_command_close + ) + # █████ ████████ ████████ ██████ ██ ██████ ██ ██ ████████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ███████ ██ ██ ██████ ██ ██████ ██ ██ ██ █████ @@ -190,26 +299,21 @@ def d_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing D-codes.""" def _(s: str, loc: int, tokens: pp.ParseResults) -> D01: - parse_result_token_list = tokens.as_list() - token_list = cast(list[Coordinate], parse_result_token_list) try: return self.get_cls(D01)( source=s, location=loc, - x=token_list[0], - y=token_list[1], - i=token_list[2] if len(token_list) >= 4 else None, # noqa: PLR2004 - j=token_list[3] if len(token_list) >= 5 else None, # noqa: PLR2004 + **tokens.as_dict(), ) except ValidationError as e: raise pp.ParseFatalException(s, loc, "Invalid D01") from e d01 = ( ( - self.coordinate - + self.coordinate - + pp.Opt(self.coordinate) - + pp.Opt(self.coordinate) + self.coordinate.set_results_name("x") + + self.coordinate.set_results_name("y") + + pp.Opt(self.coordinate.set_results_name("i")) + + pp.Opt(self.coordinate.set_results_name("j")) + pp.Regex(r"D0*1") ) .set_parse_action(_) @@ -217,39 +321,41 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> D01: ) def _(s: str, loc: int, tokens: pp.ParseResults) -> D02: - parse_result_token_list = tokens.as_list() - token_list = cast(list[Coordinate], parse_result_token_list) try: return self.get_cls(D02)( source=s, location=loc, - x=token_list[0], - y=token_list[1], + **tokens.as_dict(), ) except ValidationError as e: raise pp.ParseFatalException(s, loc, "Invalid D02") from e d02 = ( - (self.coordinate + self.coordinate + pp.Regex(r"D0*2")) + ( + self.coordinate.set_results_name("x") + + self.coordinate.set_results_name("y") + + pp.Regex(r"D0*2") + ) .set_parse_action(_) .set_name("D02") ) def _(s: str, loc: int, tokens: pp.ParseResults) -> D03: - parse_result_token_list = tokens.as_list() - token_list = cast(list[Coordinate], parse_result_token_list) try: return self.get_cls(D03)( source=s, location=loc, - x=token_list[0], - y=token_list[1], + **tokens.as_dict(), ) except ValidationError as e: raise pp.ParseFatalException(s, loc, "Invalid D03") from e d03 = ( - (self.coordinate + self.coordinate + pp.Regex(r"D0*3")) + ( + pp.Opt(self.coordinate.set_results_name("x")) + + pp.Opt(self.coordinate.set_results_name("y")) + + pp.Regex(r"D0*3") + ) .set_parse_action(_) .set_name("D03") ) @@ -518,7 +624,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: return ( ( pp.oneOf(("X", "Y", "I", "J")).set_name("coordinate_type") - + pp.Regex(r"[+-]?[0-9]+").set_name("coordinate_value") + + self.integer.set_name("coordinate_value") ) .set_parse_action(_) .set_name("coordinate") diff --git a/test/assets/gerberx3/tokens/d_codes/D01.grb b/test/assets/gerberx3/tokens/d_codes/D01.grb index e9a837c5..1312f7d5 100644 --- a/test/assets/gerberx3/tokens/d_codes/D01.grb +++ b/test/assets/gerberx3/tokens/d_codes/D01.grb @@ -1,2 +1,4 @@ X0000000Y2000000D01* X060000Y000000I000000J060000D01* +X060000Y000000J060000D01* +X060000Y000000I000000D01* diff --git a/test/assets/gerberx3/tokens/d_codes/D03.grb b/test/assets/gerberx3/tokens/d_codes/D03.grb index 2fa0782c..8831dbde 100644 --- a/test/assets/gerberx3/tokens/d_codes/D03.grb +++ b/test/assets/gerberx3/tokens/d_codes/D03.grb @@ -1 +1,2 @@ X1500000Y1500000D03* +D03* From f56e1b2d7bf26dd4f11858562536c79be8f9e104 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 25 Jul 2024 00:48:24 +0200 Subject: [PATCH 14/91] Add parsing of AD command --- .../ast/nodes/aperture/{AD.py => ADC.py} | 14 ++- .../gerberx3/ast/nodes/aperture/ADO.py | 25 ++++ .../gerberx3/ast/nodes/aperture/ADP.py | 26 ++++ .../gerberx3/ast/nodes/aperture/ADR.py | 25 ++++ .../gerberx3/ast/nodes/aperture/ADmacro.py | 24 ++++ src/pygerber/gerberx3/ast/visitor.py | 22 +++- .../gerberx3/parser/pyparsing/grammar.py | 113 +++++++++++++++++- .../gerberx3/tokens/aperture/ad/circle.grb | 2 + .../gerberx3/tokens/aperture/ad/macro.grb | 2 + .../gerberx3/tokens/aperture/ad/obround.grb | 2 + .../gerberx3/tokens/aperture/ad/polygon.grb | 3 + .../gerberx3/tokens/aperture/ad/rectangle.grb | 2 + 12 files changed, 252 insertions(+), 8 deletions(-) rename src/pygerber/gerberx3/ast/nodes/aperture/{AD.py => ADC.py} (50%) create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/ADO.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/ADP.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/ADR.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py create mode 100644 test/assets/gerberx3/tokens/aperture/ad/circle.grb create mode 100644 test/assets/gerberx3/tokens/aperture/ad/macro.grb create mode 100644 test/assets/gerberx3/tokens/aperture/ad/obround.grb create mode 100644 test/assets/gerberx3/tokens/aperture/ad/polygon.grb create mode 100644 test/assets/gerberx3/tokens/aperture/ad/rectangle.grb diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py similarity index 50% rename from src/pygerber/gerberx3/ast/nodes/aperture/AD.py rename to src/pygerber/gerberx3/ast/nodes/aperture/ADC.py index 59672f9d..ce7b182a 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py @@ -1,8 +1,10 @@ -"""`pygerber.nodes.aperture.AD` module contains definition of `AD` class.""" +"""`pygerber.nodes.aperture.ADC` module contains definition of `AD` class.""" from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node @@ -10,9 +12,13 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class AD(Node): +class ADC(Node): """Represents AD Gerber extended command.""" + aperture_identifier: str + diameter: str + hole_diameter: Optional[str] = Field(default=None) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" - visitor.on_ad(self) + visitor.on_adc(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py new file mode 100644 index 00000000..ab6b6e06 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py @@ -0,0 +1,25 @@ +"""`pygerber.nodes.aperture.ADO` module contains definition of `AD` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ADO(Node): + """Represents AD obround Gerber extended command.""" + + aperture_identifier: str + width: str + height: str + hole_diameter: Optional[str] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ado(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py new file mode 100644 index 00000000..4e38738e --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py @@ -0,0 +1,26 @@ +"""`pygerber.nodes.aperture.ADP` module contains definition of `AD` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ADP(Node): + """Represents AD polygon Gerber extended command.""" + + aperture_identifier: str + outer_diameter: str + vertices: str + rotation: Optional[str] = Field(default=None) + hole_diameter: Optional[str] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_adp(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py new file mode 100644 index 00000000..88761f74 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py @@ -0,0 +1,25 @@ +"""`pygerber.nodes.aperture.ADP` module contains definition of `AD` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ADR(Node): + """Represents AD rectangle Gerber extended command.""" + + aperture_identifier: str + width: str + height: str + hole_diameter: Optional[str] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_adr(self) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py new file mode 100644 index 00000000..f8bd61d5 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py @@ -0,0 +1,24 @@ +"""`pygerber.nodes.aperture.ADmacro` module contains definition of `AD` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, List, Optional + +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class ADmacro(Node): + """Represents AD macro Gerber extended command.""" + + aperture_identifier: str + name: str + params: Optional[List[str]] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ad_macro(self) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 5240410f..d9c04a74 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -7,7 +7,11 @@ if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen - from pygerber.gerberx3.ast.nodes.aperture.AD import AD + from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC + from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro + from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO + from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP + from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose @@ -99,8 +103,20 @@ def on_ab_close(self, node: ABclose) -> None: def on_ab_open(self, node: ABopen) -> None: """Handle `ABopen` node.""" - def on_ad(self, node: AD) -> None: - """Handle `AD` node.""" + def on_adc(self, node: ADC) -> None: + """Handle `AD` circle node.""" + + def on_adr(self, node: ADR) -> None: + """Handle `AD` rectangle node.""" + + def on_ado(self, node: ADO) -> None: + """Handle `AD` obround node.""" + + def on_adp(self, node: ADP) -> None: + """Handle `AD` polygon node.""" + + def on_ad_macro(self, node: ADmacro) -> None: + """Handle `AD` macro node.""" def on_am_close(self, node: AMclose) -> None: """Handle `AMclose` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 40330952..b225b68a 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -4,13 +4,18 @@ from __future__ import annotations -from typing import ClassVar, List, Type, TypeVar, cast +from typing import ClassVar, List, Literal, Type, TypeVar, cast import pyparsing as pp from pydantic import ValidationError from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen +from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC +from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro +from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO +from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP +from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen @@ -163,6 +168,7 @@ def aperture(self) -> pp.ParserElement: self.aperture_block(), self.macro(), self.step_repeat(), + self.add_aperture(), ] ) @@ -283,6 +289,111 @@ def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: + self.extended_command_close ) + def add_aperture(self) -> pp.ParserElement: + """Create a parser element capable of parsing add-aperture commands.""" + return ( + self.extended_command_open + + pp.Suppress(pp.Literal("AD")) + + pp.MatchFirst( + [ + self.add_aperture_circle(), + self.add_aperture_rectangle("R", ADR), + self.add_aperture_rectangle("O", ADO), + self.add_aperture_polygon(), + self.add_aperture_macro(), + ] + ) + + self.command_end + + self.extended_command_close + ) + + def add_aperture_circle(self) -> pp.ParserElement: + """Create a parser element capable of parsing add-aperture-circle commands.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(ADC)(source=s, location=loc, **tokens.as_dict()) + + return ( + ( + self.aperture_identifier + + pp.Literal("C,") + + self.double.set_results_name("diameter") + + pp.Opt(self.x + self.double.set_results_name("hole_diameter")) + ) + .set_name("ADC") + .set_parse_action(_) + ) + + def add_aperture_rectangle( + self, symbol: Literal["R", "O"], cls: Type[Node] + ) -> pp.ParserElement: + """Create a parser element capable of parsing add-aperture-rectangle + commands. + """ + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(cls)(source=s, location=loc, **tokens.as_dict()) + + return ( + ( + self.aperture_identifier + + pp.Literal(f"{symbol},") + + self.double.set_results_name("width") + + self.x + + self.double.set_results_name("height") + + pp.Opt(self.x + self.double.set_results_name("hole_diameter")) + ) + .set_name("ADC") + .set_parse_action(_) + ) + + def add_aperture_polygon(self) -> pp.ParserElement: + """Create a parser element capable of parsing add-aperture-polygon + commands. + """ + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(ADP)(source=s, location=loc, **tokens.as_dict()) + + return ( + ( + self.aperture_identifier + + pp.Literal("P,") + + self.double.set_results_name("outer_diameter") + + self.x + + self.double.set_results_name("vertices") + + pp.Opt(self.x + self.double.set_results_name("rotation")) + + pp.Opt(self.x + self.double.set_results_name("hole_diameter")) + ) + .set_name("ADC") + .set_parse_action(_) + ) + + def add_aperture_macro(self) -> pp.ParserElement: + """Create a parser element capable of parsing add-aperture-polygon + commands. + """ + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(ADmacro)(source=s, location=loc, **tokens.as_dict()) + + param = self.double.set_results_name("params", list_all_matches=True) + + return ( + ( + self.aperture_identifier + + self.name.set_results_name("name") + + pp.Opt(self.comma + param + pp.ZeroOrMore(self.x + param)) + ) + .set_name("ADC") + .set_parse_action(_) + ) + + @pp.cached_property + def x(self) -> pp.ParserElement: + """Create a parser element capable of parsing literal X.""" + return pp.Suppress(pp.Literal("X")).set_name("X") + # █████ ████████ ████████ ██████ ██ ██████ ██ ██ ████████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ███████ ██ ██ ██████ ██ ██████ ██ ██ ██ █████ diff --git a/test/assets/gerberx3/tokens/aperture/ad/circle.grb b/test/assets/gerberx3/tokens/aperture/ad/circle.grb new file mode 100644 index 00000000..2c2d4ab3 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/ad/circle.grb @@ -0,0 +1,2 @@ +%ADD10C,0.5*% +%ADD10C,0.5X0.25*% diff --git a/test/assets/gerberx3/tokens/aperture/ad/macro.grb b/test/assets/gerberx3/tokens/aperture/ad/macro.grb new file mode 100644 index 00000000..90f1ec69 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/ad/macro.grb @@ -0,0 +1,2 @@ +%ADD34DONUTVAR,0.100X0X0X0.080*% +%ADD34DONUTVAR,0.100X0X0X0.080X0.9X1.2X3.3*% diff --git a/test/assets/gerberx3/tokens/aperture/ad/obround.grb b/test/assets/gerberx3/tokens/aperture/ad/obround.grb new file mode 100644 index 00000000..58c95bbb --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/ad/obround.grb @@ -0,0 +1,2 @@ +%ADD22O,0.046X0.026*% +%ADD22O,0.046X0.026X0.019*% diff --git a/test/assets/gerberx3/tokens/aperture/ad/polygon.grb b/test/assets/gerberx3/tokens/aperture/ad/polygon.grb new file mode 100644 index 00000000..3513d537 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/ad/polygon.grb @@ -0,0 +1,3 @@ +%ADD17P,.040X6*% +%ADD17P,.040X6X0.0X0.019*% +%ADD17P,.040X6X0.0X0.019X0.0*% diff --git a/test/assets/gerberx3/tokens/aperture/ad/rectangle.grb b/test/assets/gerberx3/tokens/aperture/ad/rectangle.grb new file mode 100644 index 00000000..93692665 --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/ad/rectangle.grb @@ -0,0 +1,2 @@ +%ADD22R,0.044X0.025*% +%ADD23R,0.044X0.025X0.019*% From 4e136407dd83b0aac309e3ed6f9568dea479f574 Mon Sep 17 00:00:00 2001 From: Sander de Regt Date: Thu, 25 Jul 2024 19:11:25 +0200 Subject: [PATCH 15/91] Add implementation of FS node parsing --- .../gerberx3/ast/nodes/properties/FS.py | 6 ++++ .../gerberx3/parser/pyparsing/grammar.py | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py index fc45855b..cedde215 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/FS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -13,6 +14,11 @@ class FS(Node): """Represents FS Gerber extended command.""" + x: Coordinate + y: Coordinate + zeros: str + coordinate_mode: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_fs(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index b225b68a..160fc15b 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -69,6 +69,7 @@ from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 +from pygerber.gerberx3.ast.nodes.properties.FS import FS T = TypeVar("T", bound=Node) @@ -103,6 +104,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: self.m_codes(), self.d_codes(), self.aperture(), + self.properties(), self.command_end, ] ) @@ -960,5 +962,33 @@ def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██████ ██ ███████ ██ ██ ██ ██ ███████ ███████ + def properties(self) -> pp.ParserElement: + """Create a parser element capable of parsing Properties-commands.""" + return self.fs() + + def fs(self) -> pp.ParserElement: + """Create a parser for the FS command.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> FS: + try: + return self.get_cls(FS)(source=s, location=loc, **tokens.as_dict()) + except ValidationError as e: + raise pp.ParseFatalException(s, loc, "Invalid FS") from e + + return ( + self.extended_command_open + + ( + pp.Literal("FS") + + pp.oneOf(("L", "T")).set_results_name("zeros") + + pp.oneOf(("I", "A")).set_results_name("coordinate_mode") + + self.coordinate.set_results_name("x") + + self.coordinate.set_results_name("y") + ) + .set_parse_action(_) + .set_name("FS") + + self.command_end + + self.extended_command_close + ) + Grammar.DEFAULT = Grammar({}).build() From 98075f1bdbb91ee2dba6d6500409a40bd04cef04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 26 Jul 2024 18:32:42 +0200 Subject: [PATCH 16/91] Add MO command parsing --- .../gerberx3/ast/nodes/g_codes/G04.py | 4 +- .../gerberx3/ast/nodes/properties/MO.py | 10 +++ .../gerberx3/parser/pyparsing/grammar.py | 84 +++++++++++++------ 3 files changed, 71 insertions(+), 27 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py index 7e5c45c8..50fda352 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional from pydantic import Field @@ -15,7 +15,7 @@ class G04(Node): """Represents G04 Gerber command.""" - content: str = Field(default="") + string: Optional[str] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MO.py b/src/pygerber/gerberx3/ast/nodes/properties/MO.py index d1bc3d73..21a8c188 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/MO.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/MO.py @@ -2,6 +2,7 @@ from __future__ import annotations +from enum import Enum from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node @@ -10,9 +11,18 @@ from pygerber.gerberx3.ast.visitor import AstVisitor +class UnitMode(Enum): + """Unit mode enumeration.""" + + INCH = "IN" + MILLIMETER = "MM" + + class MO(Node): """Represents MO Gerber extended command.""" + mode: UnitMode + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_mo(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 160fc15b..387a4835 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -4,7 +4,8 @@ from __future__ import annotations -from typing import ClassVar, List, Literal, Type, TypeVar, cast +from enum import IntFlag +from typing import Callable, ClassVar, List, Literal, Type, TypeVar, cast import pyparsing as pp from pydantic import ValidationError @@ -70,10 +71,19 @@ from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 from pygerber.gerberx3.ast.nodes.properties.FS import FS +from pygerber.gerberx3.ast.nodes.properties.MO import MO T = TypeVar("T", bound=Node) +class Optimization(IntFlag): + """Namespace class holding optimization level constants.""" + + DISCARD_COMMAND_BOUNDARIES = 0b0001 + DISCARD_COMMENTS = 0b0010 + DISCARD_ATTRIBUTES = 0b0100 + + class Grammar: """Internal representation of the Gerber X3 grammar.""" @@ -85,10 +95,12 @@ def __init__( *, enable_packrat: bool = True, enable_debug: bool = False, + optimization: int = 1, ) -> None: self.ast_node_class_overrides = ast_node_class_overrides self.enable_packrat = enable_packrat self.enable_debug = enable_debug + self.optimization = optimization def build(self) -> pp.ParserElement: """Build the grammar.""" @@ -157,6 +169,16 @@ def aperture_identifier(self) -> pp.ParserElement: """Create a parser element capable of parsing aperture identifiers.""" return pp.Regex(r"D[0]*[1-9][0-9]+").set_results_name("aperture_identifier") + def make_unpack_callback( + self, node_type: Type[Node] + ) -> Callable[[str, int, pp.ParseResults], Node]: + """Create a callback for unpacking the results of the parser.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + return self.get_cls(node_type)(source=s, location=loc, **tokens.as_dict()) + + return _ + # █████ ██████ ███████ ██████ ████████ ██ ██ ██████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ███████ ██████ █████ ██████ ██ ██ ██ ██████ █████ @@ -498,15 +520,12 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Dnn: def g_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing G-codes.""" + g04_comment = (pp.Regex(r"G0*4") + pp.Opt(self.string)).set_name("G04") - def _(s: str, loc: int, tokens: pp.ParseResults) -> G04: - content = tokens.as_dict().get("string") - assert isinstance(content, str) - return self.get_cls(G04)(source=s, location=loc, content=content) - - g04_comment = ( - (pp.Regex(r"G0*4") + self.string).set_name("G04").set_parse_action(_) - ) + if self.optimization & Optimization.DISCARD_COMMENTS: + g04_comment = pp.Suppress(g04_comment) + else: + g04_comment = g04_comment.set_parse_action(self.make_unpack_callback(G04)) return pp.MatchFirst( [ @@ -514,14 +533,13 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> G04: *( self.g( value, - self.get_cls(cls), # type: ignore[arg-type] + self.get_cls(cls), # type: ignore[arg-type, type-abstract] ) for (value, cls) in reversed( ( (1, G01), (2, G02), (3, G03), - (4, G04), (36, G36), (37, G37), (54, G54), @@ -651,7 +669,7 @@ def _mul(s: str, loc: int, tokens: pp.ParseResults) -> Expression: _div, ), ( - pp.Suppress(pp.oneOf("x X")), + pp.Suppress(pp.one_of("x X")), 2, pp.OpAssoc.LEFT, _mul, @@ -711,11 +729,12 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: @pp.cached_property def command_end(self) -> pp.ParserElement: """Create a parser element capable of parsing the command end.""" + parser = pp.Literal(r"*").set_name("*") - def _(s: str, loc: int, _tokens: pp.ParseResults) -> CommandEnd: - return self.get_cls(CommandEnd)(source=s, location=loc) + if self.optimization & Optimization.DISCARD_COMMAND_BOUNDARIES: + return pp.Suppress(parser) - return pp.Literal("*").set_name("*").set_parse_action(_) + return parser.set_parse_action(self.make_unpack_callback(CommandEnd)) @pp.cached_property def coordinate(self) -> pp.ParserElement: @@ -736,7 +755,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: return ( ( - pp.oneOf(("X", "Y", "I", "J")).set_name("coordinate_type") + pp.one_of(("X", "Y", "I", "J")).set_name("coordinate_type") + self.integer.set_name("coordinate_value") ) .set_parse_action(_) @@ -746,20 +765,22 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: @pp.cached_property def extended_command_open(self) -> pp.ParserElement: """Create a parser element capable of parsing the extended command open.""" + parser = pp.Literal(r"%").set_name("%") - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(ExtendedCommandOpen)(source=s, location=loc) + if self.optimization & Optimization.DISCARD_COMMAND_BOUNDARIES: + return pp.Suppress(parser) - return pp.Regex(r"%").set_name("%").set_parse_action(_) + return parser.set_parse_action(self.make_unpack_callback(ExtendedCommandOpen)) @pp.cached_property def extended_command_close(self) -> pp.ParserElement: """Create a parser element capable of parsing the extended command close.""" + parser = pp.Literal(r"%").set_name("%") - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(ExtendedCommandClose)(source=s, location=loc) + if self.optimization & Optimization.DISCARD_COMMAND_BOUNDARIES: + return pp.Suppress(parser) - return pp.Regex(r"%").set_name("%").set_parse_action(_) + return parser.set_parse_action(self.make_unpack_callback(ExtendedCommandClose)) # ██████ ██████ ██ ███ ███ ██ ████████ ██ ██ ██ ███████ ███████ # ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ @@ -964,7 +985,7 @@ def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: def properties(self) -> pp.ParserElement: """Create a parser element capable of parsing Properties-commands.""" - return self.fs() + return pp.MatchFirst([self.fs(), self.mo()]) def fs(self) -> pp.ParserElement: """Create a parser for the FS command.""" @@ -979,8 +1000,8 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> FS: self.extended_command_open + ( pp.Literal("FS") - + pp.oneOf(("L", "T")).set_results_name("zeros") - + pp.oneOf(("I", "A")).set_results_name("coordinate_mode") + + pp.one_of(("L", "T")).set_results_name("zeros") + + pp.one_of(("I", "A")).set_results_name("coordinate_mode") + self.coordinate.set_results_name("x") + self.coordinate.set_results_name("y") ) @@ -990,5 +1011,18 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> FS: + self.extended_command_close ) + def mo(self) -> pp.ParserElement: + """Create a parser for the MO command.""" + return ( + self.extended_command_open + + ( + (pp.Literal("MO") + pp.one_of(["IN", "MM"]).set_results_name("mode")) + .set_parse_action(self.make_unpack_callback(MO)) + .set_name("MO") + ) + + self.command_end + + self.extended_command_close + ) + Grammar.DEFAULT = Grammar({}).build() From 9662f0218957784597e13665191678a4f9297527 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 26 Jul 2024 18:34:24 +0200 Subject: [PATCH 17/91] Skip token tests in Parser2 related suites --- test/gerberx3/test_assets.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/gerberx3/test_assets.py b/test/gerberx3/test_assets.py index d43b2302..f7276dac 100644 --- a/test/gerberx3/test_assets.py +++ b/test/gerberx3/test_assets.py @@ -35,6 +35,7 @@ class Config(ConfigBase): common_case_generator_config = { "macro.*": Config(dpmm=100), "incomplete.*": Config(skip=True), + "tokens.*": Config(skip=True), } From 94313c3607773d42c944567eb1e0f907d40ed40f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 26 Jul 2024 20:39:45 +0200 Subject: [PATCH 18/91] Add TA extended command support --- .../gerberx3/ast/nodes/attribute/TA.py | 93 ++++++++++++- src/pygerber/gerberx3/ast/visitor.py | 23 ++++ .../gerberx3/parser/pyparsing/grammar.py | 127 +++++++++++++++++- test/assets/gerberx3/tokens/attribute/TA.grb | 7 - .../tokens/attribute/TA_AperFunction.grb | 15 +++ .../tokens/attribute/TA_DrillTolerance.grb | 2 + .../tokens/attribute/TA_FlashText.grb | 2 + .../gerberx3/tokens/attribute/TA_custom.grb | 1 + 8 files changed, 258 insertions(+), 12 deletions(-) delete mode 100644 test/assets/gerberx3/tokens/attribute/TA.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TA_AperFunction.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TA_DrillTolerance.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TA_FlashText.grb diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py index 9c70b2f1..b082aa1e 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py @@ -2,7 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from abc import abstractmethod +from enum import Enum +from typing import TYPE_CHECKING, List, Literal, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node @@ -13,6 +17,91 @@ class TA(Node): """Represents TA Gerber extended command.""" + @abstractmethod + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + + +class TA_UserName(TA): # noqa: N801 + """Represents TA Gerber extended command with user name.""" + + user_name: str + fields: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ta_user_name(self) + + +class AperFunction(Enum): + """Enum representing possible AperFunction values.""" + + ViaDrill = "ViaDrill" + BackDrill = "BackDrill" + ComponentDrill = "ComponentDrill" + MechanicalDrill = "MechanicalDrill" + CastellatedDrill = "CastellatedDrill" + OtherDrill = "OtherDrill" + ComponentPad = "ComponentPad" + SMDPad = "SMDPad" + BGAPad = "BGAPad" + ConnectorPad = "ConnectorPad" + HeatsinkPad = "HeatsinkPad" + ViaPad = "ViaPad" + TestPad = "TestPad" + CastellatedPad = "CastellatedPad" + FiducialPad = "FiducialPad" + ThermalReliefPad = "ThermalReliefPad" + WasherPad = "WasherPad" + AntiPad = "AntiPad" + OtherPad = "OtherPad" + Conductor = "Conductor" + EtchedComponent = "EtchedComponent" + NonConductor = "NonConductor" + CopperBalancing = "CopperBalancing" + Border = "Border" + OtherCopper = "OtherCopper" + ComponentMain = "ComponentMain" + ComponentOutline = "ComponentOutline" + ComponentPin = "ComponentPin" + Profile = "Profile" + Material = "Material" + NonMaterial = "NonMaterial" + Other = "Other" + + +class TA_AperFunction(TA): # noqa: N801 + """Represents TA .AperFunction Gerber attribute.""" + + function: Optional[AperFunction] = Field(default=None) + fields: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ta_aper_function(self) + + +class TA_DrillTolerance(TA): # noqa: N801 + """Represents TA .DrillTolerance Gerber attribute.""" + + plus_tolerance: Optional[float] = Field(default=None) + minus_tolerance: Optional[float] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ta_drill_tolerance(self) + + +class TA_FlashText(TA): # noqa: N801 + """Represents TA .FlashText Gerber attribute.""" + + string: str + mode: Literal["B", "C"] + mirroring: Literal["R", "M"] = Field(default="R") + font: Optional[str] = Field(default=None) + size: Optional[float] = Field(default=None) + comments: List[str] = Field(default_factory=list) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" - visitor.on_ta(self) + visitor.on_ta_flash_text(self) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index d9c04a74..cee95d3a 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -4,6 +4,13 @@ from typing import TYPE_CHECKING +from pygerber.gerberx3.ast.nodes.attribute.TA import ( + TA_AperFunction, + TA_DrillTolerance, + TA_FlashText, + TA_UserName, +) + if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen @@ -135,6 +142,22 @@ def on_sr_open(self, node: SRopen) -> None: def on_ta(self, node: TA) -> None: """Handle `TA` node.""" + def on_ta_user_name(self, node: TA_UserName) -> None: + """Handle `TA` node.""" + self.on_ta(node) + + def on_ta_aper_function(self, node: TA_AperFunction) -> None: + """Handle `TA` node.""" + self.on_ta(node) + + def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: + """Handle `TA` node.""" + self.on_ta(node) + + def on_ta_flash_text(self, node: TA_FlashText) -> None: + """Handle `TA` node.""" + self.on_ta(node) + def on_td(self, node: TD) -> None: """Handle `TD` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 387a4835..f75a34c9 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -20,6 +20,13 @@ from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen +from pygerber.gerberx3.ast.nodes.attribute.TA import ( + AperFunction, + TA_AperFunction, + TA_DrillTolerance, + TA_FlashText, + TA_UserName, +) from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 @@ -112,10 +119,11 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: pp.OneOrMore( pp.MatchFirst( [ + self.aperture(), + self.attribute(), self.g_codes(), self.m_codes(), self.d_codes(), - self.aperture(), self.properties(), self.command_end, ] @@ -145,12 +153,22 @@ def string(self) -> pp.ParserElement: @pp.cached_property def comma(self) -> pp.ParserElement: """Create a parser element capable of parsing commas.""" - return pp.Suppress(pp.Literal(",").set_name("comma")) + return pp.Suppress(pp.Literal(",").set_name(",")) @pp.cached_property def name(self) -> pp.ParserElement: """Create a parser element capable of parsing names.""" - return pp.Regex(r"[._$a-zA-Z][._$a-zA-Z0-9]{0,126}").set_results_name("name") + return pp.Regex(r"[._a-zA-Z$][._a-zA-Z0-9]*/").set_results_name("name") + + @pp.cached_property + def user_name(self) -> pp.ParserElement: + """Create a parser element capable of parsing user attribute names.""" + return pp.Regex(r"[_a-zA-Z$][._a-zA-Z0-9]*").set_results_name("user_name") + + @pp.cached_property + def field(self) -> pp.ParserElement: + """Create a parser element capable of parsing user attribute names.""" + return pp.Regex(r"[^%*,]*").set_results_name("field") @pp.cached_property def double(self) -> pp.ParserElement: @@ -424,6 +442,109 @@ def x(self) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██ ██ ██ ██ ██████ ██████ ██ ███████ + def attribute(self) -> pp.ParserElement: + """Create a parser element capable of parsing attributes.""" + return pp.MatchFirst( + [ + self.ta(), + ] + ) + + def ta(self) -> pp.ParserElement: + """Create a parser element capable of parsing TA attributes.""" + return ( + self.extended_command_open + + pp.MatchFirst( + [ + self._ta_user_name, + self._ta_aper_function, + self._ta_drill_tolerance, + self._ta_flash_text, + ] + ) + + self.command_end + + self.extended_command_close + ) + + @pp.cached_property + def _ta_user_name(self) -> pp.ParserElement: + return ( + ( + pp.Literal("TA") + + self.user_name + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("fields", list_all_matches=True) + ) + ) + .set_name("TA-with-user-name") + .set_parse_action(self.make_unpack_callback(TA_UserName)) + ) + + @pp.cached_property + def _ta_aper_function(self) -> pp.ParserElement: + return ( + ( + pp.Literal("TA") + + pp.Literal(".AperFunction") + + pp.Optional( + self.comma + + pp.one_of([v.value for v in AperFunction]).set_results_name( + "function" + ) + ) + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("fields", list_all_matches=True) + ) + ) + .set_name("TA.AperFunction") + .set_parse_action(self.make_unpack_callback(TA_AperFunction)) + ) + + @pp.cached_property + def _ta_drill_tolerance(self) -> pp.ParserElement: + return ( + ( + pp.Literal("TA") + + pp.Literal(".DrillTolerance") + + pp.Optional( + self.comma + + self.double.set_results_name("plus_tolerance") + + pp.Optional( + self.comma + self.double.set_results_name("minus_tolerance") + ) + ) + ) + .set_name("TA.DrillTolerance") + .set_parse_action(self.make_unpack_callback(TA_DrillTolerance)) + ) + + @pp.cached_property + def _ta_flash_text(self) -> pp.ParserElement: + return ( + ( + pp.Literal("TA") + + pp.Literal(".FlashText") + + self.comma + + self.field.set_results_name("string") + + self.comma + + pp.one_of(list("BC")).set_results_name("mode") + + self.comma + + pp.one_of(list("RM")).set_results_name("mirroring") + + self.comma + + self.field.set_results_name("font") + + self.comma + + self.field.set_results_name("size") + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("comments", list_all_matches=True) + ) + ) + .set_name("TA.FlashText") + .set_parse_action(self.make_unpack_callback(TA_FlashText)) + ) + # ██████ █████ ███████ ██████ ███████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██ ██ ██ ██ █████ ███████ diff --git a/test/assets/gerberx3/tokens/attribute/TA.grb b/test/assets/gerberx3/tokens/attribute/TA.grb deleted file mode 100644 index 0be31a80..00000000 --- a/test/assets/gerberx3/tokens/attribute/TA.grb +++ /dev/null @@ -1,7 +0,0 @@ -%TA.AperFunction,AntiPad*% -%TA.AperFunction,ViaPad*% -%TA.DrillTolerance,0.02,0.01*% -%TA.AperFunction,ComponentDrill*% -%TA.AperFunction,Other,Special*% -%TA.AperFunction,MechanicalDrill*% -%TA.DrillTolerance,0.15,0.15*% diff --git a/test/assets/gerberx3/tokens/attribute/TA_AperFunction.grb b/test/assets/gerberx3/tokens/attribute/TA_AperFunction.grb new file mode 100644 index 00000000..d589cdd8 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TA_AperFunction.grb @@ -0,0 +1,15 @@ +%TA.AperFunction,AntiPad*% +%TA.AperFunction,ViaPad*% +%TA.AperFunction,ComponentDrill*% +%TA.AperFunction,Other,Special*% +%TA.AperFunction,MechanicalDrill*% +%TA.AperFunction,Conductor*% +%TA.AperFunction,SMDPad,CuDef*% +%TA.AperFunction,ViaPad*% +%TA.AperFunction,ComponentPad*% +%TA.AperFunction,EtchedComponent*% +%TA.AperFunction,BGAPad,CuDef*% +%TA.AperFunction,TestPad*% +%TA.AperFunction,Profile*% +%TA.AperFunction,NonConductor*% +%TA.AperFunction*% diff --git a/test/assets/gerberx3/tokens/attribute/TA_DrillTolerance.grb b/test/assets/gerberx3/tokens/attribute/TA_DrillTolerance.grb new file mode 100644 index 00000000..4fe870d9 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TA_DrillTolerance.grb @@ -0,0 +1,2 @@ +%TA.DrillTolerance,0.02,0.01*% +%TA.DrillTolerance,0.15,0.15*% diff --git a/test/assets/gerberx3/tokens/attribute/TA_FlashText.grb b/test/assets/gerberx3/tokens/attribute/TA_FlashText.grb new file mode 100644 index 00000000..5fb93cc4 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TA_FlashText.grb @@ -0,0 +1,2 @@ +%TA.FlashText,L1,C,R,Courier,10,Layer number (L1 is top)*% +%TA.FlashText,XZ12ADF,B,,Code128,,Project identifier *% diff --git a/test/assets/gerberx3/tokens/attribute/TA_custom.grb b/test/assets/gerberx3/tokens/attribute/TA_custom.grb index e2e46cc2..2795ec4d 100644 --- a/test/assets/gerberx3/tokens/attribute/TA_custom.grb +++ b/test/assets/gerberx3/tokens/attribute/TA_custom.grb @@ -1,2 +1,3 @@ +%TAMyApertureAttributeWithValue,value,value2*% %TAMyApertureAttributeWithValue,value*% %TAMyApertureAttributeWithoutValue*% From 3021de140f188ffdacf46b5205170e2c874f2f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 26 Jul 2024 20:49:40 +0200 Subject: [PATCH 19/91] Add TD extended command support --- src/pygerber/gerberx3/ast/nodes/attribute/TD.py | 6 +++++- src/pygerber/gerberx3/parser/pyparsing/grammar.py | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TD.py b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py index d936554b..1c0d7a99 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TD.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py @@ -2,7 +2,9 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node @@ -13,6 +15,8 @@ class TD(Node): """Represents TD Gerber extended command.""" + name: Optional[str] = Field(default=None) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_td(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index f75a34c9..d09ba223 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -27,6 +27,7 @@ TA_FlashText, TA_UserName, ) +from pygerber.gerberx3.ast.nodes.attribute.TD import TD from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 @@ -447,6 +448,7 @@ def attribute(self) -> pp.ParserElement: return pp.MatchFirst( [ self.ta(), + self.td(), ] ) @@ -545,6 +547,19 @@ def _ta_flash_text(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(TA_FlashText)) ) + def td(self) -> pp.ParserElement: + """Create a parser element capable of parsing TD attributes.""" + return ( + self.extended_command_open + + ( + (pp.Literal("TD") + pp.Opt(self.string.set_results_name("name"))) + .set_name("TD") + .set_parse_action(self.make_unpack_callback(TD)) + ) + + self.command_end + + self.extended_command_close + ) + # ██████ █████ ███████ ██████ ███████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██ ██ ██ ██ █████ ███████ From e336a7ac7d9987e52d73bd6bc5ece9f107c02b85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 27 Jul 2024 01:22:08 +0200 Subject: [PATCH 20/91] Add file attribute parsing logic --- .../gerberx3/ast/nodes/attribute/TA.py | 7 +- .../gerberx3/ast/nodes/attribute/TF.py | 156 +++++++++- .../gerberx3/ast/nodes/d_codes/D01.py | 4 +- src/pygerber/gerberx3/ast/visitor.py | 42 +++ .../gerberx3/parser/pyparsing/grammar.py | 286 +++++++++++++----- .../gerberx3/parser/pyparsing/parser.py | 4 +- .../tokens/attribute/TA_FlashText.grb | 1 + .../tokens/attribute/TF_CreationDate.grb | 3 + .../attribute/{TF.grb => TF_FileFunction.grb} | 25 +- .../tokens/attribute/TF_FilePolarity.grb | 2 + .../attribute/TF_GenerationSoftware.grb | 5 + .../gerberx3/tokens/attribute/TF_MD5.grb | 1 + .../gerberx3/tokens/attribute/TF_Part.grb | 5 + .../tokens/attribute/TF_ProjectId.grb | 6 + .../tokens/attribute/TF_SameCoordinates.grb | 5 + test/gerberx3/test_ast/__init__.py | 0 test/gerberx3/test_ast/test_nodes/__init__.py | 0 .../test_nodes/test_attribute/__init__.py | 0 .../test_nodes/test_attribute/test_fs.py | 24 ++ 19 files changed, 477 insertions(+), 99 deletions(-) create mode 100644 test/assets/gerberx3/tokens/attribute/TF_CreationDate.grb rename test/assets/gerberx3/tokens/attribute/{TF.grb => TF_FileFunction.grb} (55%) create mode 100644 test/assets/gerberx3/tokens/attribute/TF_FilePolarity.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TF_MD5.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TF_Part.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TF_ProjectId.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TF_SameCoordinates.grb create mode 100644 test/gerberx3/test_ast/__init__.py create mode 100644 test/gerberx3/test_ast/test_nodes/__init__.py create mode 100644 test/gerberx3/test_ast/test_nodes/test_attribute/__init__.py create mode 100644 test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py index b082aa1e..786fb327 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py @@ -69,6 +69,11 @@ class AperFunction(Enum): NonMaterial = "NonMaterial" Other = "Other" + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + __str__ = __repr__ + class TA_AperFunction(TA): # noqa: N801 """Represents TA .AperFunction Gerber attribute.""" @@ -99,7 +104,7 @@ class TA_FlashText(TA): # noqa: N801 mode: Literal["B", "C"] mirroring: Literal["R", "M"] = Field(default="R") font: Optional[str] = Field(default=None) - size: Optional[float] = Field(default=None) + size: Optional[str] = Field(default=None) comments: List[str] = Field(default_factory=list) def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py index 1b7b9790..d3e04889 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -2,7 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import datetime # noqa: TCH003 +import hashlib +from abc import abstractmethod +from enum import Enum +from typing import TYPE_CHECKING, List, Literal, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node @@ -13,6 +19,152 @@ class TF(Node): """Represents TF Gerber extended command.""" + @abstractmethod + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + + +class Part(Enum): + """Enumerate supported part types.""" + + Single = "Single" + Array = "Array" + FabricationPanel = "FabricationPanel" + Coupon = "Coupon" + Other = "Other" + + +class TF_Part(TF): # noqa: N801 + """Represents TF Gerber extended command with part attribute.""" + + part: Part + fields: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_part(self) + + +class FileFunction(Enum): + """Enumerate supported file function types.""" + + Copper = "Copper" + Plated = "Plated" + NonPlated = "NonPlated" + Profile = "Profile" + Soldermask = "Soldermask" + Legend = "Legend" + Component = "Component" + Paste = "Paste" + Glue = "Glue" + Carbonmask = "Carbonmask" + Goldmask = "Goldmask" + Heatsinkmask = "Heatsinkmask" + Peelablemask = "Peelablemask" + Silvermask = "Silvermask" + Tinmask = "Tinmask" + Depthrout = "Depthrout" + Vcut = "Vcut" + Viafill = "Viafill" + Pads = "Pads" + Other = "Other" + Drillmap = "Drillmap" + FabricationDrawing = "FabricationDrawing" + Vcutmap = "Vcutmap" + AssemblyDrawing = "AssemblyDrawing" + ArrayDrawing = "ArrayDrawing" + OtherDrawing = "OtherDrawing" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + __str__ = __repr__ + + +class TF_FileFunction(TF): # noqa: N801 + """Represents TF Gerber extended command with file function attribute.""" + + file_function: FileFunction + fields: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_file_function(self) + + +class TF_FilePolarity(TF): # noqa: N801 + """Represents TF Gerber extended command with file polarity attribute.""" + + polarity: Literal["Positive", "Negative"] + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_file_polarity(self) + + +class TF_SameCoordinates(TF): # noqa: N801 + """Represents TF Gerber extended command with same coordinates attribute.""" + + identifier: Optional[str] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_same_coordinates(self) + + +class TF_CreationDate(TF): # noqa: N801 + """Represents TF Gerber extended command with creation date attribute.""" + + creation_date: datetime.datetime + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_creation_date(self) + + +class TF_GenerationSoftware(TF): # noqa: N801 + """Represents TF Gerber extended command with generation software attribute.""" + + vendor: Optional[str] = Field(default=None) + application: Optional[str] = Field(default=None) + version: Optional[str] = Field(default=None) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" - visitor.on_tf(self) + visitor.on_tf_generation_software(self) + + +class TF_ProjectId(TF): # noqa: N801 + """Represents TF Gerber extended command with project id attribute.""" + + name: Optional[str] = Field(default=None) + guid: Optional[str] = Field(default=None) + revision: Optional[str] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_project_id(self) + + +MD5_LENGTH_HEX = 32 + + +class TF_MD5(TF): # noqa: N801 + """Represents TF Gerber extended command with MD5 attribute.""" + + md5: str = Field(min_length=MD5_LENGTH_HEX, max_length=MD5_LENGTH_HEX) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_md5(self) + + def check_source_hash(self) -> bool: + """Validate MD5 attribute.""" + source = ( + self.source[: self.location - 1] + .replace("\n", "") + .replace("\r", "") + .encode("utf-8") + ) + source_hash = hashlib.md5(source).hexdigest() # noqa: S324 + return source_hash == self.md5 diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py index adc7de25..0865c638 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -16,8 +16,8 @@ class D01(Node): """Represents D01 Gerber command.""" - x: Coordinate - y: Coordinate + x: Optional[Coordinate] = Field(default=None) + y: Optional[Coordinate] = Field(default=None) i: Optional[Coordinate] = Field(default=None) j: Optional[Coordinate] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index cee95d3a..c5e7a3c3 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -10,6 +10,16 @@ TA_FlashText, TA_UserName, ) +from pygerber.gerberx3.ast.nodes.attribute.TF import ( + TF_MD5, + TF_CreationDate, + TF_FileFunction, + TF_FilePolarity, + TF_GenerationSoftware, + TF_Part, + TF_ProjectId, + TF_SameCoordinates, +) if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose @@ -164,6 +174,38 @@ def on_td(self, node: TD) -> None: def on_tf(self, node: TF) -> None: """Handle `TF` node.""" + def on_tf_part(self, node: TF_Part) -> None: + """Handle `TF` node.""" + self.on_tf(node) + + def on_tf_file_function(self, node: TF_FileFunction) -> None: + """Handle `TF` node.""" + self.on_tf(node) + + def on_tf_file_polarity(self, node: TF_FilePolarity) -> None: + """Handle `TF` node.""" + self.on_tf(node) + + def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: + """Handle `TF` node.""" + self.on_tf(node) + + def on_tf_creation_date(self, node: TF_CreationDate) -> None: + """Handle `TF` node.""" + self.on_tf(node) + + def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: + """Handle `TF` node.""" + self.on_tf(node) + + def on_tf_project_id(self, node: TF_ProjectId) -> None: + """Handle `TF` node.""" + self.on_tf(node) + + def on_tf_md5(self, node: TF_MD5) -> None: + """Handle `TF` node.""" + self.on_tf(node) + def on_to(self, node: TO) -> None: """Handle `TO` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index d09ba223..d490636c 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -28,6 +28,16 @@ TA_UserName, ) from pygerber.gerberx3.ast.nodes.attribute.TD import TD +from pygerber.gerberx3.ast.nodes.attribute.TF import ( + TF_MD5, + TF_CreationDate, + TF_FileFunction, + TF_FilePolarity, + TF_GenerationSoftware, + TF_Part, + TF_ProjectId, + TF_SameCoordinates, +) from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 @@ -361,7 +371,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: self.aperture_identifier + pp.Literal("C,") + self.double.set_results_name("diameter") - + pp.Opt(self.x + self.double.set_results_name("hole_diameter")) + + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) ) .set_name("ADC") .set_parse_action(_) @@ -382,9 +392,9 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: self.aperture_identifier + pp.Literal(f"{symbol},") + self.double.set_results_name("width") - + self.x + + self._x + self.double.set_results_name("height") - + pp.Opt(self.x + self.double.set_results_name("hole_diameter")) + + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) ) .set_name("ADC") .set_parse_action(_) @@ -403,10 +413,10 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: self.aperture_identifier + pp.Literal("P,") + self.double.set_results_name("outer_diameter") - + self.x + + self._x + self.double.set_results_name("vertices") - + pp.Opt(self.x + self.double.set_results_name("rotation")) - + pp.Opt(self.x + self.double.set_results_name("hole_diameter")) + + pp.Opt(self._x + self.double.set_results_name("rotation")) + + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) ) .set_name("ADC") .set_parse_action(_) @@ -426,15 +436,14 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: ( self.aperture_identifier + self.name.set_results_name("name") - + pp.Opt(self.comma + param + pp.ZeroOrMore(self.x + param)) + + pp.Opt(self.comma + param + pp.ZeroOrMore(self._x + param)) ) .set_name("ADC") .set_parse_action(_) ) @pp.cached_property - def x(self) -> pp.ParserElement: - """Create a parser element capable of parsing literal X.""" + def _x(self) -> pp.ParserElement: return pp.Suppress(pp.Literal("X")).set_name("X") # █████ ████████ ████████ ██████ ██ ██████ ██ ██ ████████ ███████ @@ -449,6 +458,7 @@ def attribute(self) -> pp.ParserElement: [ self.ta(), self.td(), + self.tf(), ] ) @@ -472,14 +482,14 @@ def ta(self) -> pp.ParserElement: def _ta_user_name(self) -> pp.ParserElement: return ( ( - pp.Literal("TA") + self._ta + self.user_name + pp.ZeroOrMore( self.comma + self.field.set_results_name("fields", list_all_matches=True) ) ) - .set_name("TA-with-user-name") + .set_name("TA") .set_parse_action(self.make_unpack_callback(TA_UserName)) ) @@ -487,7 +497,7 @@ def _ta_user_name(self) -> pp.ParserElement: def _ta_aper_function(self) -> pp.ParserElement: return ( ( - pp.Literal("TA") + self._ta + pp.Literal(".AperFunction") + pp.Optional( self.comma @@ -508,7 +518,7 @@ def _ta_aper_function(self) -> pp.ParserElement: def _ta_drill_tolerance(self) -> pp.ParserElement: return ( ( - pp.Literal("TA") + self._ta + pp.Literal(".DrillTolerance") + pp.Optional( self.comma @@ -526,18 +536,18 @@ def _ta_drill_tolerance(self) -> pp.ParserElement: def _ta_flash_text(self) -> pp.ParserElement: return ( ( - pp.Literal("TA") + self._ta + pp.Literal(".FlashText") + self.comma + self.field.set_results_name("string") + self.comma + pp.one_of(list("BC")).set_results_name("mode") + self.comma - + pp.one_of(list("RM")).set_results_name("mirroring") + + pp.Opt(pp.one_of(list("RM")).set_results_name("mirroring")) + self.comma - + self.field.set_results_name("font") + + pp.Opt(self.field.set_results_name("font")) + self.comma - + self.field.set_results_name("size") + + pp.Opt(self.field.set_results_name("size")) + pp.ZeroOrMore( self.comma + self.field.set_results_name("comments", list_all_matches=True) @@ -547,6 +557,10 @@ def _ta_flash_text(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(TA_FlashText)) ) + @pp.cached_property + def _ta(self) -> pp.ParserElement: + return pp.Literal("TA") + def td(self) -> pp.ParserElement: """Create a parser element capable of parsing TD attributes.""" return ( @@ -560,6 +574,171 @@ def td(self) -> pp.ParserElement: + self.extended_command_close ) + def tf(self) -> pp.ParserElement: + """Create a parser element capable of parsing TF attributes.""" + return ( + self.extended_command_open + + pp.MatchFirst( + [ + self._tf_user_name, + self._tf_part, + self._tf_file_function, + self._tf_file_polarity, + self._tf_same_coordinates, + self._tf_creation_date, + self._tf_generation_software, + self._tf_project_id, + self._tf_md5, + ] + ) + + self.command_end + + self.extended_command_close + ) + + @pp.cached_property + def _tf_user_name(self) -> pp.ParserElement: + return ( + ( + self._tf + + self.user_name + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("fields", list_all_matches=True) + ) + ) + .set_name("TF") + .set_parse_action(self.make_unpack_callback(TA_UserName)) + ) + + @pp.cached_property + def _tf_part(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".Part") + + self.comma + + self.field.set_results_name("part") + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("fields", list_all_matches=True) + ) + ) + .set_name("TF.Part") + .set_parse_action(self.make_unpack_callback(TF_Part)) + ) + + @pp.cached_property + def _tf_file_function(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".FileFunction") + + self.comma + + self.field.set_results_name("file_function") + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("fields", list_all_matches=True) + ) + ) + .set_name("TF.FileFunction") + .set_parse_action(self.make_unpack_callback(TF_FileFunction)) + ) + + @pp.cached_property + def _tf_file_polarity(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".FilePolarity") + + self.comma + + self.field.set_results_name("polarity") + ) + .set_name("TF.FilePolarity") + .set_parse_action(self.make_unpack_callback(TF_FilePolarity)) + ) + + @pp.cached_property + def _tf_same_coordinates(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".SameCoordinates") + + pp.Opt(self.comma + self.field.set_results_name("identifier")) + ) + .set_name("TF.SameCoordinates") + .set_parse_action(self.make_unpack_callback(TF_SameCoordinates)) + ) + + @pp.cached_property + def _tf_creation_date(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".CreationDate") + + self.comma + + self.field.set_results_name("creation_date") + ) + .set_name("TF.CreationDate") + .set_parse_action(self.make_unpack_callback(TF_CreationDate)) + ) + + @pp.cached_property + def _tf_generation_software(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".GenerationSoftware") + + pp.Opt( + self.comma + + self.field.set_results_name("vendor") + + pp.Opt( + self.comma + + self.field.set_results_name("application") + + pp.Opt(self.comma + self.field.set_results_name("version")) + ) + ) + ) + .set_name("TF.GenerationSoftware") + .set_parse_action(self.make_unpack_callback(TF_GenerationSoftware)) + ) + + @pp.cached_property + def _tf_project_id(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".ProjectId") + + pp.Opt( + self.comma + + self.field.set_results_name("name") + + pp.Opt( + self.comma + + self.field.set_results_name("guid") + + pp.Opt(self.comma + self.field.set_results_name("revision")) + ) + ) + ) + .set_name("TF.ProjectId") + .set_parse_action(self.make_unpack_callback(TF_ProjectId)) + ) + + @pp.cached_property + def _tf_md5(self) -> pp.ParserElement: + return ( + ( + self._tf + + pp.CaselessLiteral(".MD5") + + self.comma + + self.field.set_results_name("md5") + ) + .set_name("TF.MD5") + .set_parse_action(self.make_unpack_callback(TF_MD5)) + ) + + @pp.cached_property + def _tf(self) -> pp.ParserElement: + return pp.Literal("TF") + # ██████ █████ ███████ ██████ ███████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██ ██ ██ ██ █████ ███████ @@ -568,85 +747,52 @@ def td(self) -> pp.ParserElement: def d_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing D-codes.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> D01: - try: - return self.get_cls(D01)( - source=s, - location=loc, - **tokens.as_dict(), - ) - except ValidationError as e: - raise pp.ParseFatalException(s, loc, "Invalid D01") from e - d01 = ( ( - self.coordinate.set_results_name("x") - + self.coordinate.set_results_name("y") + pp.Opt(self.coordinate.set_results_name("x")) + + pp.Opt(self.coordinate.set_results_name("y")) + pp.Opt(self.coordinate.set_results_name("i")) + pp.Opt(self.coordinate.set_results_name("j")) + pp.Regex(r"D0*1") ) - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(D01)) .set_name("D01") ) - def _(s: str, loc: int, tokens: pp.ParseResults) -> D02: - try: - return self.get_cls(D02)( - source=s, - location=loc, - **tokens.as_dict(), - ) - except ValidationError as e: - raise pp.ParseFatalException(s, loc, "Invalid D02") from e - d02 = ( ( self.coordinate.set_results_name("x") + self.coordinate.set_results_name("y") + pp.Regex(r"D0*2") ) - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(D02)) .set_name("D02") ) - def _(s: str, loc: int, tokens: pp.ParseResults) -> D03: - try: - return self.get_cls(D03)( - source=s, - location=loc, - **tokens.as_dict(), - ) - except ValidationError as e: - raise pp.ParseFatalException(s, loc, "Invalid D03") from e - d03 = ( ( pp.Opt(self.coordinate.set_results_name("x")) + pp.Opt(self.coordinate.set_results_name("y")) + pp.Regex(r"D0*3") ) - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(D03)) .set_name("D03") ) - def _(s: str, loc: int, tokens: pp.ParseResults) -> Dnn: - parse_result_token_list = tokens.as_list() - token_list = cast(list[Coordinate], parse_result_token_list) - value = token_list[0] - assert isinstance(value, str) # type: ignore[unreachable] - assert value.startswith("D") # type: ignore[unreachable] - assert len(value) > 1 - - try: - return self.get_cls(Dnn)(source=s, location=loc, value=value) - except ValidationError as e: - raise pp.ParseFatalException(s, loc, "Invalid Dnn") from e - - dnn = pp.Regex(r"D0*[0-9]*").set_parse_action(_).set_name("Dnn") + dnn = ( + self.aperture_identifier.set_results_name("value") + .set_parse_action(self.make_unpack_callback(Dnn)) + .set_name("Dnn") + ) - return pp.MatchFirst([d01, d02, d03, dnn]) + return pp.MatchFirst( + [ + dnn, + d01, + d02, + d03, + ] + ) # ██████ █████ ███████ ██████ ███████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ @@ -720,9 +866,9 @@ def m_codes(self) -> pp.ParserElement: self.get_cls(cls), # type: ignore[arg-type, type-abstract] ) for (value, cls) in ( - (0, M00), - (1, M01), (2, M02), + (1, M01), + (0, M00), ) ] ) diff --git a/src/pygerber/gerberx3/parser/pyparsing/parser.py b/src/pygerber/gerberx3/parser/pyparsing/parser.py index 431c3b63..f3244c12 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/parser.py +++ b/src/pygerber/gerberx3/parser/pyparsing/parser.py @@ -22,8 +22,8 @@ def __init__( else: self.grammar = Grammar.DEFAULT - def parse(self, code: str) -> File: + def parse(self, code: str, *, strict: bool = True) -> File: """Parse the input.""" - parse_result = self.grammar.parseString(code).get("root_node") + parse_result = self.grammar.parseString(code, parse_all=strict).get("root_node") assert isinstance(parse_result, File) return parse_result diff --git a/test/assets/gerberx3/tokens/attribute/TA_FlashText.grb b/test/assets/gerberx3/tokens/attribute/TA_FlashText.grb index 5fb93cc4..cd2bb956 100644 --- a/test/assets/gerberx3/tokens/attribute/TA_FlashText.grb +++ b/test/assets/gerberx3/tokens/attribute/TA_FlashText.grb @@ -1,2 +1,3 @@ %TA.FlashText,L1,C,R,Courier,10,Layer number (L1 is top)*% %TA.FlashText,XZ12ADF,B,,Code128,,Project identifier *% +%TA.FlashText,XZ12ADF,B,,,*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_CreationDate.grb b/test/assets/gerberx3/tokens/attribute/TF_CreationDate.grb new file mode 100644 index 00000000..46c9b8a6 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_CreationDate.grb @@ -0,0 +1,3 @@ +%TF.CreationDate,2015-02-23T15:59:51+01:00*% +%TF.CreationDate,2023-07-12T11:50:11+01:00*% +%TF.CreationDate,2024-07-05T18:49:25+02:00*% diff --git a/test/assets/gerberx3/tokens/attribute/TF.grb b/test/assets/gerberx3/tokens/attribute/TF_FileFunction.grb similarity index 55% rename from test/assets/gerberx3/tokens/attribute/TF.grb rename to test/assets/gerberx3/tokens/attribute/TF_FileFunction.grb index 10c27a3c..066007ea 100644 --- a/test/assets/gerberx3/tokens/attribute/TF.grb +++ b/test/assets/gerberx3/tokens/attribute/TF_FileFunction.grb @@ -1,11 +1,3 @@ -%TF.FileFunction,Soldermask,Top*% -%TF.Part,Other,example*% -%TF.FileFunction,Other,Sample*% -%TF.Part,Array*% -%TF.GenerationSoftware,Ucamco,UcamX,2016.04-160425*% -%TF.CreationDate,2016-04-25T00:00;00+01:00*% -%TF.Part,Other,Testfile*% -%TF.FileFunction,Copper,L1,Top*% %TF.FileFunction,Legend,Top*% %TF.FileFunction,Soldermask,Top*% @@ -19,20 +11,9 @@ %TF.FileFunction,Plated,1,4,PTH*% %TF.FileFunction,Profile,NP*% %TF.FileFunction,Drillmap*% - +%TF.FileFunction,Soldermask,Top*% +%TF.FileFunction,Other,Sample*% +%TF.FileFunction,Copper,L1,Top*% %TF.FileFunction,Copper,L2,Inr,Plane*% -%TF.FilePolarity,Positive*% - %TF.FileFunction,Soldermask,Top*% -%TF.FilePolarity,Negative*% - -%TF.SameCoordinates*% -%TF.SameCoordinates,f81d4fae-7dec-11d0-a765-00a0c91e6bf6*% - -%TF.ProjectId,My PCB,f81d4fae-7dec-11d0-a765-00a0c91e6bf6,2*% -%TF.ProjectId,project#8,68753a44-4D6F-1226-9C60-0050E4C00067,/main/18*% - -%TF.MD5,6ab9e892830469cdff7e3e346331d404*% - %TF.FileFunction,Plated,1,8,PTH*% -%TF.Part,Single*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_FilePolarity.grb b/test/assets/gerberx3/tokens/attribute/TF_FilePolarity.grb new file mode 100644 index 00000000..7ab296b1 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_FilePolarity.grb @@ -0,0 +1,2 @@ +%TF.FilePolarity,Positive*% +%TF.FilePolarity,Negative*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb b/test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb new file mode 100644 index 00000000..7781380d --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb @@ -0,0 +1,5 @@ +%TF.GenerationSoftware,KiCad,Pcbnew,7.0.6-7.0.6~ubuntu22.04.1*% +%TF.GenerationSoftware,KiCad,Pcbnew,5.1.5-52549c5~84~ubuntu18.04.1*% +%TF.GenerationSoftware,Altium Limited,Altium Designer,23.5.1 (21)*% +%TF.GenerationSoftware,Ucamco,UcamX,2016.04-160425*% +%TF.GenerationSoftware,Ucamco,UcamX,2017.04*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_MD5.grb b/test/assets/gerberx3/tokens/attribute/TF_MD5.grb new file mode 100644 index 00000000..7fb296a9 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_MD5.grb @@ -0,0 +1 @@ +%TF.MD5,6ab9e892830469cdff7e3e346331d404*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_Part.grb b/test/assets/gerberx3/tokens/attribute/TF_Part.grb new file mode 100644 index 00000000..63554d32 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_Part.grb @@ -0,0 +1,5 @@ + +%TF.Part,Single*% +%TF.Part,Other,example*% +%TF.Part,Array*% +%TF.Part,Other,Testfile*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_ProjectId.grb b/test/assets/gerberx3/tokens/attribute/TF_ProjectId.grb new file mode 100644 index 00000000..5d232c19 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_ProjectId.grb @@ -0,0 +1,6 @@ +%TF.ProjectId,My PCB,f81d4fae-7dec-11d0-a765-00a0c91e6bf6,2*% +%TF.ProjectId,project#8,68753a44-4D6F-1226-9C60-0050E4C00067,/main/18*% +%TF.ProjectId,A64-OlinuXino_Rev_G,4136342d-4f6c-4696-9e75-58696e6f5f52,G*% +%TF.ProjectId,main,6d61696e-2e6b-4696-9361-645f70636258,rev?*% +%TF.ProjectId,test_gerbers,74657374-5f67-4657-9262-6572732e6b69,rev?*% +%TF.ProjectId,simple_2layer,73696d70-6c65-45f3-926c-617965722e6b,rev?*% diff --git a/test/assets/gerberx3/tokens/attribute/TF_SameCoordinates.grb b/test/assets/gerberx3/tokens/attribute/TF_SameCoordinates.grb new file mode 100644 index 00000000..07c5f7da --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TF_SameCoordinates.grb @@ -0,0 +1,5 @@ +%TF.SameCoordinates*% +%TF.SameCoordinates,f81d4fae-7dec-11d0-a765-00a0c91e6bf6*% +%TF.SameCoordinates,Original*% +%TF.SameCoordinates,4F0F0AB4-5C0D-4210-9CCF-4C3744BDD4F0*% +%TF.SameCoordinates,CA9C4AC4-C4BE-41B9-9754-440A126A42FF*% diff --git a/test/gerberx3/test_ast/__init__.py b/test/gerberx3/test_ast/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/gerberx3/test_ast/test_nodes/__init__.py b/test/gerberx3/test_ast/test_nodes/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/gerberx3/test_ast/test_nodes/test_attribute/__init__.py b/test/gerberx3/test_ast/test_nodes/test_attribute/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py b/test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py new file mode 100644 index 00000000..8bb70708 --- /dev/null +++ b/test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py @@ -0,0 +1,24 @@ +from __future__ import annotations + +from pathlib import Path + +from pygerber.gerberx3.ast.nodes.attribute.TF import TF_MD5 +from pygerber.gerberx3.ast.visitor import AstVisitor +from pygerber.gerberx3.parser.pyparsing.parser import Parser + + +class TestFS_MD4: # noqa: N801 + def test_check_source_hash(self) -> None: + """Test check_source_hash.""" + source = Path( + "test/assets/gerberx3/AltiumGerberX2/PCB1_Profile.gbr" + ).read_text() + parser = Parser() + + output = parser.parse(source, strict=True) + + class CheckMD5(AstVisitor): + def on_tf_md5(self, node: TF_MD5) -> None: + assert node.check_source_hash() is True + + CheckMD5().on_file(output) From a3a1151e3656f98ee62037a529e8ce20d95c49b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 28 Jul 2024 16:38:58 +0200 Subject: [PATCH 21/91] Add TO attribute nodes implementation --- .../gerberx3/ast/nodes/attribute/TF.py | 11 + .../gerberx3/ast/nodes/attribute/TO.py | 180 +++++++++++- src/pygerber/gerberx3/ast/visitor.py | 111 ++++++- .../gerberx3/parser/pyparsing/grammar.py | 277 +++++++++++++++++- test/assets/gerberx3/tokens/attribute/TO.grb | 9 - .../assets/gerberx3/tokens/attribute/TO_C.grb | 6 + .../gerberx3/tokens/attribute/TO_CLbD.grb | 1 + .../gerberx3/tokens/attribute/TO_CLbN.grb | 16 + .../gerberx3/tokens/attribute/TO_CMPN.grb | 1 + .../gerberx3/tokens/attribute/TO_CMfr.grb | 1 + .../gerberx3/tokens/attribute/TO_CMnt.grb | 4 + .../gerberx3/tokens/attribute/TO_CPgD.grb | 1 + .../gerberx3/tokens/attribute/TO_CPgN.grb | 1 + .../gerberx3/tokens/attribute/TO_CRot.grb | 3 + .../gerberx3/tokens/attribute/TO_CSup.grb | 1 + .../gerberx3/tokens/attribute/TO_CVal.grb | 4 + .../assets/gerberx3/tokens/attribute/TO_N.grb | 8 + .../assets/gerberx3/tokens/attribute/TO_P.grb | 13 + .../gerberx3/tokens/attribute/TO_custom.grb | 1 + 19 files changed, 626 insertions(+), 23 deletions(-) delete mode 100644 test/assets/gerberx3/tokens/attribute/TO.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_C.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CLbD.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CLbN.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CMPN.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CMfr.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CMnt.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CPgD.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CPgN.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CRot.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CSup.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_CVal.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_N.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_P.grb create mode 100644 test/assets/gerberx3/tokens/attribute/TO_custom.grb diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py index d3e04889..8154eff8 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -24,6 +24,17 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" +class TF_UserName(TF): # noqa: N801 + """Represents TF Gerber extended command with user name.""" + + user_name: str + fields: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_tf_user_name(self) + + class Part(Enum): """Enumerate supported part types.""" diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py index 0a168319..645cee6f 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py @@ -2,7 +2,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from enum import Enum +from typing import TYPE_CHECKING, List, Optional + +from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node @@ -16,3 +19,178 @@ class TO(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to(self) + + +class TO_UserName(TO): # noqa: N801 + """Represents TO Gerber extended command with user name.""" + + user_name: str + fields: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_user_name(self) + + +class TO_N(TO): # noqa: N801 + """Represents TO Gerber extended command with .N attribute.""" + + net_names: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_n(self) + + +class TO_P(TO): # noqa: N801 + """Represents TO Gerber extended command with .P attribute.""" + + refdes: str + number: str + function: Optional[str] = Field(default=None) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_p(self) + + +class TO_C(TO): # noqa: N801 + """Represents TO Gerber extended command with .C attribute.""" + + refdes: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_c(self) + + +class TO_CRot(TO): # noqa: N801 + """Represents TO Gerber extended command with .CRot attribute.""" + + angle: float + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_crot(self) + + +class TO_CMfr(TO): # noqa: N801 + """Represents TO Gerber extended command with .CMfr attribute.""" + + manufacturer: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_cmfr(self) + + +class TO_CMNP(TO): # noqa: N801 + """Represents TO Gerber extended command with .CMNP attribute.""" + + part_number: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_cmnp(self) + + +class TO_CVal(TO): # noqa: N801 + """Represents TO Gerber extended command with .CVal attribute.""" + + value: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_cval(self) + + +class Mount(Enum): + """Mount type enumeration.""" + + SMD = "SMD" + TH = "TH" + Pressfit = "Pressfit" + Other = "Other" + + +class TO_CMnt(TO): # noqa: N801 + """Represents TO Gerber extended command with .CMnt attribute.""" + + mount: Mount + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_cmnt(self) + + +class TO_CFtp(TO): # noqa: N801 + """Represents TO Gerber extended command with .CFtp attribute.""" + + footprint: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_cftp(self) + + +class TO_CPgN(TO): # noqa: N801 + """Represents TO Gerber extended command with .CPgN attribute.""" + + name: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_cpgn(self) + + +class TO_CPgD(TO): # noqa: N801 + """Represents TO Gerber extended command with .CPgD attribute.""" + + description: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_cpgd(self) + + +class TO_CHgt(TO): # noqa: N801 + """Represents TO Gerber extended command with .CHgt attribute.""" + + height: float + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_chgt(self) + + +class TO_CLbN(TO): # noqa: N801 + """Represents TO Gerber extended command with .CLbN attribute.""" + + name: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_clbn(self) + + +class TO_CLbD(TO): # noqa: N801 + """Represents TO Gerber extended command with .CLbD attribute.""" + + description: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_clbd(self) + + +class TO_CSup(TO): # noqa: N801 + """Represents TO Gerber extended command with .CSup attribute.""" + + supplier: str + supplier_part: str + + other_suppliers: List[str] = Field(default_factory=list) + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_to_csup(self) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index c5e7a3c3..b7f2432d 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -19,6 +19,25 @@ TF_Part, TF_ProjectId, TF_SameCoordinates, + TF_UserName, +) +from pygerber.gerberx3.ast.nodes.attribute.TO import ( + TO_C, + TO_CMNP, + TO_N, + TO_P, + TO_CFtp, + TO_CHgt, + TO_CLbD, + TO_CLbN, + TO_CMfr, + TO_CMnt, + TO_CPgD, + TO_CPgN, + TO_CRot, + TO_CSup, + TO_CVal, + TO_UserName, ) if TYPE_CHECKING: @@ -153,19 +172,19 @@ def on_ta(self, node: TA) -> None: """Handle `TA` node.""" def on_ta_user_name(self, node: TA_UserName) -> None: - """Handle `TA` node.""" + """Handle `TA_UserName` node.""" self.on_ta(node) def on_ta_aper_function(self, node: TA_AperFunction) -> None: - """Handle `TA` node.""" + """Handle `TA_AperFunction` node.""" self.on_ta(node) def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: - """Handle `TA` node.""" + """Handle `TA_DrillTolerance` node.""" self.on_ta(node) def on_ta_flash_text(self, node: TA_FlashText) -> None: - """Handle `TA` node.""" + """Handle `TA_FlashText` node.""" self.on_ta(node) def on_td(self, node: TD) -> None: @@ -174,41 +193,109 @@ def on_td(self, node: TD) -> None: def on_tf(self, node: TF) -> None: """Handle `TF` node.""" + def on_tf_user_name(self, node: TF_UserName) -> None: + """Handle `TF_UserName` node.""" + self.on_tf(node) + def on_tf_part(self, node: TF_Part) -> None: - """Handle `TF` node.""" + """Handle `TF_Part` node.""" self.on_tf(node) def on_tf_file_function(self, node: TF_FileFunction) -> None: - """Handle `TF` node.""" + """Handle `TF_FileFunction` node.""" self.on_tf(node) def on_tf_file_polarity(self, node: TF_FilePolarity) -> None: - """Handle `TF` node.""" + """Handle `TF_FilePolarity` node.""" self.on_tf(node) def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: - """Handle `TF` node.""" + """Handle `TF_SameCoordinates` node.""" self.on_tf(node) def on_tf_creation_date(self, node: TF_CreationDate) -> None: - """Handle `TF` node.""" + """Handle `TF_CreationDate` node.""" self.on_tf(node) def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: - """Handle `TF` node.""" + """Handle `TF_GenerationSoftware` node.""" self.on_tf(node) def on_tf_project_id(self, node: TF_ProjectId) -> None: - """Handle `TF` node.""" + """Handle `TF_ProjectId` node.""" self.on_tf(node) def on_tf_md5(self, node: TF_MD5) -> None: - """Handle `TF` node.""" + """Handle `TF_MD5` node.""" self.on_tf(node) def on_to(self, node: TO) -> None: """Handle `TO` node.""" + def on_to_user_name(self, node: TO_UserName) -> None: + """Handle `TO_UserName` node.""" + self.on_to(node) + + def on_to_n(self, node: TO_N) -> None: + """Handle `TO_N` node.""" + self.on_to(node) + + def on_to_p(self, node: TO_P) -> None: + """Handle `TO_P` node`.""" + self.on_to(node) + + def on_to_c(self, node: TO_C) -> None: + """Handle `TO_C` node.""" + self.on_to(node) + + def on_to_crot(self, node: TO_CRot) -> None: + """Handle `TO_CRot` node.""" + self.on_to(node) + + def on_to_cmfr(self, node: TO_CMfr) -> None: + """Handle `TO_CMfr` node.""" + self.on_to(node) + + def on_to_cmnp(self, node: TO_CMNP) -> None: + """Handle `TO_CMNP` node.""" + self.on_to(node) + + def on_to_cval(self, node: TO_CVal) -> None: + """Handle `TO_CVal` node.""" + self.on_to(node) + + def on_to_cmnt(self, node: TO_CMnt) -> None: + """Handle `TO_CVal` node.""" + self.on_to(node) + + def on_to_cftp(self, node: TO_CFtp) -> None: + """Handle `TO_Cftp` node.""" + self.on_to(node) + + def on_to_cpgn(self, node: TO_CPgN) -> None: + """Handle `TO_CPgN` node.""" + self.on_to(node) + + def on_to_cpgd(self, node: TO_CPgD) -> None: + """Handle `TO_CPgD` node.""" + self.on_to(node) + + def on_to_chgt(self, node: TO_CHgt) -> None: + """Handle `TO_CHgt` node.""" + self.on_to(node) + + def on_to_clbn(self, node: TO_CLbN) -> None: + """Handle `TO_CLbN` node.""" + self.on_to(node) + + def on_to_clbd(self, node: TO_CLbD) -> None: + """Handle `TO_CLbD` node.""" + self.on_to(node) + + def on_to_csup(self, node: TO_CSup) -> None: + """Handle `TO_CSup` node.""" + self.on_to(node) + # D codes def on_d01(self, node: D01) -> None: diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index d490636c..07f73775 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -37,6 +37,25 @@ TF_Part, TF_ProjectId, TF_SameCoordinates, + TF_UserName, +) +from pygerber.gerberx3.ast.nodes.attribute.TO import ( + TO_C, + TO_CMNP, + TO_N, + TO_P, + TO_CFtp, + TO_CHgt, + TO_CLbD, + TO_CLbN, + TO_CMfr, + TO_CMnt, + TO_CPgD, + TO_CPgN, + TO_CRot, + TO_CSup, + TO_CVal, + TO_UserName, ) from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 @@ -459,6 +478,7 @@ def attribute(self) -> pp.ParserElement: self.ta(), self.td(), self.tf(), + self.to(), ] ) @@ -607,7 +627,7 @@ def _tf_user_name(self) -> pp.ParserElement: ) ) .set_name("TF") - .set_parse_action(self.make_unpack_callback(TA_UserName)) + .set_parse_action(self.make_unpack_callback(TF_UserName)) ) @pp.cached_property @@ -739,6 +759,261 @@ def _tf_md5(self) -> pp.ParserElement: def _tf(self) -> pp.ParserElement: return pp.Literal("TF") + def to(self) -> pp.ParserElement: + """Create a parser element capable of parsing TO attributes.""" + return ( + self.extended_command_open + + pp.MatchFirst( + [ + self._to_user_name, + self._to_n, + self._to_p, + self._to_c, + self._to_crot, + self._to_cmfr, + self._to_cmpn, + self._to_cval, + self._to_cmnt, + self._to_cftp, + self._to_cpgn, + self._to_cpgd, + self._to_chgt, + self._to_clbn, + self._to_clbd, + self._to_csup, + ] + ) + + self.command_end + + self.extended_command_close + ) + + @pp.cached_property + def _to_user_name(self) -> pp.ParserElement: + return ( + ( + self._to + + self.user_name + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("fields", list_all_matches=True) + ) + ) + .set_name("TO") + .set_parse_action(self.make_unpack_callback(TO_UserName)) + ) + + @pp.cached_property + def _to_n(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".N") + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name("net_names", list_all_matches=True) + ) + ) + .set_name("TO.N") + .set_parse_action(self.make_unpack_callback(TO_N)) + ) + + @pp.cached_property + def _to_p(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".P") + + self.comma + + self.field.set_results_name("refdes") + + self.comma + + self.field.set_results_name("number") + + pp.Opt(self.comma + self.field.set_results_name("function")) + ) + .set_name("TO.P") + .set_parse_action(self.make_unpack_callback(TO_P)) + ) + + @pp.cached_property + def _to_c(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".C") + + self.comma + + self.field.set_results_name("refdes") + ) + .set_name("TO.C") + .set_parse_action(self.make_unpack_callback(TO_C)) + ) + + @pp.cached_property + def _to_crot(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CRot") + + self.comma + + self.field.set_results_name("angle") + ) + .set_name("TO.CRot") + .set_parse_action(self.make_unpack_callback(TO_CRot)) + ) + + @pp.cached_property + def _to_cmfr(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CMfr") + + self.comma + + self.field.set_results_name("manufacturer") + ) + .set_name("TO.CMfr") + .set_parse_action(self.make_unpack_callback(TO_CMfr)) + ) + + @pp.cached_property + def _to_cmpn(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CMPN") + + self.comma + + self.field.set_results_name("part_number") + ) + .set_name("TO.CMPN") + .set_parse_action(self.make_unpack_callback(TO_CMNP)) + ) + + @pp.cached_property + def _to_cval(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CVal") + + self.comma + + self.field.set_results_name("value") + ) + .set_name("TO.CVal") + .set_parse_action(self.make_unpack_callback(TO_CVal)) + ) + + @pp.cached_property + def _to_cmnt(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CMnt") + + self.comma + + self.field.set_results_name("mount") + ) + .set_name("TO.CMnt") + .set_parse_action(self.make_unpack_callback(TO_CMnt)) + ) + + @pp.cached_property + def _to_cftp(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CFtp") + + self.comma + + self.field.set_results_name("footprint") + ) + .set_name("TO.CFtp") + .set_parse_action(self.make_unpack_callback(TO_CFtp)) + ) + + @pp.cached_property + def _to_cpgn(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CPgN") + + self.comma + + self.field.set_results_name("name") + ) + .set_name("TO.CPgN") + .set_parse_action(self.make_unpack_callback(TO_CPgN)) + ) + + @pp.cached_property + def _to_cpgd(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CPgD") + + self.comma + + self.field.set_results_name("description") + ) + .set_name("TO.CPgD") + .set_parse_action(self.make_unpack_callback(TO_CPgD)) + ) + + @pp.cached_property + def _to_chgt(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CHgt") + + self.comma + + self.field.set_results_name("height") + ) + .set_name("TO.CHgt") + .set_parse_action(self.make_unpack_callback(TO_CHgt)) + ) + + @pp.cached_property + def _to_clbn(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CLbn") + + self.comma + + self.field.set_results_name("name") + ) + .set_name("TO.CLbn") + .set_parse_action(self.make_unpack_callback(TO_CLbN)) + ) + + @pp.cached_property + def _to_clbd(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CLbD") + + self.comma + + self.field.set_results_name("description") + ) + .set_name("TO.CLbD") + .set_parse_action(self.make_unpack_callback(TO_CLbD)) + ) + + @pp.cached_property + def _to_csup(self) -> pp.ParserElement: + return ( + ( + self._to + + pp.CaselessLiteral(".CSup") + + self.comma + + self.field.set_results_name("supplier") + + self.comma + + self.field.set_results_name("supplier_part") + + pp.ZeroOrMore( + self.comma + + self.field.set_results_name( + "other_suppliers", list_all_matches=True + ) + ) + ) + .set_name("TO.CSup") + .set_parse_action(self.make_unpack_callback(TO_CSup)) + ) + + @pp.cached_property + def _to(self) -> pp.ParserElement: + return pp.Literal("TO") + # ██████ █████ ███████ ██████ ███████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██ ██ ██ ██ █████ ███████ diff --git a/test/assets/gerberx3/tokens/attribute/TO.grb b/test/assets/gerberx3/tokens/attribute/TO.grb deleted file mode 100644 index d6b762b5..00000000 --- a/test/assets/gerberx3/tokens/attribute/TO.grb +++ /dev/null @@ -1,9 +0,0 @@ -%TO.C,R6*% -%TO.P,IC12,2,TRIG*% -%TO.P,IC12,1,GND*% -%TO.P,R5,1*% -%TO.P,R5,2*% -%TO.C,R2*% -%TO.P,U1,4*% -%TO.N,Clk3*% -%TO.C,R301*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_C.grb b/test/assets/gerberx3/tokens/attribute/TO_C.grb new file mode 100644 index 00000000..32ebd2b5 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_C.grb @@ -0,0 +1,6 @@ +%TO.C,U1*% +%TO.C,J1*% +%TO.C,L2*% +%TO.C,R301*% +%TO.C,R2*% +%TO.C,R6*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CLbD.grb b/test/assets/gerberx3/tokens/attribute/TO_CLbD.grb new file mode 100644 index 00000000..42c74ccb --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CLbD.grb @@ -0,0 +1 @@ +%TO.CLbD,foobar*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CLbN.grb b/test/assets/gerberx3/tokens/attribute/TO_CLbN.grb new file mode 100644 index 00000000..79d99982 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CLbN.grb @@ -0,0 +1,16 @@ +%TO.CLbN,Package_DIP*% +%TO.CLbN,Connector_PinHeader_2.54mm*% +%TO.CLbN,Button_Switch_THT*% +%TO.CLbN,Resistor_THT*% +%TO.CLbN,Resistor_THT*% +%TO.CLbN,Capacitor_THT*% +%TO.CLbN,Resistor_SMD*% +%TO.CLbN,Capacitor_SMD*% +%TO.CLbN,digikey-footprints*% +%TO.CLbN,Capacitor_SMD*% +%TO.CLbN,Capacitor_SMD*% +%TO.CLbN,TestPoint*% +%TO.CLbN,"Own_Footprint"*% +%TO.CLbN,"Own_Footprint"*% +%TO.CLbN,"Own_Footprint"*% +%TO.CLbN,"footprint"*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CMPN.grb b/test/assets/gerberx3/tokens/attribute/TO_CMPN.grb new file mode 100644 index 00000000..5ecc73ea --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CMPN.grb @@ -0,0 +1 @@ +%TO.CMPN,value*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CMfr.grb b/test/assets/gerberx3/tokens/attribute/TO_CMfr.grb new file mode 100644 index 00000000..b57df894 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CMfr.grb @@ -0,0 +1 @@ +%TO.CMfr,MANU1*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CMnt.grb b/test/assets/gerberx3/tokens/attribute/TO_CMnt.grb new file mode 100644 index 00000000..0d66a954 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CMnt.grb @@ -0,0 +1,4 @@ +%TO.CMnt,TH*% +%TO.CMnt,SMD*% +%TO.CMnt,Pressfit*% +%TO.CMnt,Other*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CPgD.grb b/test/assets/gerberx3/tokens/attribute/TO_CPgD.grb new file mode 100644 index 00000000..1bbd1db6 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CPgD.grb @@ -0,0 +1 @@ +%TO.CPgD,Value*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CPgN.grb b/test/assets/gerberx3/tokens/attribute/TO_CPgN.grb new file mode 100644 index 00000000..59bd4228 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CPgN.grb @@ -0,0 +1 @@ +%TO.CPgN,SOIC-16-N*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CRot.grb b/test/assets/gerberx3/tokens/attribute/TO_CRot.grb new file mode 100644 index 00000000..9cef034c --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CRot.grb @@ -0,0 +1,3 @@ +%TO.CRot,-90*% +%TO.CRot,180*% +%TO.CRot,0*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CSup.grb b/test/assets/gerberx3/tokens/attribute/TO_CSup.grb new file mode 100644 index 00000000..168310e1 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CSup.grb @@ -0,0 +1 @@ +%TO.CSup,Digikey,12345, Mouser,67890*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_CVal.grb b/test/assets/gerberx3/tokens/attribute/TO_CVal.grb new file mode 100644 index 00000000..aafdee74 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_CVal.grb @@ -0,0 +1,4 @@ +%TO.CVal,10pF*% +%TO.CVal,NX3225GD-8MHZ-STD-CRA-3*% +%TO.CVal,"Conn_01x06_Male"*% +%TO.CVal,"1k"*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_N.grb b/test/assets/gerberx3/tokens/attribute/TO_N.grb new file mode 100644 index 00000000..7cca24b8 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_N.grb @@ -0,0 +1,8 @@ +%TO.N,Clk3*% +%TO.N,unconnected-(J2-TX2--PadB3)*% +%TO.N,unconnected-(J2-SBU2-PadB8)*% +%TO.N,/Shield*% +%TO.N,/D2-*% +%TO.N,GND*% +%TO.N,/USB&HDMI\002CWiFi&BT\002CEthernet\002CLCD/MDI[3]+*% +%TO.N,/NAND Flash \002C eMMC\002C T-Card and Audio/NAND0-ALE\005CSDC2-DS*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_P.grb b/test/assets/gerberx3/tokens/attribute/TO_P.grb new file mode 100644 index 00000000..478ea059 --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_P.grb @@ -0,0 +1,13 @@ +%TO.P,IC12,1,GND*% +%TO.P,IC12,2,TRIG*% +%TO.P,R5,1*% +%TO.P,R5,2*% +%TO.P,J2,S1,SHIELD*% +%TO.P,UEXT1,1*% +%TO.P,Mounting_hole2,0*% +%TO.P,MICRO_SD1,11*% +%TO.P,3.3V/VCC-PE:2.8V1,1*% +%TO.P,HEADPHONES/LINEOUT1,4*% +%TO.P,J2,S1,SHIELD*% +%TO.P,J2,B9,VBUS*% +%TO.P,J1,10,Pin_10*% diff --git a/test/assets/gerberx3/tokens/attribute/TO_custom.grb b/test/assets/gerberx3/tokens/attribute/TO_custom.grb new file mode 100644 index 00000000..07eed79b --- /dev/null +++ b/test/assets/gerberx3/tokens/attribute/TO_custom.grb @@ -0,0 +1 @@ +%TOUserAttr,Value*% From 6ed6c38e5245771335956aecc7bf376bf6300452 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 28 Jul 2024 19:32:48 +0200 Subject: [PATCH 22/91] Redesign Coordinate representation --- .../gerberx3/ast/nodes/d_codes/D01.py | 15 ++- .../gerberx3/ast/nodes/d_codes/D02.py | 9 +- .../gerberx3/ast/nodes/d_codes/D03.py | 9 +- .../gerberx3/ast/nodes/other/coordinate.py | 43 +++++- .../gerberx3/ast/nodes/properties/FS.py | 9 +- src/pygerber/gerberx3/ast/visitor.py | 43 ++++++ .../gerberx3/parser/pyparsing/grammar.py | 126 +++++++++++------- 7 files changed, 187 insertions(+), 67 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py index 0865c638..d499dd9d 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -7,7 +7,12 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate +from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, +) if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,10 +21,10 @@ class D01(Node): """Represents D01 Gerber command.""" - x: Optional[Coordinate] = Field(default=None) - y: Optional[Coordinate] = Field(default=None) - i: Optional[Coordinate] = Field(default=None) - j: Optional[Coordinate] = Field(default=None) + x: Optional[CoordinateX] = Field(default=None) + y: Optional[CoordinateY] = Field(default=None) + i: Optional[CoordinateI] = Field(default=None) + j: Optional[CoordinateJ] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py index 1f887304..d77eef8f 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py @@ -5,7 +5,10 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate +from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateX, + CoordinateY, +) if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -14,8 +17,8 @@ class D02(Node): """Represents D02 Gerber command.""" - x: Coordinate - y: Coordinate + x: CoordinateX + y: CoordinateY def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py index 68b20b2c..ee03e604 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py @@ -7,7 +7,10 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate +from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateX, + CoordinateY, +) if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,8 +19,8 @@ class D03(Node): """Represents D03 Gerber command.""" - x: Optional[Coordinate] = Field(default=None) - y: Optional[Coordinate] = Field(default=None) + x: Optional[CoordinateX] = Field(default=None) + y: Optional[CoordinateY] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py index a14a44b6..dbf97973 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py +++ b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py @@ -4,7 +4,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Literal +from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node @@ -15,9 +15,46 @@ class Coordinate(Node): """Represents Coordinate node.""" - type: Literal["X", "Y", "I", "J"] + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_coordinate(self) + + +class CoordinateX(Coordinate): + """Represents X Coordinate node.""" + value: str def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" - visitor.on_coordinate(self) + visitor.on_coordinate_x(self) + + +class CoordinateY(Coordinate): + """Represents Y Coordinate node.""" + + value: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_coordinate_y(self) + + +class CoordinateI(Coordinate): + """Represents I Coordinate node.""" + + value: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_coordinate_i(self) + + +class CoordinateJ(Coordinate): + """Represents J Coordinate node.""" + + value: str + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_coordinate_j(self) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py index cedde215..2cc8ab39 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/FS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -5,7 +5,6 @@ from typing import TYPE_CHECKING from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate if TYPE_CHECKING: from pygerber.gerberx3.ast.visitor import AstVisitor @@ -14,11 +13,15 @@ class FS(Node): """Represents FS Gerber extended command.""" - x: Coordinate - y: Coordinate zeros: str coordinate_mode: str + x_integral: int + x_decimal: int + + y_integral: int + y_decimal: int + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_fs(self) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index b7f2432d..b1389710 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -39,6 +39,12 @@ TO_CVal, TO_UserName, ) +from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, +) if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose @@ -300,12 +306,33 @@ def on_to_csup(self, node: TO_CSup) -> None: def on_d01(self, node: D01) -> None: """Handle `D01` node.""" + if node.x: + node.x.visit(self) + + if node.y: + node.y.visit(self) + + if node.i: + node.i.visit(self) + + if node.j: + node.j.visit(self) def on_d02(self, node: D02) -> None: """Handle `D02` node.""" + if node.x: + node.x.visit(self) + + if node.y: + node.y.visit(self) def on_d03(self, node: D03) -> None: """Handle `D03` node.""" + if node.x: + node.x.visit(self) + + if node.y: + node.y.visit(self) def on_dnn(self, node: Dnn) -> None: """Handle `Dnn` node.""" @@ -429,6 +456,22 @@ def on_command_end(self, node: CommandEnd) -> None: def on_coordinate(self, node: Coordinate) -> None: """Handle `Coordinate` node.""" + def on_coordinate_x(self, node: CoordinateX) -> None: + """Handle `Coordinate` node.""" + self.on_coordinate(node) + + def on_coordinate_y(self, node: CoordinateY) -> None: + """Handle `Coordinate` node.""" + self.on_coordinate(node) + + def on_coordinate_i(self, node: CoordinateI) -> None: + """Handle `Coordinate` node.""" + self.on_coordinate(node) + + def on_coordinate_j(self, node: CoordinateJ) -> None: + """Handle `Coordinate` node.""" + self.on_coordinate(node) + def on_extended_command_close(self, node: ExtendedCommandClose) -> None: """Handle `ExtendedCommandClose` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 07f73775..f0ff5505 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -92,7 +92,12 @@ from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable from pygerber.gerberx3.ast.nodes.other.command_end import CommandEnd -from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate +from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, +) from pygerber.gerberx3.ast.nodes.other.extended_command_close import ( ExtendedCommandClose, ) @@ -1022,53 +1027,64 @@ def _to(self) -> pp.ParserElement: def d_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing D-codes.""" - d01 = ( + return pp.MatchFirst( + [ + self._dnn, + self._d01, + self._d02, + self._d03, + ] + ) + + @pp.cached_property + def _dnn(self) -> pp.ParserElement: + return ( + self.aperture_identifier.set_results_name("value") + .set_parse_action(self.make_unpack_callback(Dnn)) + .set_name("Dnn") + ) + + @pp.cached_property + def _d01(self) -> pp.ParserElement: + return ( ( - pp.Opt(self.coordinate.set_results_name("x")) - + pp.Opt(self.coordinate.set_results_name("y")) - + pp.Opt(self.coordinate.set_results_name("i")) - + pp.Opt(self.coordinate.set_results_name("j")) + pp.Opt(self._coordinate_x.set_results_name("x")) + + pp.Opt(self._coordinate_y.set_results_name("y")) + + pp.Opt(self._coordinate_i.set_results_name("i")) + + pp.Opt(self._coordinate_j.set_results_name("j")) + pp.Regex(r"D0*1") + + pp.Literal("*") ) .set_parse_action(self.make_unpack_callback(D01)) .set_name("D01") ) - d02 = ( + @pp.cached_property + def _d02(self) -> pp.ParserElement: + return ( ( - self.coordinate.set_results_name("x") - + self.coordinate.set_results_name("y") + self._coordinate_x.set_results_name("x") + + self._coordinate_y.set_results_name("y") + pp.Regex(r"D0*2") + + pp.Literal("*") ) .set_parse_action(self.make_unpack_callback(D02)) .set_name("D02") ) - d03 = ( + @pp.cached_property + def _d03(self) -> pp.ParserElement: + return ( ( - pp.Opt(self.coordinate.set_results_name("x")) - + pp.Opt(self.coordinate.set_results_name("y")) + pp.Opt(self._coordinate_x.set_results_name("x")) + + pp.Opt(self._coordinate_y.set_results_name("y")) + pp.Regex(r"D0*3") + + pp.Literal("*") ) .set_parse_action(self.make_unpack_callback(D03)) .set_name("D03") ) - dnn = ( - self.aperture_identifier.set_results_name("value") - .set_parse_action(self.make_unpack_callback(Dnn)) - .set_name("Dnn") - ) - - return pp.MatchFirst( - [ - dnn, - d01, - d02, - d03, - ] - ) - # ██████ █████ ███████ ██████ ███████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ # ██ ███ ██ ██ ██ ██ ██ █████ ███████ @@ -1294,29 +1310,35 @@ def command_end(self) -> pp.ParserElement: return parser.set_parse_action(self.make_unpack_callback(CommandEnd)) @pp.cached_property - def coordinate(self) -> pp.ParserElement: - """Create a parser element capable of parsing coordinates.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Coordinate: - type_, value = tokens.as_list() - assert isinstance(type_, str), type(type_) - assert type_ in ("X", "Y", "I", "J") - assert isinstance(value, str) - - return self.get_cls(Coordinate)( - source=s, - location=loc, - type=type_, # type: ignore[arg-type] - value=value, - ) + def _coordinate_x(self) -> pp.ParserElement: + return ( + (pp.CaselessLiteral("X") + self.integer.set_results_name("value")) + .set_parse_action(self.make_unpack_callback(CoordinateX)) + .set_name("coordinate.x") + ) + + @pp.cached_property + def _coordinate_y(self) -> pp.ParserElement: + return ( + (pp.CaselessLiteral("Y") + self.integer.set_results_name("value")) + .set_parse_action(self.make_unpack_callback(CoordinateY)) + .set_name("coordinate.y") + ) + @pp.cached_property + def _coordinate_i(self) -> pp.ParserElement: return ( - ( - pp.one_of(("X", "Y", "I", "J")).set_name("coordinate_type") - + self.integer.set_name("coordinate_value") - ) - .set_parse_action(_) - .set_name("coordinate") + (pp.CaselessLiteral("I") + self.integer.set_results_name("value")) + .set_parse_action(self.make_unpack_callback(CoordinateI)) + .set_name("coordinate.i") + ) + + @pp.cached_property + def _coordinate_j(self) -> pp.ParserElement: + return ( + (pp.CaselessLiteral("J") + self.integer.set_results_name("value")) + .set_parse_action(self.make_unpack_callback(CoordinateJ)) + .set_name("coordinate.j") ) @pp.cached_property @@ -1559,8 +1581,12 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> FS: pp.Literal("FS") + pp.one_of(("L", "T")).set_results_name("zeros") + pp.one_of(("I", "A")).set_results_name("coordinate_mode") - + self.coordinate.set_results_name("x") - + self.coordinate.set_results_name("y") + + pp.CaselessLiteral("X") + + pp.Regex(r"[0-9]").set_results_name("x_integral") + + pp.Regex(r"[0-9]").set_results_name("x_decimal") + + pp.CaselessLiteral("Y") + + pp.Regex(r"[0-9]").set_results_name("y_integral") + + pp.Regex(r"[0-9]").set_results_name("y_decimal") ) .set_parse_action(_) .set_name("FS") From 2e641aa6669779b1eff8624c6237d5949e8c789c Mon Sep 17 00:00:00 2001 From: Sander de Regt Date: Mon, 29 Jul 2024 21:27:23 +0200 Subject: [PATCH 23/91] Add implementation of IP node parsing --- .../gerberx3/ast/nodes/properties/IP.py | 2 ++ .../gerberx3/parser/pyparsing/grammar.py | 24 ++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IP.py b/src/pygerber/gerberx3/ast/nodes/properties/IP.py index 10a49d06..ebe1eb4f 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IP.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IP.py @@ -13,6 +13,8 @@ class IP(Node): """Represents IP Gerber extended command.""" + polarity: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ip(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index f0ff5505..49b7cc20 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -113,6 +113,7 @@ from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 from pygerber.gerberx3.ast.nodes.properties.FS import FS +from pygerber.gerberx3.ast.nodes.properties.IP import IP from pygerber.gerberx3.ast.nodes.properties.MO import MO T = TypeVar("T", bound=Node) @@ -1564,7 +1565,7 @@ def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: def properties(self) -> pp.ParserElement: """Create a parser element capable of parsing Properties-commands.""" - return pp.MatchFirst([self.fs(), self.mo()]) + return pp.MatchFirst([self.fs(), self.mo(), self.ip()]) def fs(self) -> pp.ParserElement: """Create a parser for the FS command.""" @@ -1594,6 +1595,27 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> FS: + self.extended_command_close ) + def ip(self) -> pp.ParserElement: + """Create a parser for the IP command.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> IP: + try: + return self.get_cls(IP)(source=s, location=loc, **tokens.as_dict()) + except ValidationError as e: + raise pp.ParseFatalException(s, loc, "Invalid IP") from e + + return ( + self.extended_command_open + + ( + pp.Literal("IP") + + pp.one_of(("POS", "NEG")).set_results_name("polarity") + ) + .set_parse_action(_) + .set_name("IP") + + self.command_end + + self.extended_command_close + ) + def mo(self) -> pp.ParserElement: """Create a parser for the MO command.""" return ( From 8892da46e8ee38d42fe2db948798eae9b774685d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 1 Aug 2024 01:22:37 +0200 Subject: [PATCH 24/91] Add test for AstVisitor base class --- .../gerberx3/ast/nodes/aperture/AB_close.py | 10 +- .../gerberx3/ast/nodes/aperture/AB_open.py | 10 +- .../gerberx3/ast/nodes/aperture/ADC.py | 10 +- .../gerberx3/ast/nodes/aperture/ADO.py | 10 +- .../gerberx3/ast/nodes/aperture/ADP.py | 10 +- .../gerberx3/ast/nodes/aperture/ADR.py | 10 +- .../gerberx3/ast/nodes/aperture/ADmacro.py | 10 +- .../gerberx3/ast/nodes/aperture/AM_close.py | 10 +- .../gerberx3/ast/nodes/aperture/AM_open.py | 10 +- .../gerberx3/ast/nodes/aperture/SR_close.py | 10 +- .../gerberx3/ast/nodes/aperture/SR_open.py | 10 +- .../gerberx3/ast/nodes/attribute/TA.py | 33 +- .../gerberx3/ast/nodes/attribute/TD.py | 10 +- .../gerberx3/ast/nodes/attribute/TF.py | 63 ++- .../gerberx3/ast/nodes/attribute/TO.py | 104 +++- src/pygerber/gerberx3/ast/nodes/base.py | 10 +- .../gerberx3/ast/nodes/d_codes/D01.py | 10 +- .../gerberx3/ast/nodes/d_codes/D02.py | 10 +- .../gerberx3/ast/nodes/d_codes/D03.py | 10 +- .../gerberx3/ast/nodes/d_codes/Dnn.py | 10 +- src/pygerber/gerberx3/ast/nodes/file.py | 12 +- .../gerberx3/ast/nodes/g_codes/G01.py | 10 +- .../gerberx3/ast/nodes/g_codes/G02.py | 10 +- .../gerberx3/ast/nodes/g_codes/G03.py | 10 +- .../gerberx3/ast/nodes/g_codes/G04.py | 10 +- .../gerberx3/ast/nodes/g_codes/G36.py | 10 +- .../gerberx3/ast/nodes/g_codes/G37.py | 10 +- .../gerberx3/ast/nodes/g_codes/G54.py | 10 +- .../gerberx3/ast/nodes/g_codes/G55.py | 10 +- .../gerberx3/ast/nodes/g_codes/G70.py | 10 +- .../gerberx3/ast/nodes/g_codes/G71.py | 10 +- .../gerberx3/ast/nodes/g_codes/G74.py | 10 +- .../gerberx3/ast/nodes/g_codes/G75.py | 10 +- .../gerberx3/ast/nodes/g_codes/G90.py | 10 +- .../gerberx3/ast/nodes/g_codes/G91.py | 10 +- src/pygerber/gerberx3/ast/nodes/load/LM.py | 10 +- src/pygerber/gerberx3/ast/nodes/load/LN.py | 10 +- src/pygerber/gerberx3/ast/nodes/load/LP.py | 10 +- src/pygerber/gerberx3/ast/nodes/load/LR.py | 10 +- src/pygerber/gerberx3/ast/nodes/load/LS.py | 10 +- .../gerberx3/ast/nodes/m_codes/M00.py | 10 +- .../gerberx3/ast/nodes/m_codes/M01.py | 10 +- .../gerberx3/ast/nodes/m_codes/M02.py | 10 +- .../gerberx3/ast/nodes/math/assignment.py | 10 +- .../gerberx3/ast/nodes/math/constant.py | 10 +- .../gerberx3/ast/nodes/math/expression.py | 10 +- .../ast/nodes/math/operators/binary/add.py | 10 +- .../ast/nodes/math/operators/binary/div.py | 10 +- .../ast/nodes/math/operators/binary/mul.py | 10 +- .../ast/nodes/math/operators/binary/sub.py | 10 +- .../ast/nodes/math/operators/unary/neg.py | 10 +- .../ast/nodes/math/operators/unary/pos.py | 10 +- src/pygerber/gerberx3/ast/nodes/math/point.py | 10 +- .../gerberx3/ast/nodes/math/variable.py | 10 +- .../gerberx3/ast/nodes/other/command_end.py | 10 +- .../gerberx3/ast/nodes/other/coordinate.py | 32 +- .../ast/nodes/other/extended_command_close.py | 10 +- .../ast/nodes/other/extended_command_open.py | 10 +- .../gerberx3/ast/nodes/primitives/code_0.py | 10 +- .../gerberx3/ast/nodes/primitives/code_1.py | 10 +- .../gerberx3/ast/nodes/primitives/code_2.py | 10 +- .../gerberx3/ast/nodes/primitives/code_20.py | 10 +- .../gerberx3/ast/nodes/primitives/code_21.py | 10 +- .../gerberx3/ast/nodes/primitives/code_22.py | 10 +- .../gerberx3/ast/nodes/primitives/code_4.py | 10 +- .../gerberx3/ast/nodes/primitives/code_5.py | 10 +- .../gerberx3/ast/nodes/primitives/code_6.py | 10 +- .../gerberx3/ast/nodes/primitives/code_7.py | 10 +- .../gerberx3/ast/nodes/properties/AS.py | 10 +- .../gerberx3/ast/nodes/properties/FS.py | 10 +- .../gerberx3/ast/nodes/properties/IN.py | 10 +- .../gerberx3/ast/nodes/properties/IP.py | 10 +- .../gerberx3/ast/nodes/properties/IR.py | 10 +- .../gerberx3/ast/nodes/properties/MI.py | 10 +- .../gerberx3/ast/nodes/properties/MO.py | 16 +- .../gerberx3/ast/nodes/properties/OF.py | 10 +- .../gerberx3/ast/nodes/properties/SF.py | 10 +- src/pygerber/gerberx3/ast/visitor.py | 2 +- .../gerberx3/parser/pyparsing/grammar.py | 2 +- test/gerberx3/test_ast/test_ast_visitor.py | 476 ++++++++++++++++++ 80 files changed, 1350 insertions(+), 100 deletions(-) create mode 100644 test/gerberx3/test_ast/test_ast_visitor.py diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py index 1e13f109..a3d8cdf6 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class ABclose(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ab_close(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ab_close diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py index 50f66a9c..e1b0cf89 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class ABopen(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ab_open(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ab_open diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py index ce7b182a..1b8365c6 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class ADC(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_adc(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_adc diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py index ab6b6e06..70f318c4 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -23,3 +25,9 @@ class ADO(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ado(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ado diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py index 4e38738e..08c29bbe 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -24,3 +26,9 @@ class ADP(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_adp(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_adp diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py index 88761f74..74b420cd 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -23,3 +25,9 @@ class ADR(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_adr(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_adr diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py index f8bd61d5..1459f12e 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Callable, List, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class ADmacro(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ad_macro(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ad_macro diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py index 47a8b597..a37bd89f 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class AMclose(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_am_close(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_am_close diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py index ff31a834..b7b96530 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class AMopen(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_am_open(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_am_open diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py index cb305ae2..51bbca86 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class SRclose(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_sr_close(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_sr_close diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py index 3dd981fc..eeb3ada2 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -23,3 +25,9 @@ class SRopen(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_sr_open(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_sr_open diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py index 786fb327..ee7b8253 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py @@ -2,25 +2,22 @@ from __future__ import annotations -from abc import abstractmethod from enum import Enum -from typing import TYPE_CHECKING, List, Literal, Optional +from typing import TYPE_CHECKING, Callable, List, Literal, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor class TA(Node): """Represents TA Gerber extended command.""" - @abstractmethod - def visit(self, visitor: AstVisitor) -> None: - """Handle visitor call.""" - class TA_UserName(TA): # noqa: N801 """Represents TA Gerber extended command with user name.""" @@ -32,6 +29,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_user_name(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ta_user_name + class AperFunction(Enum): """Enum representing possible AperFunction values.""" @@ -85,6 +88,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_aper_function(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ta_aper_function + class TA_DrillTolerance(TA): # noqa: N801 """Represents TA .DrillTolerance Gerber attribute.""" @@ -96,6 +105,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_drill_tolerance(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ta_drill_tolerance + class TA_FlashText(TA): # noqa: N801 """Represents TA .FlashText Gerber attribute.""" @@ -110,3 +125,9 @@ class TA_FlashText(TA): # noqa: N801 def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_flash_text(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ta_flash_text diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TD.py b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py index 1c0d7a99..60d53cbb 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TD.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -20,3 +22,9 @@ class TD(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_td(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_td diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py index 8154eff8..ee020a8e 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -4,25 +4,22 @@ import datetime # noqa: TCH003 import hashlib -from abc import abstractmethod from enum import Enum -from typing import TYPE_CHECKING, List, Literal, Optional +from typing import TYPE_CHECKING, Callable, List, Literal, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor class TF(Node): """Represents TF Gerber extended command.""" - @abstractmethod - def visit(self, visitor: AstVisitor) -> None: - """Handle visitor call.""" - class TF_UserName(TF): # noqa: N801 """Represents TF Gerber extended command with user name.""" @@ -34,6 +31,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_user_name(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_user_name + class Part(Enum): """Enumerate supported part types.""" @@ -55,6 +58,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_part(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_part + class FileFunction(Enum): """Enumerate supported file function types.""" @@ -102,6 +111,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_file_function(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_file_function + class TF_FilePolarity(TF): # noqa: N801 """Represents TF Gerber extended command with file polarity attribute.""" @@ -112,6 +127,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_file_polarity(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_file_polarity + class TF_SameCoordinates(TF): # noqa: N801 """Represents TF Gerber extended command with same coordinates attribute.""" @@ -122,6 +143,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_same_coordinates(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_same_coordinates + class TF_CreationDate(TF): # noqa: N801 """Represents TF Gerber extended command with creation date attribute.""" @@ -132,6 +159,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_creation_date(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_creation_date + class TF_GenerationSoftware(TF): # noqa: N801 """Represents TF Gerber extended command with generation software attribute.""" @@ -144,6 +177,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_generation_software(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_generation_software + class TF_ProjectId(TF): # noqa: N801 """Represents TF Gerber extended command with project id attribute.""" @@ -156,6 +195,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_project_id(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_project_id + MD5_LENGTH_HEX = 32 @@ -179,3 +224,9 @@ def check_source_hash(self) -> bool: ) source_hash = hashlib.md5(source).hexdigest() # noqa: S324 return source_hash == self.md5 + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_tf_md5 diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py index 645cee6f..a6ba05cc 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py @@ -3,23 +3,21 @@ from __future__ import annotations from enum import Enum -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING, Callable, List, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor class TO(Node): """Represents TO Gerber extended command.""" - def visit(self, visitor: AstVisitor) -> None: - """Handle visitor call.""" - visitor.on_to(self) - class TO_UserName(TO): # noqa: N801 """Represents TO Gerber extended command with user name.""" @@ -31,6 +29,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_user_name(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_user_name + class TO_N(TO): # noqa: N801 """Represents TO Gerber extended command with .N attribute.""" @@ -41,6 +45,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_n(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_n + class TO_P(TO): # noqa: N801 """Represents TO Gerber extended command with .P attribute.""" @@ -53,6 +63,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_p(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_p + class TO_C(TO): # noqa: N801 """Represents TO Gerber extended command with .C attribute.""" @@ -63,6 +79,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_c(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_c + class TO_CRot(TO): # noqa: N801 """Represents TO Gerber extended command with .CRot attribute.""" @@ -73,6 +95,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_crot(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_crot + class TO_CMfr(TO): # noqa: N801 """Represents TO Gerber extended command with .CMfr attribute.""" @@ -83,6 +111,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cmfr(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_cmfr + class TO_CMNP(TO): # noqa: N801 """Represents TO Gerber extended command with .CMNP attribute.""" @@ -93,6 +127,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cmnp(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_cmnp + class TO_CVal(TO): # noqa: N801 """Represents TO Gerber extended command with .CVal attribute.""" @@ -103,6 +143,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cval(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_cval + class Mount(Enum): """Mount type enumeration.""" @@ -122,6 +168,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cmnt(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_cmnt + class TO_CFtp(TO): # noqa: N801 """Represents TO Gerber extended command with .CFtp attribute.""" @@ -132,6 +184,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cftp(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_cftp + class TO_CPgN(TO): # noqa: N801 """Represents TO Gerber extended command with .CPgN attribute.""" @@ -142,6 +200,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cpgn(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_cpgn + class TO_CPgD(TO): # noqa: N801 """Represents TO Gerber extended command with .CPgD attribute.""" @@ -152,6 +216,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cpgd(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_cpgd + class TO_CHgt(TO): # noqa: N801 """Represents TO Gerber extended command with .CHgt attribute.""" @@ -162,6 +232,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_chgt(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_chgt + class TO_CLbN(TO): # noqa: N801 """Represents TO Gerber extended command with .CLbN attribute.""" @@ -172,6 +248,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_clbn(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_clbn + class TO_CLbD(TO): # noqa: N801 """Represents TO Gerber extended command with .CLbD attribute.""" @@ -182,6 +264,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_clbd(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_clbd + class TO_CSup(TO): # noqa: N801 """Represents TO Gerber extended command with .CSup attribute.""" @@ -194,3 +282,9 @@ class TO_CSup(TO): # noqa: N801 def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_csup(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_to_csup diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py index 07db1e7a..ee9547c4 100644 --- a/src/pygerber/gerberx3/ast/nodes/base.py +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -3,13 +3,15 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pydantic import Field from pygerber.gerberx3.ast.nodes.model import ModelType if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class Node(ModelType): @abstractmethod def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" + + @abstractmethod + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py index d499dd9d..6de7c76d 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field @@ -15,6 +15,8 @@ ) if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -29,3 +31,9 @@ class D01(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_d01(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_d01 diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py index d77eef8f..34c5007d 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.other.coordinate import ( @@ -11,6 +11,8 @@ ) if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -23,3 +25,9 @@ class D02(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_d02(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_d02 diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py index ee03e604..2b6a621f 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field @@ -13,6 +13,8 @@ ) if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -25,3 +27,9 @@ class D03(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_d03(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_d03 diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py index 8b0153a0..24409833 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class Dnn(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_dnn(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_dnn diff --git a/src/pygerber/gerberx3/ast/nodes/file.py b/src/pygerber/gerberx3/ast/nodes/file.py index 67dcd112..fc68d2ca 100644 --- a/src/pygerber/gerberx3/ast/nodes/file.py +++ b/src/pygerber/gerberx3/ast/nodes/file.py @@ -2,19 +2,27 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor class File(Node): """AST node representing Gerber file.""" - commands: list[Node] + nodes: list[Node] def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_file(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_file diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py index f6987fbb..ce77f2f3 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G01(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g01(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g01 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py index 2c94b526..ede70972 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G02(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g02(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g02 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py index 57c8a49d..1f2fc629 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G03(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g03(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g03 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py index 50fda352..27169be7 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -20,3 +22,9 @@ class G04(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g04(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g04 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py index 454cf125..30c72344 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G36(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g36(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g36 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py index fcfea78c..472a6b94 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G37(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g37(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g37 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py index 9ad02660..a1b06346 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G54(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g54(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g54 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py index bb36c199..76f0ab76 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G55(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g55(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g55 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py index 7164b820..b08f05d1 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G70(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g70(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g70 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py index fd8cdb15..72adc8d4 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G71(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g71(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g71 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py index 451bb210..1dc50cf1 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G74(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g74(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g74 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py index 2517566a..50ae8bdb 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G75(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g75(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g75 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py index 51bc8a04..6c6b255c 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G90(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g90(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g90 diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py index 911ae9eb..cd7911df 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class G91(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g91(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_g91 diff --git a/src/pygerber/gerberx3/ast/nodes/load/LM.py b/src/pygerber/gerberx3/ast/nodes/load/LM.py index b2fb5ed2..59795d2f 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LM.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LM.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class LM(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_lm(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_lm diff --git a/src/pygerber/gerberx3/ast/nodes/load/LN.py b/src/pygerber/gerberx3/ast/nodes/load/LN.py index d59f07f6..e6ba4088 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LN.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LN.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class LN(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ln(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ln diff --git a/src/pygerber/gerberx3/ast/nodes/load/LP.py b/src/pygerber/gerberx3/ast/nodes/load/LP.py index c7431733..6984ec29 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LP.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LP.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class LP(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_lp(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_lp diff --git a/src/pygerber/gerberx3/ast/nodes/load/LR.py b/src/pygerber/gerberx3/ast/nodes/load/LR.py index e25f361e..fa840ac9 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LR.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LR.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class LR(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_lr(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_lr diff --git a/src/pygerber/gerberx3/ast/nodes/load/LS.py b/src/pygerber/gerberx3/ast/nodes/load/LS.py index c197d415..2b765267 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LS.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LS.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class LS(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ls(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ls diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py index e5b39974..5db53937 100644 --- a/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class M00(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_m00(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_m00 diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py index baeba584..331aa9b0 100644 --- a/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class M01(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_m01(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_m01 diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py index 8d299961..cb6b6a21 100644 --- a/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class M02(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_m02(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_m02 diff --git a/src/pygerber/gerberx3/ast/nodes/math/assignment.py b/src/pygerber/gerberx3/ast/nodes/math/assignment.py index 058f6881..bd63c736 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/assignment.py +++ b/src/pygerber/gerberx3/ast/nodes/math/assignment.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression from pygerber.gerberx3.ast.nodes.math.variable import Variable if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -21,3 +23,9 @@ class Assignment(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_assignment(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_assignment diff --git a/src/pygerber/gerberx3/ast/nodes/math/constant.py b/src/pygerber/gerberx3/ast/nodes/math/constant.py index 2d91f090..faae9c8c 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/constant.py +++ b/src/pygerber/gerberx3/ast/nodes/math/constant.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class Constant(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_constant(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_constant diff --git a/src/pygerber/gerberx3/ast/nodes/math/expression.py b/src/pygerber/gerberx3/ast/nodes/math/expression.py index 0fdc429e..e9b3ed5c 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/expression.py +++ b/src/pygerber/gerberx3/ast/nodes/math/expression.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class Expression(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_expression(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_expression diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py index fb4b4316..3c0e6220 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py @@ -4,13 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Callable, List from pydantic import Field from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class Add(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_add(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_add diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py index fafa2567..151f8267 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py @@ -4,13 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Callable, List from pydantic import Field from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class Div(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_div(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_div diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py index ff785e4f..fbfabc38 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py @@ -4,13 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Callable, List from pydantic import Field from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class Mul(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_mul(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_mul diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py index ca2625f3..05a334bb 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py @@ -4,13 +4,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Callable, List from pydantic import Field from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class Sub(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_sub(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_sub diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py index c4a8b036..31a717ae 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -20,3 +22,9 @@ class Neg(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_neg(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_neg diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py index c2938a06..cfd2e9a1 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -20,3 +22,9 @@ class Pos(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_pos(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_pos diff --git a/src/pygerber/gerberx3/ast/nodes/math/point.py b/src/pygerber/gerberx3/ast/nodes/math/point.py index 55214ee2..3433675c 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/point.py +++ b/src/pygerber/gerberx3/ast/nodes/math/point.py @@ -4,12 +4,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -22,3 +24,9 @@ class Point(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_point(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_point diff --git a/src/pygerber/gerberx3/ast/nodes/math/variable.py b/src/pygerber/gerberx3/ast/nodes/math/variable.py index 7965799a..27e1d723 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/variable.py +++ b/src/pygerber/gerberx3/ast/nodes/math/variable.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class Variable(Expression): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_variable(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_variable diff --git a/src/pygerber/gerberx3/ast/nodes/other/command_end.py b/src/pygerber/gerberx3/ast/nodes/other/command_end.py index 2e74c77c..70b11e24 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/command_end.py +++ b/src/pygerber/gerberx3/ast/nodes/other/command_end.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class CommandEnd(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_command_end(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_command_end diff --git a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py index dbf97973..57e88ede 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py +++ b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py @@ -4,21 +4,19 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor class Coordinate(Node): """Represents Coordinate node.""" - def visit(self, visitor: AstVisitor) -> None: - """Handle visitor call.""" - visitor.on_coordinate(self) - class CoordinateX(Coordinate): """Represents X Coordinate node.""" @@ -29,6 +27,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_coordinate_x(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_coordinate_x + class CoordinateY(Coordinate): """Represents Y Coordinate node.""" @@ -39,6 +43,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_coordinate_y(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_coordinate_y + class CoordinateI(Coordinate): """Represents I Coordinate node.""" @@ -49,6 +59,12 @@ def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_coordinate_i(self) + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_coordinate_i + class CoordinateJ(Coordinate): """Represents J Coordinate node.""" @@ -58,3 +74,9 @@ class CoordinateJ(Coordinate): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_coordinate_j(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_coordinate_j diff --git a/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py b/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py index 63977b5a..bb1a9829 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py +++ b/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class ExtendedCommandClose(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_extended_command_close(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_extended_command_close diff --git a/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py b/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py index 61ac0d2e..88c31a1a 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py +++ b/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py @@ -4,11 +4,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class ExtendedCommandOpen(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_extended_command_open(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_extended_command_open diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py index 9f6031f1..53df3952 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class Code0(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_0(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_0 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py index a2cd74c4..e19e19d6 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Optional +from typing import TYPE_CHECKING, Callable, Optional from pydantic import Field @@ -10,6 +10,8 @@ from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -25,3 +27,9 @@ class Code1(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_1(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_1 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py index 2670c01a..66b23720 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -25,3 +27,9 @@ class Code2(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_2(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_2 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py index e396854d..7f55950c 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -25,3 +27,9 @@ class Code20(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_20(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_20 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py index ae7c0968..0cc1c178 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -24,3 +26,9 @@ class Code21(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_21(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_21 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py index 5e7d095e..cf29180c 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -24,3 +26,9 @@ class Code22(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_22(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_22 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py index 2e2deb54..a0b1ea92 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py @@ -2,13 +2,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List +from typing import TYPE_CHECKING, Callable, List from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression from pygerber.gerberx3.ast.nodes.math.point import Point if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -25,3 +27,9 @@ class Code4(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_4(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_4 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py index a3c9ad0f..f4769a3a 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -24,3 +26,9 @@ class Code5(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_5(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_5 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py index b44e92db..f762d135 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -27,3 +29,9 @@ class Code6(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_6(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_6 diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py index e77a4073..46e684e1 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py @@ -2,12 +2,14 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.math.expression import Expression if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -24,3 +26,9 @@ class Code7(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_code_7(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_code_7 diff --git a/src/pygerber/gerberx3/ast/nodes/properties/AS.py b/src/pygerber/gerberx3/ast/nodes/properties/AS.py index b479dfae..58dd946c 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/AS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/AS.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class AS(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_as(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_as diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py index 2cc8ab39..ec4d1247 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/FS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -25,3 +27,9 @@ class FS(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_fs(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_fs diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IN.py b/src/pygerber/gerberx3/ast/nodes/properties/IN.py index 64054936..e3af38b1 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IN.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IN.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class IN(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_in(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_in diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IP.py b/src/pygerber/gerberx3/ast/nodes/properties/IP.py index ebe1eb4f..80a22f7d 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IP.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IP.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -18,3 +20,9 @@ class IP(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ip(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ip diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IR.py b/src/pygerber/gerberx3/ast/nodes/properties/IR.py index 2ae54353..1b867744 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IR.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IR.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class IR(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ir(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ir diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MI.py b/src/pygerber/gerberx3/ast/nodes/properties/MI.py index fa837bef..f868ae72 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/MI.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/MI.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class MI(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_mi(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_mi diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MO.py b/src/pygerber/gerberx3/ast/nodes/properties/MO.py index 21a8c188..7fad7d87 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/MO.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/MO.py @@ -3,19 +3,23 @@ from __future__ import annotations from enum import Enum -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor class UnitMode(Enum): """Unit mode enumeration.""" - INCH = "IN" - MILLIMETER = "MM" + IMPERIAL = "IN" + """Imperial unit mode. In this mode inches are used to express lengths.""" + METRIC = "MM" + """Metric unit mode. In this mode millimeters are used to express lengths.""" class MO(Node): @@ -26,3 +30,9 @@ class MO(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_mo(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_mo diff --git a/src/pygerber/gerberx3/ast/nodes/properties/OF.py b/src/pygerber/gerberx3/ast/nodes/properties/OF.py index 93310a2f..18cbc48c 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/OF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/OF.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class OF(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_of(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_of diff --git a/src/pygerber/gerberx3/ast/nodes/properties/SF.py b/src/pygerber/gerberx3/ast/nodes/properties/SF.py index 3125d816..9f4987af 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/SF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/SF.py @@ -2,11 +2,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: + from typing_extensions import Self + from pygerber.gerberx3.ast.visitor import AstVisitor @@ -16,3 +18,9 @@ class SF(Node): def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_sf(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_sf diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index b1389710..f5f80863 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -543,5 +543,5 @@ def on_sf(self, node: SF) -> None: def on_file(self, node: File) -> None: """Handle `File` node.""" - for command in node.commands: + for command in node.nodes: command.visit(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 49b7cc20..750fa769 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -149,7 +149,7 @@ def build(self) -> pp.ParserElement: """Build the grammar.""" def _(s: str, loc: int, tokens: pp.ParseResults) -> File: - return self.get_cls(File)(source=s, location=loc, commands=tokens.as_list()) + return self.get_cls(File)(source=s, location=loc, nodes=tokens.as_list()) root = ( pp.OneOrMore( diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py new file mode 100644 index 00000000..af16deca --- /dev/null +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -0,0 +1,476 @@ +from __future__ import annotations + +import datetime +from typing import Dict, Type +from unittest import mock + +import pytest +import tzlocal + +from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose +from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen +from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC +from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro +from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO +from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP +from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR +from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose +from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen +from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose +from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen +from pygerber.gerberx3.ast.nodes.attribute.TA import ( + AperFunction, + TA_AperFunction, + TA_DrillTolerance, + TA_FlashText, + TA_UserName, +) +from pygerber.gerberx3.ast.nodes.attribute.TD import TD +from pygerber.gerberx3.ast.nodes.attribute.TF import ( + TF_MD5, + FileFunction, + Part, + TF_CreationDate, + TF_FileFunction, + TF_FilePolarity, + TF_GenerationSoftware, + TF_Part, + TF_ProjectId, + TF_SameCoordinates, + TF_UserName, +) +from pygerber.gerberx3.ast.nodes.attribute.TO import ( + TO_C, + TO_CMNP, + TO_N, + TO_P, + Mount, + TO_CFtp, + TO_CHgt, + TO_CLbD, + TO_CLbN, + TO_CMfr, + TO_CMnt, + TO_CPgD, + TO_CPgN, + TO_CRot, + TO_CSup, + TO_CVal, + TO_UserName, +) +from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 +from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 +from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 +from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn +from pygerber.gerberx3.ast.nodes.file import File +from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 +from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 +from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 +from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 +from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 +from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 +from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 +from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 +from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 +from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 +from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 +from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 +from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 +from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 +from pygerber.gerberx3.ast.nodes.load.LM import LM +from pygerber.gerberx3.ast.nodes.load.LN import LN +from pygerber.gerberx3.ast.nodes.load.LP import LP +from pygerber.gerberx3.ast.nodes.load.LR import LR +from pygerber.gerberx3.ast.nodes.load.LS import LS +from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 +from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 +from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 +from pygerber.gerberx3.ast.nodes.math.assignment import Assignment +from pygerber.gerberx3.ast.nodes.math.constant import Constant +from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add +from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div +from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul +from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub +from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg +from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos +from pygerber.gerberx3.ast.nodes.math.point import Point +from pygerber.gerberx3.ast.nodes.math.variable import Variable +from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, +) +from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 +from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 +from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 +from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 +from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 +from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 +from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 +from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 +from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 +from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 +from pygerber.gerberx3.ast.nodes.properties.AS import AS +from pygerber.gerberx3.ast.nodes.properties.FS import FS +from pygerber.gerberx3.ast.nodes.properties.IN import IN +from pygerber.gerberx3.ast.nodes.properties.IP import IP +from pygerber.gerberx3.ast.nodes.properties.IR import IR +from pygerber.gerberx3.ast.nodes.properties.MI import MI +from pygerber.gerberx3.ast.nodes.properties.MO import MO, UnitMode +from pygerber.gerberx3.ast.nodes.properties.OF import OF +from pygerber.gerberx3.ast.nodes.properties.SF import SF +from pygerber.gerberx3.ast.visitor import AstVisitor + +NODE_SAMPLES: Dict[Type[Node], Node] = { + ABclose: ABclose(source="", location=0), + ABopen: ABopen(source="", location=0, aperture_identifier="D11"), + ADC: ADC( + source="", + location=0, + aperture_identifier="D11", + diameter="0.1", + hole_diameter="0.05", + ), + ADmacro: ADmacro( + source="", + location=0, + aperture_identifier="D11", + name="macro", + params=["1", "2"], + ), + ADO: ADO( + source="", + location=0, + aperture_identifier="D11", + width="0.1", + height="0.05", + hole_diameter="0.05", + ), + ADR: ADR( + source="", + location=0, + aperture_identifier="D11", + width="0.1", + height="0.05", + hole_diameter="0.05", + ), + ADP: ADP( + source="", + location=0, + aperture_identifier="D11", + outer_diameter="0.1", + vertices="4", + rotation="0.1", + hole_diameter="0.05", + ), + AMclose: AMclose(source="", location=0), + AMopen: AMopen(source="", location=0, name="macro"), + SRclose: SRclose(source="", location=0), + SRopen: SRopen(source="", location=0, x="1", y="2", i="3", j="4"), + TA_UserName: TA_UserName(source="", location=0, user_name="user"), + TA_AperFunction: TA_AperFunction( + source="", location=0, function=AperFunction.ViaDrill + ), + TA_DrillTolerance: TA_DrillTolerance( + source="", location=0, plus_tolerance=0.1, minus_tolerance=0.05 + ), + TA_FlashText: TA_FlashText( + source="", + location=0, + string="Hello World", + mode="B", + mirroring="R", + font=None, + size=None, + comments=[], + ), + TD: TD(source="", location=0, name="name"), + TF_UserName: TF_UserName(source="", location=0, user_name="user"), + TF_Part: TF_Part(source="", location=0, part=Part.Single), + TF_FileFunction: TF_FileFunction( + source="", location=0, file_function=FileFunction.Copper + ), + TF_FilePolarity: TF_FilePolarity(source="", location=0, polarity="Positive"), + TF_SameCoordinates: TF_SameCoordinates( + source="", location=0, identifier="CA9C4AC4-C4BE-41B9-9754-440A126A42FF" + ), + TF_CreationDate: TF_CreationDate( + source="", + location=0, + creation_date=datetime.datetime.now(tz=tzlocal.get_localzone()), + ), + TF_GenerationSoftware: TF_GenerationSoftware( + source="", + location=0, + vendor="vendor", + application="application", + version="version", + ), + TF_ProjectId: TF_ProjectId( + source="", + location=0, + name="name", + guid="guid", + revision="revision", + ), + TF_MD5: TF_MD5(source="", location=0, md5="0" * 32), + TO_UserName: TO_UserName(source="", location=0, user_name="user"), + TO_N: TO_N(source="", location=0, net_names=["net"]), + TO_P: TO_P(source="", location=0, refdes="refdes", number="number"), + TO_C: TO_C(source="", location=0, refdes="refdes"), + TO_CRot: TO_CRot(source="", location=0, angle=0.1), + TO_CMfr: TO_CMfr(source="", location=0, manufacturer="manufacturer"), + TO_CMNP: TO_CMNP(source="", location=0, part_number="part_number"), + TO_CVal: TO_CVal(source="", location=0, value="value"), + TO_CMnt: TO_CMnt(source="", location=0, mount=Mount.SMD), + TO_CFtp: TO_CFtp(source="", location=0, footprint="footprint"), + TO_CPgN: TO_CPgN(source="", location=0, name="name"), + TO_CPgD: TO_CPgD(source="", location=0, description="description"), + TO_CHgt: TO_CHgt(source="", location=0, height=0.1), + TO_CLbN: TO_CLbN(source="", location=0, name="name"), + TO_CLbD: TO_CLbD(source="", location=0, description="description"), + TO_CSup: TO_CSup( + source="", location=0, supplier="supplier", supplier_part="supplier_part" + ), + D01: D01(source="", location=0, x=None, y=None, i=None, j=None), + D02: D02( + source="", + location=0, + x=CoordinateX(source="", location=0, value="1"), + y=CoordinateY(source="", location=0, value="2"), + ), + D03: D03( + source="", + location=0, + x=CoordinateX(source="", location=0, value="1"), + y=CoordinateY(source="", location=0, value="2"), + ), + Dnn: Dnn(source="", location=0, value="D11"), + G01: G01(source="", location=0), + G02: G02(source="", location=0), + G03: G03(source="", location=0), + G04: G04(source="", location=0, string="comment"), + G36: G36(source="", location=0), + G37: G37(source="", location=0), + G54: G54(source="", location=0), + G55: G55(source="", location=0), + G70: G70(source="", location=0), + G71: G71(source="", location=0), + G74: G74(source="", location=0), + G75: G75(source="", location=0), + G90: G90(source="", location=0), + G91: G91(source="", location=0), + LM: LM(source="", location=0), + LN: LN(source="", location=0), + LP: LP(source="", location=0), + LR: LR(source="", location=0), + LS: LS(source="", location=0), + M00: M00(source="", location=0), + M01: M01(source="", location=0), + M02: M02(source="", location=0), + Add: Add( + source="", + location=0, + operands=[ + Constant(source="", location=0, constant="1"), + Constant(source="", location=0, constant="2"), + ], + ), + Div: Div( + source="", + location=0, + operands=[ + Constant(source="", location=0, constant="1"), + Constant(source="", location=0, constant="2"), + ], + ), + Mul: Mul( + source="", + location=0, + operands=[ + Constant(source="", location=0, constant="1"), + Constant(source="", location=0, constant="2"), + ], + ), + Sub: Sub( + source="", + location=0, + operands=[ + Constant(source="", location=0, constant="1"), + Constant(source="", location=0, constant="2"), + ], + ), + Neg: Neg( + source="", + location=0, + operand=Constant(source="", location=0, constant="2"), + ), + Pos: Pos( + source="", + location=0, + operand=Constant(source="", location=0, constant="2"), + ), + Assignment: Assignment( + source="", + location=0, + variable=Variable(source="", location=0, variable="$1"), + expression=Constant(source="", location=0, constant="1"), + ), + Constant: Constant(source="", location=0, constant="2"), + Point: Point( + source="", + location=0, + x=Constant(source="", location=0, constant="1"), + y=Constant(source="", location=0, constant="2"), + ), + Variable: Variable(source="", location=0, variable="$1"), + CoordinateX: CoordinateX(source="", location=0, value="1"), + CoordinateY: CoordinateY(source="", location=0, value="1"), + CoordinateI: CoordinateI(source="", location=0, value="1"), + CoordinateJ: CoordinateJ(source="", location=0, value="1"), + Code0: Code0(source="", location=0, string="string"), + Code1: Code1( + source="", + location=0, + exposure=Constant(source="", location=0, constant="1"), + diameter=Constant(source="", location=0, constant="2"), + center_x=Constant(source="", location=0, constant="3"), + center_y=Constant(source="", location=0, constant="4"), + ), + Code2: Code2( + source="", + location=0, + exposure=Constant(source="", location=0, constant="1"), + width=Constant(source="", location=0, constant="2"), + start_x=Constant(source="", location=0, constant="3"), + start_y=Constant(source="", location=0, constant="4"), + end_x=Constant(source="", location=0, constant="5"), + end_y=Constant(source="", location=0, constant="6"), + rotation=Constant(source="", location=0, constant="7"), + ), + Code4: Code4( + source="", + location=0, + exposure=Constant(source="", location=0, constant="1"), + number_of_points=Constant(source="", location=0, constant="2"), + start_x=Constant(source="", location=0, constant="3"), + start_y=Constant(source="", location=0, constant="4"), + points=[ + Point( + source="", + location=0, + x=Constant(source="", location=0, constant="1"), + y=Constant(source="", location=0, constant="2"), + ) + ], + rotation=Constant(source="", location=0, constant="5"), + ), + Code5: Code5( + source="", + location=0, + exposure=Constant(source="", location=0, constant="1"), + number_of_vertices=Constant(source="", location=0, constant="2"), + center_x=Constant(source="", location=0, constant="3"), + center_y=Constant(source="", location=0, constant="4"), + diameter=Constant(source="", location=0, constant="5"), + rotation=Constant(source="", location=0, constant="6"), + ), + Code6: Code6( + source="", + location=0, + center_x=Constant(source="", location=0, constant="3"), + center_y=Constant(source="", location=0, constant="4"), + outer_diameter=Constant(source="", location=0, constant="5"), + ring_thickness=Constant(source="", location=0, constant="1"), + gap_between_rings=Constant(source="", location=0, constant="1"), + max_ring_count=Constant(source="", location=0, constant="4"), + crosshair_thickness=Constant(source="", location=0, constant="4"), + crosshair_length=Constant(source="", location=0, constant="4"), + rotation=Constant(source="", location=0, constant="6"), + ), + Code7: Code7( + source="", + location=0, + center_x=Constant(source="", location=0, constant="3"), + center_y=Constant(source="", location=0, constant="4"), + outer_diameter=Constant(source="", location=0, constant="5"), + inner_diameter=Constant(source="", location=0, constant="1"), + gap_thickness=Constant(source="", location=0, constant="1"), + rotation=Constant(source="", location=0, constant="6"), + ), + Code20: Code20( + source="", + location=0, + exposure=Constant(source="", location=0, constant="1"), + width=Constant(source="", location=0, constant="2"), + start_x=Constant(source="", location=0, constant="3"), + start_y=Constant(source="", location=0, constant="4"), + end_x=Constant(source="", location=0, constant="5"), + end_y=Constant(source="", location=0, constant="6"), + rotation=Constant(source="", location=0, constant="7"), + ), + Code21: Code21( + source="", + location=0, + exposure=Constant(source="", location=0, constant="1"), + width=Constant(source="", location=0, constant="2"), + height=Constant(source="", location=0, constant="3"), + center_x=Constant(source="", location=0, constant="4"), + center_y=Constant(source="", location=0, constant="5"), + rotation=Constant(source="", location=0, constant="6"), + ), + Code22: Code22( + source="", + location=0, + exposure=Constant(source="", location=0, constant="1"), + width=Constant(source="", location=0, constant="2"), + height=Constant(source="", location=0, constant="3"), + x_lower_left=Constant(source="", location=0, constant="4"), + y_lower_left=Constant(source="", location=0, constant="5"), + rotation=Constant(source="", location=0, constant="6"), + ), + AS: AS(source="", location=0), + FS: FS( + source="", + location=0, + zeros="L", + coordinate_mode="A", + x_integral=2, + x_decimal=3, + y_integral=4, + y_decimal=5, + ), + IN: IN(source="", location=0), + IP: IP(source="", location=0), + IR: IR(source="", location=0), + MI: MI(source="", location=0), + MO: MO(source="", location=0, mode=UnitMode.METRIC), + OF: OF(source="", location=0), + SF: SF(source="", location=0), + File: File(source="", location=0, nodes=[]), +} + + +class TestAstVisitor: + @pytest.mark.parametrize(("_type", "instance"), NODE_SAMPLES.items()) + def test_visit_node(self, _type: Type[Node], instance: Node) -> None: # noqa: PT019 + callback_mock = mock.Mock() + visitor = AstVisitor() + setattr( + visitor, + instance.get_visitor_callback_function(visitor).__name__, + callback_mock, + ) + + instance.visit(visitor) + + # Assert that the callback was properly called + callback_mock.assert_called_once_with(instance) + + def test_iter_all(self) -> None: + """Simply call visit method on all nodes to ensure default implementations work.""" + visitor = AstVisitor() + for node in NODE_SAMPLES.values(): + node.visit(visitor) From d5fca5c77376a8c33766e14f76990f5a988445ad Mon Sep 17 00:00:00 2001 From: Sander de Regt Date: Sat, 3 Aug 2024 20:49:00 +0200 Subject: [PATCH 25/91] Add implementation of IR node parsing --- .../gerberx3/ast/nodes/properties/IR.py | 2 ++ .../gerberx3/parser/pyparsing/grammar.py | 26 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IR.py b/src/pygerber/gerberx3/ast/nodes/properties/IR.py index 1b867744..3e07b0f0 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IR.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IR.py @@ -15,6 +15,8 @@ class IR(Node): """Represents IR Gerber extended command.""" + rotation_degree: int + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ir(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 750fa769..81b0dad6 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -114,6 +114,7 @@ from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 from pygerber.gerberx3.ast.nodes.properties.FS import FS from pygerber.gerberx3.ast.nodes.properties.IP import IP +from pygerber.gerberx3.ast.nodes.properties.IR import IR from pygerber.gerberx3.ast.nodes.properties.MO import MO T = TypeVar("T", bound=Node) @@ -1565,7 +1566,7 @@ def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: def properties(self) -> pp.ParserElement: """Create a parser element capable of parsing Properties-commands.""" - return pp.MatchFirst([self.fs(), self.mo(), self.ip()]) + return pp.MatchFirst([self.fs(), self.mo(), self.ip(), self.ir()]) def fs(self) -> pp.ParserElement: """Create a parser for the FS command.""" @@ -1616,6 +1617,29 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> IP: + self.extended_command_close ) + def ir(self) -> pp.ParserElement: + """Create a parser for the IR command.""" + + def _(s: str, loc: int, tokens: pp.ParseResults) -> IR: + try: + return self.get_cls(IR)(source=s, location=loc, **tokens.as_dict()) + except ValidationError as e: + raise pp.ParseFatalException(s, loc, "Invalid IR") from e + + return ( + self.extended_command_open + + ( + pp.Literal("IR") + + pp.one_of(("0", "90", "180", "270")).set_results_name( + "rotation_degree" + ) + ) + .set_parse_action(_) + .set_name("IR") + + self.command_end + + self.extended_command_close + ) + def mo(self) -> pp.ParserElement: """Create a parser for the MO command.""" return ( From 6f3b11d5b46f989ac7e93ac052e26f44a3d98806 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 12 Aug 2024 02:07:11 +0200 Subject: [PATCH 26/91] Removed standalone command end token --- .../gerberx3/ast/nodes/g_codes/G54.py | 3 + .../gerberx3/ast/nodes/g_codes/G55.py | 3 + .../gerberx3/ast/nodes/other/command_end.py | 28 -- .../gerberx3/ast/nodes/properties/IR.py | 2 +- src/pygerber/gerberx3/ast/visitor.py | 6 +- .../gerberx3/parser/pyparsing/grammar.py | 314 +++++++++--------- test/gerberx3/test_ast/test_ast_visitor.py | 8 +- 7 files changed, 166 insertions(+), 198 deletions(-) delete mode 100644 src/pygerber/gerberx3/ast/nodes/other/command_end.py diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py index a1b06346..1efff402 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn if TYPE_CHECKING: from typing_extensions import Self @@ -15,6 +16,8 @@ class G54(Node): """Represents G54 Gerber command.""" + dnn: Dnn + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g54(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py index 76f0ab76..0cdf5ab1 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 if TYPE_CHECKING: from typing_extensions import Self @@ -15,6 +16,8 @@ class G55(Node): """Represents G55 Gerber command.""" + flash: D03 + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g55(self) diff --git a/src/pygerber/gerberx3/ast/nodes/other/command_end.py b/src/pygerber/gerberx3/ast/nodes/other/command_end.py deleted file mode 100644 index 70b11e24..00000000 --- a/src/pygerber/gerberx3/ast/nodes/other/command_end.py +++ /dev/null @@ -1,28 +0,0 @@ -"""`pygerber.nodes.other.command_end` module contains definition of `CommandEnd` -class. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Callable - -from pygerber.gerberx3.ast.nodes.base import Node - -if TYPE_CHECKING: - from typing_extensions import Self - - from pygerber.gerberx3.ast.visitor import AstVisitor - - -class CommandEnd(Node): - """Represents CommandEnd node.""" - - def visit(self, visitor: AstVisitor) -> None: - """Handle visitor call.""" - visitor.on_command_end(self) - - def get_visitor_callback_function( - self, visitor: AstVisitor - ) -> Callable[[Self], None]: - """Get callback function for the node.""" - return visitor.on_command_end diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IR.py b/src/pygerber/gerberx3/ast/nodes/properties/IR.py index 3e07b0f0..bedf8648 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IR.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IR.py @@ -15,7 +15,7 @@ class IR(Node): """Represents IR Gerber extended command.""" - rotation_degree: int + rotation_degrees: int def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index f5f80863..2dbbdbf9 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -100,7 +100,6 @@ from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable - from pygerber.gerberx3.ast.nodes.other.command_end import CommandEnd from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate from pygerber.gerberx3.ast.nodes.other.extended_command_close import ( ExtendedCommandClose, @@ -359,9 +358,11 @@ def on_g37(self, node: G37) -> None: def on_g54(self, node: G54) -> None: """Handle `G54` node.""" + node.dnn.visit(self) def on_g55(self, node: G55) -> None: """Handle `G55` node.""" + node.flash.visit(self) def on_g70(self, node: G70) -> None: """Handle `G70` node.""" @@ -450,9 +451,6 @@ def on_variable(self, node: Variable) -> None: # Other - def on_command_end(self, node: CommandEnd) -> None: - """Handle `CommandEnd` node.""" - def on_coordinate(self, node: Coordinate) -> None: """Handle `Coordinate` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 81b0dad6..4f6ab7a2 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -8,7 +8,6 @@ from typing import Callable, ClassVar, List, Literal, Type, TypeVar, cast import pyparsing as pp -from pydantic import ValidationError from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen @@ -91,7 +90,6 @@ from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable -from pygerber.gerberx3.ast.nodes.other.command_end import CommandEnd from pygerber.gerberx3.ast.nodes.other.coordinate import ( CoordinateI, CoordinateJ, @@ -123,9 +121,9 @@ class Optimization(IntFlag): """Namespace class holding optimization level constants.""" - DISCARD_COMMAND_BOUNDARIES = 0b0001 - DISCARD_COMMENTS = 0b0010 - DISCARD_ATTRIBUTES = 0b0100 + DISCARD_COMMAND_BOUNDARIES = 0b0000_0001 + DISCARD_COMMENTS = 0b0000_0010 + DISCARD_ATTRIBUTES = 0b0000_0100 class Grammar: @@ -162,7 +160,6 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: self.m_codes(), self.d_codes(), self.properties(), - self.command_end, ] ) ) @@ -178,6 +175,10 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: return root + @pp.cached_property + def _asterisk(self) -> pp.ParserElement: + return pp.Literal(r"*").set_name("*") + def get_cls(self, node_cls: Type[T]) -> Type[T]: """Get the class of the node.""" return self.ast_node_class_overrides.get(node_cls.__qualname__, node_cls) # type: ignore[return-value] @@ -195,7 +196,7 @@ def comma(self) -> pp.ParserElement: @pp.cached_property def name(self) -> pp.ParserElement: """Create a parser element capable of parsing names.""" - return pp.Regex(r"[._a-zA-Z$][._a-zA-Z0-9]*/").set_results_name("name") + return pp.Regex(r"[._a-zA-Z$][._a-zA-Z0-9]*").set_results_name("name") @pp.cached_property def user_name(self) -> pp.ParserElement: @@ -263,30 +264,22 @@ def aperture_block(self) -> pp.ParserElement: @pp.cached_property def ab_open(self) -> pp.ParserElement: """Create a parser element capable of parsing AB-open.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(ABopen)(source=s, location=loc, **tokens.as_dict()) - return ( self.extended_command_open - + (pp.Literal("AB") + self.aperture_identifier) + + (pp.Literal("AB") + self.aperture_identifier + self._asterisk) .set_name("ABopen") - .set_parse_action(_) - + self.command_end + .set_parse_action(self.make_unpack_callback(ABopen)) + self.extended_command_close ) @pp.cached_property def ab_close(self) -> pp.ParserElement: """Create a parser element capable of parsing AB-close.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(ABclose)(source=s, location=loc) - return ( self.extended_command_open - + pp.Literal("AB").set_name("ABclose").set_parse_action(_) - + self.command_end + + (pp.Literal("AB") + self._asterisk) + .set_name("ABclose") + .set_parse_action(self.make_unpack_callback(ABclose)) + self.extended_command_close ) @@ -295,7 +288,6 @@ def macro(self) -> pp.ParserElement: return ( self.extended_command_open + self.am_open - + self.command_end + self.primitives + self.am_close + self.extended_command_close @@ -304,22 +296,20 @@ def macro(self) -> pp.ParserElement: @pp.cached_property def am_open(self) -> pp.ParserElement: """Create a parser element capable of parsing AM-open.""" - - def _am_open(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(AMopen)(source=s, location=loc, **tokens.as_dict()) - return ( - (pp.Literal("AM") + self.name).set_name("AMopen").set_parse_action(_am_open) + (pp.Literal("AM") + self.name + self._asterisk) + .set_name("AMopen") + .set_parse_action(self.make_unpack_callback(AMopen)) ) @pp.cached_property def am_close(self) -> pp.ParserElement: """Create a parser element capable of parsing AM-close.""" - - def _am_close(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(AMclose)(source=s, location=loc, **tokens.as_dict()) - - return pp.Literal("").set_name("AM_close").set_parse_action(_am_close) + return ( + pp.Literal("") + .set_name("AM_close") + .set_parse_action(self.make_unpack_callback(AMclose)) + ) def step_repeat(self) -> pp.ParserElement: """Create a parser element capable of parsing step repeats.""" @@ -333,10 +323,6 @@ def step_repeat(self) -> pp.ParserElement: @pp.cached_property def sr_open(self) -> pp.ParserElement: """Create a parser element capable of parsing SR-open.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(SRopen)(source=s, location=loc, **tokens.as_dict()) - return ( self.extended_command_open + ( @@ -346,25 +332,22 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: + pp.Opt(pp.Literal("Y") + self.double.set_results_name("y")) + pp.Opt(pp.Literal("I") + self.double.set_results_name("i")) + pp.Opt(pp.Literal("J") + self.double.set_results_name("j")) + + self._asterisk ) .set_name("SRopen") - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(SRopen)) ) - + self.command_end + self.extended_command_close ) @pp.cached_property def sr_close(self) -> pp.ParserElement: """Create a parser element capable of parsing SR-close.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(ABclose)(source=s, location=loc) - return ( self.extended_command_open - + pp.Literal("SR").set_name("SRclose").set_parse_action(_) - + self.command_end + + (pp.Literal("SR") + self._asterisk) + .set_name("SRclose") + .set_parse_action(self.make_unpack_callback(AMclose)) + self.extended_command_close ) @@ -372,7 +355,6 @@ def add_aperture(self) -> pp.ParserElement: """Create a parser element capable of parsing add-aperture commands.""" return ( self.extended_command_open - + pp.Suppress(pp.Literal("AD")) + pp.MatchFirst( [ self.add_aperture_circle(), @@ -382,25 +364,22 @@ def add_aperture(self) -> pp.ParserElement: self.add_aperture_macro(), ] ) - + self.command_end + self.extended_command_close ) def add_aperture_circle(self) -> pp.ParserElement: """Create a parser element capable of parsing add-aperture-circle commands.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(ADC)(source=s, location=loc, **tokens.as_dict()) - return ( ( - self.aperture_identifier + pp.Literal("AD") + + self.aperture_identifier + pp.Literal("C,") + self.double.set_results_name("diameter") + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) + + self._asterisk ) .set_name("ADC") - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(ADC)) ) def add_aperture_rectangle( @@ -409,63 +388,57 @@ def add_aperture_rectangle( """Create a parser element capable of parsing add-aperture-rectangle commands. """ - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(cls)(source=s, location=loc, **tokens.as_dict()) - return ( ( - self.aperture_identifier + pp.Literal("AD") + + self.aperture_identifier + pp.Literal(f"{symbol},") + self.double.set_results_name("width") + self._x + self.double.set_results_name("height") + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) + + self._asterisk ) - .set_name("ADC") - .set_parse_action(_) + .set_name(f"AD{symbol}") + .set_parse_action(self.make_unpack_callback(cls)) ) def add_aperture_polygon(self) -> pp.ParserElement: """Create a parser element capable of parsing add-aperture-polygon commands. """ - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(ADP)(source=s, location=loc, **tokens.as_dict()) - return ( ( - self.aperture_identifier + pp.Literal("AD") + + self.aperture_identifier + pp.Literal("P,") + self.double.set_results_name("outer_diameter") + self._x + self.double.set_results_name("vertices") + pp.Opt(self._x + self.double.set_results_name("rotation")) + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) + + self._asterisk ) - .set_name("ADC") - .set_parse_action(_) + .set_name("ADP") + .set_parse_action(self.make_unpack_callback(ADP)) ) def add_aperture_macro(self) -> pp.ParserElement: """Create a parser element capable of parsing add-aperture-polygon commands. """ - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(ADmacro)(source=s, location=loc, **tokens.as_dict()) - param = self.double.set_results_name("params", list_all_matches=True) return ( ( - self.aperture_identifier + pp.Literal("AD") + + self.aperture_identifier + self.name.set_results_name("name") + pp.Opt(self.comma + param + pp.ZeroOrMore(self._x + param)) + + self._asterisk ) - .set_name("ADC") - .set_parse_action(_) + .set_name("ADmacro") + .set_parse_action(self.make_unpack_callback(ADmacro)) ) @pp.cached_property @@ -501,7 +474,6 @@ def ta(self) -> pp.ParserElement: self._ta_flash_text, ] ) - + self.command_end + self.extended_command_close ) @@ -515,6 +487,7 @@ def _ta_user_name(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) + + self._asterisk ) .set_name("TA") .set_parse_action(self.make_unpack_callback(TA_UserName)) @@ -536,6 +509,7 @@ def _ta_aper_function(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) + + self._asterisk ) .set_name("TA.AperFunction") .set_parse_action(self.make_unpack_callback(TA_AperFunction)) @@ -554,6 +528,7 @@ def _ta_drill_tolerance(self) -> pp.ParserElement: self.comma + self.double.set_results_name("minus_tolerance") ) ) + + self._asterisk ) .set_name("TA.DrillTolerance") .set_parse_action(self.make_unpack_callback(TA_DrillTolerance)) @@ -579,6 +554,7 @@ def _ta_flash_text(self) -> pp.ParserElement: self.comma + self.field.set_results_name("comments", list_all_matches=True) ) + + self._asterisk ) .set_name("TA.FlashText") .set_parse_action(self.make_unpack_callback(TA_FlashText)) @@ -593,11 +569,14 @@ def td(self) -> pp.ParserElement: return ( self.extended_command_open + ( - (pp.Literal("TD") + pp.Opt(self.string.set_results_name("name"))) + ( + pp.Literal("TD") + + pp.Opt(self.string.set_results_name("name")) + + self._asterisk + ) .set_name("TD") .set_parse_action(self.make_unpack_callback(TD)) ) - + self.command_end + self.extended_command_close ) @@ -618,7 +597,6 @@ def tf(self) -> pp.ParserElement: self._tf_md5, ] ) - + self.command_end + self.extended_command_close ) @@ -632,6 +610,7 @@ def _tf_user_name(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) + + self._asterisk ) .set_name("TF") .set_parse_action(self.make_unpack_callback(TF_UserName)) @@ -649,6 +628,7 @@ def _tf_part(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) + + self._asterisk ) .set_name("TF.Part") .set_parse_action(self.make_unpack_callback(TF_Part)) @@ -666,6 +646,7 @@ def _tf_file_function(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) + + self._asterisk ) .set_name("TF.FileFunction") .set_parse_action(self.make_unpack_callback(TF_FileFunction)) @@ -679,6 +660,7 @@ def _tf_file_polarity(self) -> pp.ParserElement: + pp.CaselessLiteral(".FilePolarity") + self.comma + self.field.set_results_name("polarity") + + self._asterisk ) .set_name("TF.FilePolarity") .set_parse_action(self.make_unpack_callback(TF_FilePolarity)) @@ -691,6 +673,7 @@ def _tf_same_coordinates(self) -> pp.ParserElement: self._tf + pp.CaselessLiteral(".SameCoordinates") + pp.Opt(self.comma + self.field.set_results_name("identifier")) + + self._asterisk ) .set_name("TF.SameCoordinates") .set_parse_action(self.make_unpack_callback(TF_SameCoordinates)) @@ -704,6 +687,7 @@ def _tf_creation_date(self) -> pp.ParserElement: + pp.CaselessLiteral(".CreationDate") + self.comma + self.field.set_results_name("creation_date") + + self._asterisk ) .set_name("TF.CreationDate") .set_parse_action(self.make_unpack_callback(TF_CreationDate)) @@ -724,6 +708,7 @@ def _tf_generation_software(self) -> pp.ParserElement: + pp.Opt(self.comma + self.field.set_results_name("version")) ) ) + + self._asterisk ) .set_name("TF.GenerationSoftware") .set_parse_action(self.make_unpack_callback(TF_GenerationSoftware)) @@ -744,6 +729,7 @@ def _tf_project_id(self) -> pp.ParserElement: + pp.Opt(self.comma + self.field.set_results_name("revision")) ) ) + + self._asterisk ) .set_name("TF.ProjectId") .set_parse_action(self.make_unpack_callback(TF_ProjectId)) @@ -757,6 +743,7 @@ def _tf_md5(self) -> pp.ParserElement: + pp.CaselessLiteral(".MD5") + self.comma + self.field.set_results_name("md5") + + self._asterisk ) .set_name("TF.MD5") .set_parse_action(self.make_unpack_callback(TF_MD5)) @@ -790,7 +777,6 @@ def to(self) -> pp.ParserElement: self._to_csup, ] ) - + self.command_end + self.extended_command_close ) @@ -804,6 +790,7 @@ def _to_user_name(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) + + self._asterisk ) .set_name("TO") .set_parse_action(self.make_unpack_callback(TO_UserName)) @@ -819,6 +806,7 @@ def _to_n(self) -> pp.ParserElement: self.comma + self.field.set_results_name("net_names", list_all_matches=True) ) + + self._asterisk ) .set_name("TO.N") .set_parse_action(self.make_unpack_callback(TO_N)) @@ -835,6 +823,7 @@ def _to_p(self) -> pp.ParserElement: + self.comma + self.field.set_results_name("number") + pp.Opt(self.comma + self.field.set_results_name("function")) + + self._asterisk ) .set_name("TO.P") .set_parse_action(self.make_unpack_callback(TO_P)) @@ -848,6 +837,7 @@ def _to_c(self) -> pp.ParserElement: + pp.CaselessLiteral(".C") + self.comma + self.field.set_results_name("refdes") + + self._asterisk ) .set_name("TO.C") .set_parse_action(self.make_unpack_callback(TO_C)) @@ -861,6 +851,7 @@ def _to_crot(self) -> pp.ParserElement: + pp.CaselessLiteral(".CRot") + self.comma + self.field.set_results_name("angle") + + self._asterisk ) .set_name("TO.CRot") .set_parse_action(self.make_unpack_callback(TO_CRot)) @@ -874,6 +865,7 @@ def _to_cmfr(self) -> pp.ParserElement: + pp.CaselessLiteral(".CMfr") + self.comma + self.field.set_results_name("manufacturer") + + self._asterisk ) .set_name("TO.CMfr") .set_parse_action(self.make_unpack_callback(TO_CMfr)) @@ -887,6 +879,7 @@ def _to_cmpn(self) -> pp.ParserElement: + pp.CaselessLiteral(".CMPN") + self.comma + self.field.set_results_name("part_number") + + self._asterisk ) .set_name("TO.CMPN") .set_parse_action(self.make_unpack_callback(TO_CMNP)) @@ -900,6 +893,7 @@ def _to_cval(self) -> pp.ParserElement: + pp.CaselessLiteral(".CVal") + self.comma + self.field.set_results_name("value") + + self._asterisk ) .set_name("TO.CVal") .set_parse_action(self.make_unpack_callback(TO_CVal)) @@ -913,6 +907,7 @@ def _to_cmnt(self) -> pp.ParserElement: + pp.CaselessLiteral(".CMnt") + self.comma + self.field.set_results_name("mount") + + self._asterisk ) .set_name("TO.CMnt") .set_parse_action(self.make_unpack_callback(TO_CMnt)) @@ -926,6 +921,7 @@ def _to_cftp(self) -> pp.ParserElement: + pp.CaselessLiteral(".CFtp") + self.comma + self.field.set_results_name("footprint") + + self._asterisk ) .set_name("TO.CFtp") .set_parse_action(self.make_unpack_callback(TO_CFtp)) @@ -939,6 +935,7 @@ def _to_cpgn(self) -> pp.ParserElement: + pp.CaselessLiteral(".CPgN") + self.comma + self.field.set_results_name("name") + + self._asterisk ) .set_name("TO.CPgN") .set_parse_action(self.make_unpack_callback(TO_CPgN)) @@ -952,6 +949,7 @@ def _to_cpgd(self) -> pp.ParserElement: + pp.CaselessLiteral(".CPgD") + self.comma + self.field.set_results_name("description") + + self._asterisk ) .set_name("TO.CPgD") .set_parse_action(self.make_unpack_callback(TO_CPgD)) @@ -965,6 +963,7 @@ def _to_chgt(self) -> pp.ParserElement: + pp.CaselessLiteral(".CHgt") + self.comma + self.field.set_results_name("height") + + self._asterisk ) .set_name("TO.CHgt") .set_parse_action(self.make_unpack_callback(TO_CHgt)) @@ -978,6 +977,7 @@ def _to_clbn(self) -> pp.ParserElement: + pp.CaselessLiteral(".CLbn") + self.comma + self.field.set_results_name("name") + + self._asterisk ) .set_name("TO.CLbn") .set_parse_action(self.make_unpack_callback(TO_CLbN)) @@ -991,6 +991,7 @@ def _to_clbd(self) -> pp.ParserElement: + pp.CaselessLiteral(".CLbD") + self.comma + self.field.set_results_name("description") + + self._asterisk ) .set_name("TO.CLbD") .set_parse_action(self.make_unpack_callback(TO_CLbD)) @@ -1012,6 +1013,7 @@ def _to_csup(self) -> pp.ParserElement: "other_suppliers", list_all_matches=True ) ) + + self._asterisk ) .set_name("TO.CSup") .set_parse_action(self.make_unpack_callback(TO_CSup)) @@ -1041,7 +1043,7 @@ def d_codes(self) -> pp.ParserElement: @pp.cached_property def _dnn(self) -> pp.ParserElement: return ( - self.aperture_identifier.set_results_name("value") + (self.aperture_identifier.set_results_name("value") + self._asterisk) .set_parse_action(self.make_unpack_callback(Dnn)) .set_name("Dnn") ) @@ -1055,7 +1057,7 @@ def _d01(self) -> pp.ParserElement: + pp.Opt(self._coordinate_i.set_results_name("i")) + pp.Opt(self._coordinate_j.set_results_name("j")) + pp.Regex(r"D0*1") - + pp.Literal("*") + + self._asterisk ) .set_parse_action(self.make_unpack_callback(D01)) .set_name("D01") @@ -1068,7 +1070,7 @@ def _d02(self) -> pp.ParserElement: self._coordinate_x.set_results_name("x") + self._coordinate_y.set_results_name("y") + pp.Regex(r"D0*2") - + pp.Literal("*") + + self._asterisk ) .set_parse_action(self.make_unpack_callback(D02)) .set_name("D02") @@ -1081,7 +1083,7 @@ def _d03(self) -> pp.ParserElement: pp.Opt(self._coordinate_x.set_results_name("x")) + pp.Opt(self._coordinate_y.set_results_name("y")) + pp.Regex(r"D0*3") - + pp.Literal("*") + + self._asterisk ) .set_parse_action(self.make_unpack_callback(D03)) .set_name("D03") @@ -1095,16 +1097,42 @@ def _d03(self) -> pp.ParserElement: def g_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing G-codes.""" - g04_comment = (pp.Regex(r"G0*4") + pp.Opt(self.string)).set_name("G04") + g04_comment = ( + pp.Regex(r"G0*4") + pp.Opt(self.string) + self._asterisk + ).set_name("G04") if self.optimization & Optimization.DISCARD_COMMENTS: g04_comment = pp.Suppress(g04_comment) else: g04_comment = g04_comment.set_parse_action(self.make_unpack_callback(G04)) + g54 = ( + (pp.Regex(r"G0*54") + self._dnn.set_results_name("dnn")) + .set_name("G54") + .set_parse_action(self.make_unpack_callback(G54)) + ) + g55 = ( + ( + pp.Regex(r"G0*55") + + ( + ( + pp.Opt(self._coordinate_x.set_results_name("x")) + + pp.Opt(self._coordinate_y.set_results_name("y")) + + pp.Regex(r"D0*3") + + self._asterisk + ) + .set_parse_action(self.make_unpack_callback(D03)) + .set_name("D03") + ).set_results_name("flash") + ) + .set_name("G55") + .set_parse_action(self.make_unpack_callback(G55)) + ) return pp.MatchFirst( [ g04_comment, + g54, + g55, *( self.g( value, @@ -1118,7 +1146,6 @@ def g_codes(self) -> pp.ParserElement: (36, G36), (37, G37), (54, G54), - (55, G55), (70, G70), (71, G71), (74, G74), @@ -1133,9 +1160,10 @@ def g_codes(self) -> pp.ParserElement: def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: """Create a parser element capable of parsing particular G-code.""" - element = pp.Regex(r"G0*" + str(value)).set_name(f"G{value}") - return element.setParseAction( - lambda s, loc, _tokens: self.get_cls(cls)(source=s, location=loc) + return ( + (pp.Regex(r"G0*" + str(value)) + self._asterisk) + .set_name(f"G{value}") + .set_parse_action(self.make_unpack_callback(cls)) ) # ██ ██████ █████ ██████ █████ ███████ ███ ███ ███ ███ █████ ███ ██ ██████ ███████ # noqa: E501 @@ -1168,9 +1196,10 @@ def m_codes(self) -> pp.ParserElement: def m(self, value: int, cls: Type[Node]) -> pp.ParserElement: """Create a parser element capable of parsing particular D-code.""" - element = pp.Regex(r"M0*" + str(value)).set_name(f"M{value}") - return element.setParseAction( - lambda s, loc, _tokens: cls(source=s, location=loc) + return ( + (pp.Regex(r"M0*" + str(value)) + self._asterisk) + .set_name(f"M{value}") + .set_parse_action(self.make_unpack_callback(cls)) ) # ███ ███ █████ ████████ ██ ██ @@ -1267,33 +1296,32 @@ def _mul(s: str, loc: int, tokens: pp.ParseResults) -> Expression: @pp.cached_property def constant(self) -> pp.ParserElement: """Create a parser element capable of parsing constants.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(Constant)(source=s, location=loc, **_tokens.as_dict()) - - return self.double.set_results_name("constant").set_parse_action(_) + return self.double.set_results_name("constant").set_parse_action( + self.make_unpack_callback(Constant) + ) @pp.cached_property def variable(self) -> pp.ParserElement: """Create a parser element capable of parsing variables.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(Variable)(source=s, location=loc, **_tokens.as_dict()) - - return pp.Regex(r"\$[0-9]+").set_results_name("variable").set_parse_action(_) + return ( + pp.Regex(r"\$[0-9]+") + .set_results_name("variable") + .set_parse_action(self.make_unpack_callback(Variable)) + ) @pp.cached_property def assignment(self) -> pp.ParserElement: """Create a parser element capable of parsing assignments.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(Assignment)(source=s, location=loc, **tokens.as_dict()) - return ( - self.variable - + pp.Suppress("=") - + self.expression.set_results_name("expression") - ).set_results_name("assignment").set_parse_action(_) + self.command_end + ( + self.variable + + pp.Suppress("=") + + self.expression.set_results_name("expression") + + self._asterisk + ) + .set_results_name("assignment") + .set_parse_action(self.make_unpack_callback(Assignment)) + ) # ██████ ████████ ██ ██ ███████ ██████ # ██ ██ ██ ██ ██ ██ ██ ██ @@ -1301,16 +1329,6 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: # ██ ██ ██ ██ ██ ██ ██ ██ # ██████ ██ ██ ██ ███████ ██ ██ - @pp.cached_property - def command_end(self) -> pp.ParserElement: - """Create a parser element capable of parsing the command end.""" - parser = pp.Literal(r"*").set_name("*") - - if self.optimization & Optimization.DISCARD_COMMAND_BOUNDARIES: - return pp.Suppress(parser) - - return parser.set_parse_action(self.make_unpack_callback(CommandEnd)) - @pp.cached_property def _coordinate_x(self) -> pp.ParserElement: return ( @@ -1372,10 +1390,6 @@ def extended_command_close(self) -> pp.ParserElement: @pp.cached_property def primitives(self) -> pp.ParserElement: """Create a parser element capable of parsing macro primitives.""" - - def _point(s: str, loc: int, tokens: pp.ParseResults) -> Point: - return self.get_cls(Point)(source=s, location=loc, **tokens.as_dict()) - cs = self.comma return pp.ZeroOrMore( @@ -1433,7 +1447,7 @@ def _point(s: str, loc: int, tokens: pp.ParseResults) -> Point: + self.expression.set_results_name("y") ) .set_results_name("points", list_all_matches=True) - .set_parse_action(_point), + .set_parse_action(self.make_unpack_callback(Point)), ) + cs + self.expression.set_results_name("rotation"), @@ -1550,13 +1564,11 @@ def primitive( self, cls: Type[Node], code: int, fields: pp.ParserElement ) -> pp.ParserElement: """Create a parser element capable of parsing a primitive.""" - - def _(s: str, loc: int, _tokens: pp.ParseResults) -> Node: - return self.get_cls(cls)(source=s, location=loc, **_tokens.as_dict()) - - return (pp.Literal(str(code)) + fields).set_name( - f"primitive-{code}" - ).set_parse_action(_) + self.command_end + return ( + (pp.Literal(str(code)) + fields + self._asterisk) + .set_name(f"primitive-{code}") + .set_parse_action(self.make_unpack_callback(cls)) + ) # ██████ ██████ ██████ ██████ ███████ ███████ ███████ ██ ███████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ @@ -1570,13 +1582,6 @@ def properties(self) -> pp.ParserElement: def fs(self) -> pp.ParserElement: """Create a parser for the FS command.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> FS: - try: - return self.get_cls(FS)(source=s, location=loc, **tokens.as_dict()) - except ValidationError as e: - raise pp.ParseFatalException(s, loc, "Invalid FS") from e - return ( self.extended_command_open + ( @@ -1589,54 +1594,38 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> FS: + pp.CaselessLiteral("Y") + pp.Regex(r"[0-9]").set_results_name("y_integral") + pp.Regex(r"[0-9]").set_results_name("y_decimal") + + self._asterisk ) - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(FS)) .set_name("FS") - + self.command_end + self.extended_command_close ) def ip(self) -> pp.ParserElement: """Create a parser for the IP command.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> IP: - try: - return self.get_cls(IP)(source=s, location=loc, **tokens.as_dict()) - except ValidationError as e: - raise pp.ParseFatalException(s, loc, "Invalid IP") from e - return ( self.extended_command_open + ( pp.Literal("IP") + pp.one_of(("POS", "NEG")).set_results_name("polarity") + + self._asterisk ) - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(IP)) .set_name("IP") - + self.command_end + self.extended_command_close ) def ir(self) -> pp.ParserElement: """Create a parser for the IR command.""" - - def _(s: str, loc: int, tokens: pp.ParseResults) -> IR: - try: - return self.get_cls(IR)(source=s, location=loc, **tokens.as_dict()) - except ValidationError as e: - raise pp.ParseFatalException(s, loc, "Invalid IR") from e - return ( self.extended_command_open + ( pp.Literal("IR") - + pp.one_of(("0", "90", "180", "270")).set_results_name( - "rotation_degree" - ) + + self.double.set_results_name("rotation_degrees") + + self._asterisk ) - .set_parse_action(_) + .set_parse_action(self.make_unpack_callback(IR)) .set_name("IR") - + self.command_end + self.extended_command_close ) @@ -1645,11 +1634,14 @@ def mo(self) -> pp.ParserElement: return ( self.extended_command_open + ( - (pp.Literal("MO") + pp.one_of(["IN", "MM"]).set_results_name("mode")) + ( + pp.Literal("MO") + + pp.one_of(["IN", "MM"]).set_results_name("mode") + + self._asterisk + ) .set_parse_action(self.make_unpack_callback(MO)) .set_name("MO") ) - + self.command_end + self.extended_command_close ) diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index af16deca..2be40978 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -254,8 +254,8 @@ G04: G04(source="", location=0, string="comment"), G36: G36(source="", location=0), G37: G37(source="", location=0), - G54: G54(source="", location=0), - G55: G55(source="", location=0), + G54: G54(source="", location=0, dnn=Dnn(source="", location=0, value="D11")), + G55: G55(source="", location=0, flash=D03(source="", location=0)), G70: G70(source="", location=0), G71: G71(source="", location=0), G74: G74(source="", location=0), @@ -443,8 +443,8 @@ y_decimal=5, ), IN: IN(source="", location=0), - IP: IP(source="", location=0), - IR: IR(source="", location=0), + IP: IP(source="", location=0, polarity="D"), + IR: IR(source="", location=0, rotation_degrees=90), MI: MI(source="", location=0), MO: MO(source="", location=0, mode=UnitMode.METRIC), OF: OF(source="", location=0), From 17e087d221db7ec3277b1ad3c3dcade35cc2e9df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 13 Aug 2024 19:17:44 +0200 Subject: [PATCH 27/91] Remove ExtendedCommandOpen/Close --- .../ast/nodes/other/extended_command_close.py | 28 -- .../ast/nodes/other/extended_command_open.py | 28 -- src/pygerber/gerberx3/ast/visitor.py | 12 - .../gerberx3/parser/pyparsing/grammar.py | 350 ++++++------------ 4 files changed, 117 insertions(+), 301 deletions(-) delete mode 100644 src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py delete mode 100644 src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py diff --git a/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py b/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py deleted file mode 100644 index bb1a9829..00000000 --- a/src/pygerber/gerberx3/ast/nodes/other/extended_command_close.py +++ /dev/null @@ -1,28 +0,0 @@ -"""`pygerber.nodes.other.ExtendedCommandClose` module contains definition of -`ExtendedCommandClose` class. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Callable - -from pygerber.gerberx3.ast.nodes.base import Node - -if TYPE_CHECKING: - from typing_extensions import Self - - from pygerber.gerberx3.ast.visitor import AstVisitor - - -class ExtendedCommandClose(Node): - """Represents ExtendedCommandClose node.""" - - def visit(self, visitor: AstVisitor) -> None: - """Handle visitor call.""" - visitor.on_extended_command_close(self) - - def get_visitor_callback_function( - self, visitor: AstVisitor - ) -> Callable[[Self], None]: - """Get callback function for the node.""" - return visitor.on_extended_command_close diff --git a/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py b/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py deleted file mode 100644 index 88c31a1a..00000000 --- a/src/pygerber/gerberx3/ast/nodes/other/extended_command_open.py +++ /dev/null @@ -1,28 +0,0 @@ -"""`pygerber.nodes.other.ExtendedCommandOpen` module contains definition of -`ExtendedCommandOpen` class. -""" - -from __future__ import annotations - -from typing import TYPE_CHECKING, Callable - -from pygerber.gerberx3.ast.nodes.base import Node - -if TYPE_CHECKING: - from typing_extensions import Self - - from pygerber.gerberx3.ast.visitor import AstVisitor - - -class ExtendedCommandOpen(Node): - """Represents ExtendedCommandOpen node.""" - - def visit(self, visitor: AstVisitor) -> None: - """Handle visitor call.""" - visitor.on_extended_command_open(self) - - def get_visitor_callback_function( - self, visitor: AstVisitor - ) -> Callable[[Self], None]: - """Get callback function for the node.""" - return visitor.on_extended_command_open diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 2dbbdbf9..239ea3ef 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -101,12 +101,6 @@ from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate - from pygerber.gerberx3.ast.nodes.other.extended_command_close import ( - ExtendedCommandClose, - ) - from pygerber.gerberx3.ast.nodes.other.extended_command_open import ( - ExtendedCommandOpen, - ) from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 @@ -470,12 +464,6 @@ def on_coordinate_j(self, node: CoordinateJ) -> None: """Handle `Coordinate` node.""" self.on_coordinate(node) - def on_extended_command_close(self, node: ExtendedCommandClose) -> None: - """Handle `ExtendedCommandClose` node.""" - - def on_extended_command_open(self, node: ExtendedCommandOpen) -> None: - """Handle `ExtendedCommandOpen` node.""" - # Primitives def on_code_0(self, node: Code0) -> None: diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 4f6ab7a2..0855fc66 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -96,10 +96,6 @@ CoordinateX, CoordinateY, ) -from pygerber.gerberx3.ast.nodes.other.extended_command_close import ( - ExtendedCommandClose, -) -from pygerber.gerberx3.ast.nodes.other.extended_command_open import ExtendedCommandOpen from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 @@ -179,6 +175,16 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: def _asterisk(self) -> pp.ParserElement: return pp.Literal(r"*").set_name("*") + @pp.cached_property + def _extended(self) -> pp.ParserElement: + return pp.Literal(r"%").set_name("%") + + def _command(self, inner: pp.ParserElement) -> pp.ParserElement: + return inner + self._asterisk + + def _extended_command(self, inner: pp.ParserElement) -> pp.ParserElement: + return self._extended + inner + self._asterisk + self._extended + def get_cls(self, node_cls: Type[T]) -> Type[T]: """Get the class of the node.""" return self.ast_node_class_overrides.get(node_cls.__qualname__, node_cls) # type: ignore[return-value] @@ -265,39 +271,29 @@ def aperture_block(self) -> pp.ParserElement: def ab_open(self) -> pp.ParserElement: """Create a parser element capable of parsing AB-open.""" return ( - self.extended_command_open - + (pp.Literal("AB") + self.aperture_identifier + self._asterisk) + self._extended_command(pp.Literal("AB") + self.aperture_identifier) .set_name("ABopen") .set_parse_action(self.make_unpack_callback(ABopen)) - + self.extended_command_close ) @pp.cached_property def ab_close(self) -> pp.ParserElement: """Create a parser element capable of parsing AB-close.""" return ( - self.extended_command_open - + (pp.Literal("AB") + self._asterisk) + self._extended_command(pp.Literal("AB")) .set_name("ABclose") .set_parse_action(self.make_unpack_callback(ABclose)) - + self.extended_command_close ) def macro(self) -> pp.ParserElement: """Create a parser element capable of parsing macros.""" - return ( - self.extended_command_open - + self.am_open - + self.primitives - + self.am_close - + self.extended_command_close - ) + return self.am_open + self.primitives + self.am_close @pp.cached_property def am_open(self) -> pp.ParserElement: """Create a parser element capable of parsing AM-open.""" return ( - (pp.Literal("AM") + self.name + self._asterisk) + (self._extended + pp.Literal("AM") + self.name + self._asterisk) .set_name("AMopen") .set_parse_action(self.make_unpack_callback(AMopen)) ) @@ -305,10 +301,8 @@ def am_open(self) -> pp.ParserElement: @pp.cached_property def am_close(self) -> pp.ParserElement: """Create a parser element capable of parsing AM-close.""" - return ( - pp.Literal("") - .set_name("AM_close") - .set_parse_action(self.make_unpack_callback(AMclose)) + return self._extended.set_name("AM_close").set_parse_action( + self.make_unpack_callback(AMclose) ) def step_repeat(self) -> pp.ParserElement: @@ -324,59 +318,47 @@ def step_repeat(self) -> pp.ParserElement: def sr_open(self) -> pp.ParserElement: """Create a parser element capable of parsing SR-open.""" return ( - self.extended_command_open - + ( - ( - pp.Literal("SR") - + pp.Opt(pp.Literal("X") + self.double.set_results_name("x")) - + pp.Opt(pp.Literal("Y") + self.double.set_results_name("y")) - + pp.Opt(pp.Literal("I") + self.double.set_results_name("i")) - + pp.Opt(pp.Literal("J") + self.double.set_results_name("j")) - + self._asterisk - ) - .set_name("SRopen") - .set_parse_action(self.make_unpack_callback(SRopen)) + self._extended_command( + pp.Literal("SR") + + pp.Opt(pp.Literal("X") + self.double.set_results_name("x")) + + pp.Opt(pp.Literal("Y") + self.double.set_results_name("y")) + + pp.Opt(pp.Literal("I") + self.double.set_results_name("i")) + + pp.Opt(pp.Literal("J") + self.double.set_results_name("j")) ) - + self.extended_command_close + .set_name("SRopen") + .set_parse_action(self.make_unpack_callback(SRopen)) ) @pp.cached_property def sr_close(self) -> pp.ParserElement: """Create a parser element capable of parsing SR-close.""" return ( - self.extended_command_open - + (pp.Literal("SR") + self._asterisk) + self._extended_command(pp.Literal("SR")) .set_name("SRclose") .set_parse_action(self.make_unpack_callback(AMclose)) - + self.extended_command_close ) def add_aperture(self) -> pp.ParserElement: """Create a parser element capable of parsing add-aperture commands.""" - return ( - self.extended_command_open - + pp.MatchFirst( - [ - self.add_aperture_circle(), - self.add_aperture_rectangle("R", ADR), - self.add_aperture_rectangle("O", ADO), - self.add_aperture_polygon(), - self.add_aperture_macro(), - ] - ) - + self.extended_command_close + return pp.MatchFirst( + [ + self.add_aperture_circle(), + self.add_aperture_rectangle("R", ADR), + self.add_aperture_rectangle("O", ADO), + self.add_aperture_polygon(), + self.add_aperture_macro(), + ] ) def add_aperture_circle(self) -> pp.ParserElement: """Create a parser element capable of parsing add-aperture-circle commands.""" return ( - ( + self._extended_command( pp.Literal("AD") + self.aperture_identifier + pp.Literal("C,") + self.double.set_results_name("diameter") + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) - + self._asterisk ) .set_name("ADC") .set_parse_action(self.make_unpack_callback(ADC)) @@ -389,7 +371,7 @@ def add_aperture_rectangle( commands. """ return ( - ( + self._extended_command( pp.Literal("AD") + self.aperture_identifier + pp.Literal(f"{symbol},") @@ -397,7 +379,6 @@ def add_aperture_rectangle( + self._x + self.double.set_results_name("height") + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) - + self._asterisk ) .set_name(f"AD{symbol}") .set_parse_action(self.make_unpack_callback(cls)) @@ -408,7 +389,7 @@ def add_aperture_polygon(self) -> pp.ParserElement: commands. """ return ( - ( + self._extended_command( pp.Literal("AD") + self.aperture_identifier + pp.Literal("P,") @@ -417,7 +398,6 @@ def add_aperture_polygon(self) -> pp.ParserElement: + self.double.set_results_name("vertices") + pp.Opt(self._x + self.double.set_results_name("rotation")) + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) - + self._asterisk ) .set_name("ADP") .set_parse_action(self.make_unpack_callback(ADP)) @@ -430,12 +410,11 @@ def add_aperture_macro(self) -> pp.ParserElement: param = self.double.set_results_name("params", list_all_matches=True) return ( - ( + self._extended_command( pp.Literal("AD") + self.aperture_identifier + self.name.set_results_name("name") + pp.Opt(self.comma + param + pp.ZeroOrMore(self._x + param)) - + self._asterisk ) .set_name("ADmacro") .set_parse_action(self.make_unpack_callback(ADmacro)) @@ -464,30 +443,25 @@ def attribute(self) -> pp.ParserElement: def ta(self) -> pp.ParserElement: """Create a parser element capable of parsing TA attributes.""" - return ( - self.extended_command_open - + pp.MatchFirst( - [ - self._ta_user_name, - self._ta_aper_function, - self._ta_drill_tolerance, - self._ta_flash_text, - ] - ) - + self.extended_command_close + return pp.MatchFirst( + [ + self._ta_user_name, + self._ta_aper_function, + self._ta_drill_tolerance, + self._ta_flash_text, + ] ) @pp.cached_property def _ta_user_name(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._ta + self.user_name + pp.ZeroOrMore( self.comma + self.field.set_results_name("fields", list_all_matches=True) ) - + self._asterisk ) .set_name("TA") .set_parse_action(self.make_unpack_callback(TA_UserName)) @@ -496,7 +470,7 @@ def _ta_user_name(self) -> pp.ParserElement: @pp.cached_property def _ta_aper_function(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._ta + pp.Literal(".AperFunction") + pp.Optional( @@ -509,7 +483,6 @@ def _ta_aper_function(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) - + self._asterisk ) .set_name("TA.AperFunction") .set_parse_action(self.make_unpack_callback(TA_AperFunction)) @@ -518,7 +491,7 @@ def _ta_aper_function(self) -> pp.ParserElement: @pp.cached_property def _ta_drill_tolerance(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._ta + pp.Literal(".DrillTolerance") + pp.Optional( @@ -528,7 +501,6 @@ def _ta_drill_tolerance(self) -> pp.ParserElement: self.comma + self.double.set_results_name("minus_tolerance") ) ) - + self._asterisk ) .set_name("TA.DrillTolerance") .set_parse_action(self.make_unpack_callback(TA_DrillTolerance)) @@ -537,7 +509,7 @@ def _ta_drill_tolerance(self) -> pp.ParserElement: @pp.cached_property def _ta_flash_text(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._ta + pp.Literal(".FlashText") + self.comma @@ -554,7 +526,6 @@ def _ta_flash_text(self) -> pp.ParserElement: self.comma + self.field.set_results_name("comments", list_all_matches=True) ) - + self._asterisk ) .set_name("TA.FlashText") .set_parse_action(self.make_unpack_callback(TA_FlashText)) @@ -567,50 +538,39 @@ def _ta(self) -> pp.ParserElement: def td(self) -> pp.ParserElement: """Create a parser element capable of parsing TD attributes.""" return ( - self.extended_command_open - + ( - ( - pp.Literal("TD") - + pp.Opt(self.string.set_results_name("name")) - + self._asterisk - ) - .set_name("TD") - .set_parse_action(self.make_unpack_callback(TD)) + self._extended_command( + pp.Literal("TD") + pp.Opt(self.string.set_results_name("name")) ) - + self.extended_command_close + .set_name("TD") + .set_parse_action(self.make_unpack_callback(TD)) ) def tf(self) -> pp.ParserElement: """Create a parser element capable of parsing TF attributes.""" - return ( - self.extended_command_open - + pp.MatchFirst( - [ - self._tf_user_name, - self._tf_part, - self._tf_file_function, - self._tf_file_polarity, - self._tf_same_coordinates, - self._tf_creation_date, - self._tf_generation_software, - self._tf_project_id, - self._tf_md5, - ] - ) - + self.extended_command_close + return pp.MatchFirst( + [ + self._tf_user_name, + self._tf_part, + self._tf_file_function, + self._tf_file_polarity, + self._tf_same_coordinates, + self._tf_creation_date, + self._tf_generation_software, + self._tf_project_id, + self._tf_md5, + ] ) @pp.cached_property def _tf_user_name(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + self.user_name + pp.ZeroOrMore( self.comma + self.field.set_results_name("fields", list_all_matches=True) ) - + self._asterisk ) .set_name("TF") .set_parse_action(self.make_unpack_callback(TF_UserName)) @@ -619,7 +579,7 @@ def _tf_user_name(self) -> pp.ParserElement: @pp.cached_property def _tf_part(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".Part") + self.comma @@ -628,7 +588,6 @@ def _tf_part(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) - + self._asterisk ) .set_name("TF.Part") .set_parse_action(self.make_unpack_callback(TF_Part)) @@ -637,7 +596,7 @@ def _tf_part(self) -> pp.ParserElement: @pp.cached_property def _tf_file_function(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".FileFunction") + self.comma @@ -646,7 +605,6 @@ def _tf_file_function(self) -> pp.ParserElement: self.comma + self.field.set_results_name("fields", list_all_matches=True) ) - + self._asterisk ) .set_name("TF.FileFunction") .set_parse_action(self.make_unpack_callback(TF_FileFunction)) @@ -655,12 +613,11 @@ def _tf_file_function(self) -> pp.ParserElement: @pp.cached_property def _tf_file_polarity(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".FilePolarity") + self.comma + self.field.set_results_name("polarity") - + self._asterisk ) .set_name("TF.FilePolarity") .set_parse_action(self.make_unpack_callback(TF_FilePolarity)) @@ -669,11 +626,10 @@ def _tf_file_polarity(self) -> pp.ParserElement: @pp.cached_property def _tf_same_coordinates(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".SameCoordinates") + pp.Opt(self.comma + self.field.set_results_name("identifier")) - + self._asterisk ) .set_name("TF.SameCoordinates") .set_parse_action(self.make_unpack_callback(TF_SameCoordinates)) @@ -682,12 +638,11 @@ def _tf_same_coordinates(self) -> pp.ParserElement: @pp.cached_property def _tf_creation_date(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".CreationDate") + self.comma + self.field.set_results_name("creation_date") - + self._asterisk ) .set_name("TF.CreationDate") .set_parse_action(self.make_unpack_callback(TF_CreationDate)) @@ -696,7 +651,7 @@ def _tf_creation_date(self) -> pp.ParserElement: @pp.cached_property def _tf_generation_software(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".GenerationSoftware") + pp.Opt( @@ -708,7 +663,6 @@ def _tf_generation_software(self) -> pp.ParserElement: + pp.Opt(self.comma + self.field.set_results_name("version")) ) ) - + self._asterisk ) .set_name("TF.GenerationSoftware") .set_parse_action(self.make_unpack_callback(TF_GenerationSoftware)) @@ -717,7 +671,7 @@ def _tf_generation_software(self) -> pp.ParserElement: @pp.cached_property def _tf_project_id(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".ProjectId") + pp.Opt( @@ -729,7 +683,6 @@ def _tf_project_id(self) -> pp.ParserElement: + pp.Opt(self.comma + self.field.set_results_name("revision")) ) ) - + self._asterisk ) .set_name("TF.ProjectId") .set_parse_action(self.make_unpack_callback(TF_ProjectId)) @@ -738,12 +691,11 @@ def _tf_project_id(self) -> pp.ParserElement: @pp.cached_property def _tf_md5(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._tf + pp.CaselessLiteral(".MD5") + self.comma + self.field.set_results_name("md5") - + self._asterisk ) .set_name("TF.MD5") .set_parse_action(self.make_unpack_callback(TF_MD5)) @@ -755,42 +707,37 @@ def _tf(self) -> pp.ParserElement: def to(self) -> pp.ParserElement: """Create a parser element capable of parsing TO attributes.""" - return ( - self.extended_command_open - + pp.MatchFirst( - [ - self._to_user_name, - self._to_n, - self._to_p, - self._to_c, - self._to_crot, - self._to_cmfr, - self._to_cmpn, - self._to_cval, - self._to_cmnt, - self._to_cftp, - self._to_cpgn, - self._to_cpgd, - self._to_chgt, - self._to_clbn, - self._to_clbd, - self._to_csup, - ] - ) - + self.extended_command_close + return pp.MatchFirst( + [ + self._to_user_name, + self._to_n, + self._to_p, + self._to_c, + self._to_crot, + self._to_cmfr, + self._to_cmpn, + self._to_cval, + self._to_cmnt, + self._to_cftp, + self._to_cpgn, + self._to_cpgd, + self._to_chgt, + self._to_clbn, + self._to_clbd, + self._to_csup, + ] ) @pp.cached_property def _to_user_name(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + self.user_name + pp.ZeroOrMore( self.comma + self.field.set_results_name("fields", list_all_matches=True) ) - + self._asterisk ) .set_name("TO") .set_parse_action(self.make_unpack_callback(TO_UserName)) @@ -799,14 +746,13 @@ def _to_user_name(self) -> pp.ParserElement: @pp.cached_property def _to_n(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".N") + pp.ZeroOrMore( self.comma + self.field.set_results_name("net_names", list_all_matches=True) ) - + self._asterisk ) .set_name("TO.N") .set_parse_action(self.make_unpack_callback(TO_N)) @@ -815,7 +761,7 @@ def _to_n(self) -> pp.ParserElement: @pp.cached_property def _to_p(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".P") + self.comma @@ -823,7 +769,6 @@ def _to_p(self) -> pp.ParserElement: + self.comma + self.field.set_results_name("number") + pp.Opt(self.comma + self.field.set_results_name("function")) - + self._asterisk ) .set_name("TO.P") .set_parse_action(self.make_unpack_callback(TO_P)) @@ -832,12 +777,11 @@ def _to_p(self) -> pp.ParserElement: @pp.cached_property def _to_c(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".C") + self.comma + self.field.set_results_name("refdes") - + self._asterisk ) .set_name("TO.C") .set_parse_action(self.make_unpack_callback(TO_C)) @@ -846,12 +790,11 @@ def _to_c(self) -> pp.ParserElement: @pp.cached_property def _to_crot(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CRot") + self.comma + self.field.set_results_name("angle") - + self._asterisk ) .set_name("TO.CRot") .set_parse_action(self.make_unpack_callback(TO_CRot)) @@ -860,12 +803,11 @@ def _to_crot(self) -> pp.ParserElement: @pp.cached_property def _to_cmfr(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CMfr") + self.comma + self.field.set_results_name("manufacturer") - + self._asterisk ) .set_name("TO.CMfr") .set_parse_action(self.make_unpack_callback(TO_CMfr)) @@ -874,12 +816,11 @@ def _to_cmfr(self) -> pp.ParserElement: @pp.cached_property def _to_cmpn(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CMPN") + self.comma + self.field.set_results_name("part_number") - + self._asterisk ) .set_name("TO.CMPN") .set_parse_action(self.make_unpack_callback(TO_CMNP)) @@ -888,12 +829,11 @@ def _to_cmpn(self) -> pp.ParserElement: @pp.cached_property def _to_cval(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CVal") + self.comma + self.field.set_results_name("value") - + self._asterisk ) .set_name("TO.CVal") .set_parse_action(self.make_unpack_callback(TO_CVal)) @@ -902,12 +842,11 @@ def _to_cval(self) -> pp.ParserElement: @pp.cached_property def _to_cmnt(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CMnt") + self.comma + self.field.set_results_name("mount") - + self._asterisk ) .set_name("TO.CMnt") .set_parse_action(self.make_unpack_callback(TO_CMnt)) @@ -916,12 +855,11 @@ def _to_cmnt(self) -> pp.ParserElement: @pp.cached_property def _to_cftp(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CFtp") + self.comma + self.field.set_results_name("footprint") - + self._asterisk ) .set_name("TO.CFtp") .set_parse_action(self.make_unpack_callback(TO_CFtp)) @@ -930,12 +868,11 @@ def _to_cftp(self) -> pp.ParserElement: @pp.cached_property def _to_cpgn(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CPgN") + self.comma + self.field.set_results_name("name") - + self._asterisk ) .set_name("TO.CPgN") .set_parse_action(self.make_unpack_callback(TO_CPgN)) @@ -944,12 +881,11 @@ def _to_cpgn(self) -> pp.ParserElement: @pp.cached_property def _to_cpgd(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CPgD") + self.comma + self.field.set_results_name("description") - + self._asterisk ) .set_name("TO.CPgD") .set_parse_action(self.make_unpack_callback(TO_CPgD)) @@ -958,12 +894,11 @@ def _to_cpgd(self) -> pp.ParserElement: @pp.cached_property def _to_chgt(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CHgt") + self.comma + self.field.set_results_name("height") - + self._asterisk ) .set_name("TO.CHgt") .set_parse_action(self.make_unpack_callback(TO_CHgt)) @@ -972,12 +907,11 @@ def _to_chgt(self) -> pp.ParserElement: @pp.cached_property def _to_clbn(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CLbn") + self.comma + self.field.set_results_name("name") - + self._asterisk ) .set_name("TO.CLbn") .set_parse_action(self.make_unpack_callback(TO_CLbN)) @@ -986,12 +920,11 @@ def _to_clbn(self) -> pp.ParserElement: @pp.cached_property def _to_clbd(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CLbD") + self.comma + self.field.set_results_name("description") - + self._asterisk ) .set_name("TO.CLbD") .set_parse_action(self.make_unpack_callback(TO_CLbD)) @@ -1000,7 +933,7 @@ def _to_clbd(self) -> pp.ParserElement: @pp.cached_property def _to_csup(self) -> pp.ParserElement: return ( - ( + self._extended_command( self._to + pp.CaselessLiteral(".CSup") + self.comma @@ -1013,7 +946,6 @@ def _to_csup(self) -> pp.ParserElement: "other_suppliers", list_all_matches=True ) ) - + self._asterisk ) .set_name("TO.CSup") .set_parse_action(self.make_unpack_callback(TO_CSup)) @@ -1112,19 +1044,7 @@ def g_codes(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(G54)) ) g55 = ( - ( - pp.Regex(r"G0*55") - + ( - ( - pp.Opt(self._coordinate_x.set_results_name("x")) - + pp.Opt(self._coordinate_y.set_results_name("y")) - + pp.Regex(r"D0*3") - + self._asterisk - ) - .set_parse_action(self.make_unpack_callback(D03)) - .set_name("D03") - ).set_results_name("flash") - ) + (pp.Regex(r"G0*55") + self._d03.set_results_name("flash")) .set_name("G55") .set_parse_action(self.make_unpack_callback(G55)) ) @@ -1361,26 +1281,6 @@ def _coordinate_j(self) -> pp.ParserElement: .set_name("coordinate.j") ) - @pp.cached_property - def extended_command_open(self) -> pp.ParserElement: - """Create a parser element capable of parsing the extended command open.""" - parser = pp.Literal(r"%").set_name("%") - - if self.optimization & Optimization.DISCARD_COMMAND_BOUNDARIES: - return pp.Suppress(parser) - - return parser.set_parse_action(self.make_unpack_callback(ExtendedCommandOpen)) - - @pp.cached_property - def extended_command_close(self) -> pp.ParserElement: - """Create a parser element capable of parsing the extended command close.""" - parser = pp.Literal(r"%").set_name("%") - - if self.optimization & Optimization.DISCARD_COMMAND_BOUNDARIES: - return pp.Suppress(parser) - - return parser.set_parse_action(self.make_unpack_callback(ExtendedCommandClose)) - # ██████ ██████ ██ ███ ███ ██ ████████ ██ ██ ██ ███████ ███████ # ██ ██ ██ ██ ██ ████ ████ ██ ██ ██ ██ ██ ██ ██ # ██████ ██████ ██ ██ ████ ██ ██ ██ ██ ██ ██ █████ ███████ @@ -1583,8 +1483,7 @@ def properties(self) -> pp.ParserElement: def fs(self) -> pp.ParserElement: """Create a parser for the FS command.""" return ( - self.extended_command_open - + ( + self._extended_command( pp.Literal("FS") + pp.one_of(("L", "T")).set_results_name("zeros") + pp.one_of(("I", "A")).set_results_name("coordinate_mode") @@ -1594,55 +1493,40 @@ def fs(self) -> pp.ParserElement: + pp.CaselessLiteral("Y") + pp.Regex(r"[0-9]").set_results_name("y_integral") + pp.Regex(r"[0-9]").set_results_name("y_decimal") - + self._asterisk ) .set_parse_action(self.make_unpack_callback(FS)) .set_name("FS") - + self.extended_command_close ) def ip(self) -> pp.ParserElement: """Create a parser for the IP command.""" return ( - self.extended_command_open - + ( + self._extended_command( pp.Literal("IP") + pp.one_of(("POS", "NEG")).set_results_name("polarity") - + self._asterisk ) .set_parse_action(self.make_unpack_callback(IP)) .set_name("IP") - + self.extended_command_close ) def ir(self) -> pp.ParserElement: """Create a parser for the IR command.""" return ( - self.extended_command_open - + ( - pp.Literal("IR") - + self.double.set_results_name("rotation_degrees") - + self._asterisk + self._extended_command( + pp.Literal("IR") + self.double.set_results_name("rotation_degrees") ) .set_parse_action(self.make_unpack_callback(IR)) .set_name("IR") - + self.extended_command_close ) def mo(self) -> pp.ParserElement: """Create a parser for the MO command.""" return ( - self.extended_command_open - + ( - ( - pp.Literal("MO") - + pp.one_of(["IN", "MM"]).set_results_name("mode") - + self._asterisk - ) - .set_parse_action(self.make_unpack_callback(MO)) - .set_name("MO") + self._extended_command( + pp.Literal("MO") + pp.one_of(["IN", "MM"]).set_results_name("mode") ) - + self.extended_command_close + .set_parse_action(self.make_unpack_callback(MO)) + .set_name("MO") ) From ffa88a5bf6ab2a0938213efb7e2263d3fb0068b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 13 Aug 2024 19:24:33 +0200 Subject: [PATCH 28/91] Replace direct use of self._asterisk with calls to self._command() --- .../gerberx3/parser/pyparsing/grammar.py | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 0855fc66..f5f0f5fe 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -117,7 +117,6 @@ class Optimization(IntFlag): """Namespace class holding optimization level constants.""" - DISCARD_COMMAND_BOUNDARIES = 0b0000_0001 DISCARD_COMMENTS = 0b0000_0010 DISCARD_ATTRIBUTES = 0b0000_0100 @@ -975,7 +974,7 @@ def d_codes(self) -> pp.ParserElement: @pp.cached_property def _dnn(self) -> pp.ParserElement: return ( - (self.aperture_identifier.set_results_name("value") + self._asterisk) + self._command(self.aperture_identifier.set_results_name("value")) .set_parse_action(self.make_unpack_callback(Dnn)) .set_name("Dnn") ) @@ -983,13 +982,12 @@ def _dnn(self) -> pp.ParserElement: @pp.cached_property def _d01(self) -> pp.ParserElement: return ( - ( + self._command( pp.Opt(self._coordinate_x.set_results_name("x")) + pp.Opt(self._coordinate_y.set_results_name("y")) + pp.Opt(self._coordinate_i.set_results_name("i")) + pp.Opt(self._coordinate_j.set_results_name("j")) + pp.Regex(r"D0*1") - + self._asterisk ) .set_parse_action(self.make_unpack_callback(D01)) .set_name("D01") @@ -998,11 +996,10 @@ def _d01(self) -> pp.ParserElement: @pp.cached_property def _d02(self) -> pp.ParserElement: return ( - ( + self._command( self._coordinate_x.set_results_name("x") + self._coordinate_y.set_results_name("y") + pp.Regex(r"D0*2") - + self._asterisk ) .set_parse_action(self.make_unpack_callback(D02)) .set_name("D02") @@ -1011,11 +1008,10 @@ def _d02(self) -> pp.ParserElement: @pp.cached_property def _d03(self) -> pp.ParserElement: return ( - ( + self._command( pp.Opt(self._coordinate_x.set_results_name("x")) + pp.Opt(self._coordinate_y.set_results_name("y")) + pp.Regex(r"D0*3") - + self._asterisk ) .set_parse_action(self.make_unpack_callback(D03)) .set_name("D03") @@ -1029,8 +1025,8 @@ def _d03(self) -> pp.ParserElement: def g_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing G-codes.""" - g04_comment = ( - pp.Regex(r"G0*4") + pp.Opt(self.string) + self._asterisk + g04_comment = self._command( + pp.Regex(r"G0*4") + pp.Opt(self.string), ).set_name("G04") if self.optimization & Optimization.DISCARD_COMMENTS: @@ -1081,7 +1077,7 @@ def g_codes(self) -> pp.ParserElement: def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: """Create a parser element capable of parsing particular G-code.""" return ( - (pp.Regex(r"G0*" + str(value)) + self._asterisk) + self._command(pp.Regex(r"G0*" + str(value))) .set_name(f"G{value}") .set_parse_action(self.make_unpack_callback(cls)) ) @@ -1117,7 +1113,7 @@ def m_codes(self) -> pp.ParserElement: def m(self, value: int, cls: Type[Node]) -> pp.ParserElement: """Create a parser element capable of parsing particular D-code.""" return ( - (pp.Regex(r"M0*" + str(value)) + self._asterisk) + self._command(pp.Regex(r"M0*" + str(value))) .set_name(f"M{value}") .set_parse_action(self.make_unpack_callback(cls)) ) @@ -1233,11 +1229,10 @@ def variable(self) -> pp.ParserElement: def assignment(self) -> pp.ParserElement: """Create a parser element capable of parsing assignments.""" return ( - ( + self._command( self.variable + pp.Suppress("=") + self.expression.set_results_name("expression") - + self._asterisk ) .set_results_name("assignment") .set_parse_action(self.make_unpack_callback(Assignment)) @@ -1465,7 +1460,7 @@ def primitive( ) -> pp.ParserElement: """Create a parser element capable of parsing a primitive.""" return ( - (pp.Literal(str(code)) + fields + self._asterisk) + self._command(pp.Literal(str(code)) + fields) .set_name(f"primitive-{code}") .set_parse_action(self.make_unpack_callback(cls)) ) From 6bd6021e24100c040c76f702eb61f04650ff2197 Mon Sep 17 00:00:00 2001 From: Sander de Regt Date: Tue, 13 Aug 2024 20:27:26 +0200 Subject: [PATCH 29/91] Add implementation of LN node parsing --- src/pygerber/gerberx3/ast/nodes/load/LN.py | 2 ++ .../gerberx3/parser/pyparsing/grammar.py | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LN.py b/src/pygerber/gerberx3/ast/nodes/load/LN.py index e6ba4088..bbea2b7b 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LN.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LN.py @@ -15,6 +15,8 @@ class LN(Node): """Represents LN Gerber extended command.""" + name: str + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ln(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index f5f0f5fe..2650b463 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -76,6 +76,7 @@ from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 +from pygerber.gerberx3.ast.nodes.load.LN import LN from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 @@ -152,6 +153,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: self.aperture(), self.attribute(), self.g_codes(), + self.load_commands(), self.m_codes(), self.d_codes(), self.properties(), @@ -1088,6 +1090,20 @@ def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # noqa: E501 # ██████ ███████ ██ ██ ██████ █████ ███████ ██ ██ ██ ██ ██ ██ ██ ████ ██████ ███████ # noqa: E501 + def load_commands(self) -> pp.ParserElement: + """Create a parser element capable of parsing Load-commands.""" + return pp.MatchFirst([self.ln()]) + + def ln(self) -> pp.ParserElement: + """Create a parser for the LN command.""" + return ( + self._extended_command( + pp.Literal("LN") + self.string.set_results_name("name") + ) + .set_parse_action(self.make_unpack_callback(LN)) + .set_name("LN") + ) + # ███ ███ █████ ███████ ██████ ███████ ███████ # ████ ████ ██ ██ ██ ██ ██ ██ ██ # ██ ████ ██ ██ ██ ██ ██ ██ █████ ███████ From 3240b62cbfaf203f545ebd9b65d1f70be5559f41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 13 Aug 2024 22:08:57 +0200 Subject: [PATCH 30/91] Add OF command --- .../gerberx3/ast/nodes/properties/OF.py | 5 ++++- .../gerberx3/parser/pyparsing/grammar.py | 22 +++++++++++++------ .../gerberx3/parser/pyparsing/parser.py | 5 +---- test/gerberx3/test_ast/test_ast_visitor.py | 4 ++-- 4 files changed, 22 insertions(+), 14 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/OF.py b/src/pygerber/gerberx3/ast/nodes/properties/OF.py index 18cbc48c..6b4fd771 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/OF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/OF.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, Optional from pygerber.gerberx3.ast.nodes.base import Node @@ -15,6 +15,9 @@ class OF(Node): """Represents OF Gerber extended command.""" + a_offset: Optional[float] + b_offset: Optional[float] + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_of(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 2650b463..25e1faa3 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -5,7 +5,7 @@ from __future__ import annotations from enum import IntFlag -from typing import Callable, ClassVar, List, Literal, Type, TypeVar, cast +from typing import Callable, List, Literal, Type, TypeVar, cast import pyparsing as pp @@ -111,6 +111,7 @@ from pygerber.gerberx3.ast.nodes.properties.IP import IP from pygerber.gerberx3.ast.nodes.properties.IR import IR from pygerber.gerberx3.ast.nodes.properties.MO import MO +from pygerber.gerberx3.ast.nodes.properties.OF import OF T = TypeVar("T", bound=Node) @@ -125,15 +126,13 @@ class Optimization(IntFlag): class Grammar: """Internal representation of the Gerber X3 grammar.""" - DEFAULT: ClassVar[pp.ParserElement] - def __init__( self, ast_node_class_overrides: dict[str, Type[Node]], *, enable_packrat: bool = True, enable_debug: bool = False, - optimization: int = 1, + optimization: int = 0, ) -> None: self.ast_node_class_overrides = ast_node_class_overrides self.enable_packrat = enable_packrat @@ -1489,7 +1488,7 @@ def primitive( def properties(self) -> pp.ParserElement: """Create a parser element capable of parsing Properties-commands.""" - return pp.MatchFirst([self.fs(), self.mo(), self.ip(), self.ir()]) + return pp.MatchFirst([self.fs(), self.mo(), self.ip(), self.ir(), self.of()]) def fs(self) -> pp.ParserElement: """Create a parser for the FS command.""" @@ -1540,5 +1539,14 @@ def mo(self) -> pp.ParserElement: .set_name("MO") ) - -Grammar.DEFAULT = Grammar({}).build() + def of(self) -> pp.ParserElement: + """Create a parser for the MO command.""" + return ( + self._extended_command( + pp.Literal("OF") + + pp.Opt(pp.Literal("A") + self.double.set_results_name("a_offset")) + + pp.Opt(pp.Literal("B") + self.double.set_results_name("b_offset")) + ) + .set_parse_action(self.make_unpack_callback(OF)) + .set_name("OF") + ) diff --git a/src/pygerber/gerberx3/parser/pyparsing/parser.py b/src/pygerber/gerberx3/parser/pyparsing/parser.py index f3244c12..55cba06c 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/parser.py +++ b/src/pygerber/gerberx3/parser/pyparsing/parser.py @@ -17,10 +17,7 @@ class Parser: def __init__( self, ast_node_class_overrides: Optional[dict[str, Type[Node]]] = None ) -> None: - if ast_node_class_overrides is not None: - self.grammar = Grammar(ast_node_class_overrides).build() - else: - self.grammar = Grammar.DEFAULT + self.grammar = Grammar(ast_node_class_overrides or {}).build() def parse(self, code: str, *, strict: bool = True) -> File: """Parse the input.""" diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 2be40978..2d2ef89d 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -263,7 +263,7 @@ G90: G90(source="", location=0), G91: G91(source="", location=0), LM: LM(source="", location=0), - LN: LN(source="", location=0), + LN: LN(source="", location=0, name="name"), LP: LP(source="", location=0), LR: LR(source="", location=0), LS: LS(source="", location=0), @@ -447,7 +447,7 @@ IR: IR(source="", location=0, rotation_degrees=90), MI: MI(source="", location=0), MO: MO(source="", location=0, mode=UnitMode.METRIC), - OF: OF(source="", location=0), + OF: OF(source="", location=0, a_offset=1, b_offset=2), SF: SF(source="", location=0), File: File(source="", location=0, nodes=[]), } From 543949d9f500a510c3c4b88325993b15bb6a5bac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 13 Aug 2024 22:14:21 +0200 Subject: [PATCH 31/91] Fix OF when A or B is not supplied --- src/pygerber/gerberx3/ast/nodes/properties/OF.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/OF.py b/src/pygerber/gerberx3/ast/nodes/properties/OF.py index 6b4fd771..e19fead8 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/OF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/OF.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Callable, Optional +from pydantic import Field + from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: @@ -15,8 +17,8 @@ class OF(Node): """Represents OF Gerber extended command.""" - a_offset: Optional[float] - b_offset: Optional[float] + a_offset: Optional[float] = Field(default=None) + b_offset: Optional[float] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" From 95831fc699a423e30dddf7e5643e02449b601312 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 14 Aug 2024 00:21:40 +0200 Subject: [PATCH 32/91] Add AS node parsing --- src/pygerber/gerberx3/ast/nodes/properties/AS.py | 10 ++++++++++ src/pygerber/gerberx3/parser/pyparsing/grammar.py | 15 ++++++++++++++- test/gerberx3/test_ast/test_ast_visitor.py | 4 ++-- 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/AS.py b/src/pygerber/gerberx3/ast/nodes/properties/AS.py index 58dd946c..04375bea 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/AS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/AS.py @@ -2,6 +2,7 @@ from __future__ import annotations +from enum import Enum from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node @@ -12,9 +13,18 @@ from pygerber.gerberx3.ast.visitor import AstVisitor +class AxisCorrespondence(Enum): + """Represents axis correspondence.""" + + AX_BY = "AXBY" + AY_BX = "AYBX" + + class AS(Node): """Represents AS Gerber extended command.""" + correspondence: AxisCorrespondence + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_as(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 25e1faa3..65bb6e1d 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -1488,7 +1488,9 @@ def primitive( def properties(self) -> pp.ParserElement: """Create a parser element capable of parsing Properties-commands.""" - return pp.MatchFirst([self.fs(), self.mo(), self.ip(), self.ir(), self.of()]) + return pp.MatchFirst( + [self.fs(), self.mo(), self.ip(), self.ir(), self.of(), self.as_()] + ) def fs(self) -> pp.ParserElement: """Create a parser for the FS command.""" @@ -1550,3 +1552,14 @@ def of(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(OF)) .set_name("OF") ) + + def as_(self) -> pp.ParserElement: + """Create a parser element capable of parsing AS-commands.""" + return ( + self._extended_command( + pp.Literal("AS") + + pp.one_of(["AXBY", "AYBX"]).set_results_name("correspondence") + ) + .set_parse_action(self.make_unpack_callback(OF)) + .set_name("OF") + ) diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 2d2ef89d..df7869d2 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -112,7 +112,7 @@ from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 -from pygerber.gerberx3.ast.nodes.properties.AS import AS +from pygerber.gerberx3.ast.nodes.properties.AS import AS, AxisCorrespondence from pygerber.gerberx3.ast.nodes.properties.FS import FS from pygerber.gerberx3.ast.nodes.properties.IN import IN from pygerber.gerberx3.ast.nodes.properties.IP import IP @@ -431,7 +431,7 @@ y_lower_left=Constant(source="", location=0, constant="5"), rotation=Constant(source="", location=0, constant="6"), ), - AS: AS(source="", location=0), + AS: AS(source="", location=0, correspondence=AxisCorrespondence.AX_BY), FS: FS( source="", location=0, From eb564b2277c07c2e1f8de802be5d3e3de8deab6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 14 Aug 2024 00:36:23 +0200 Subject: [PATCH 33/91] Add MI command parsing --- .../gerberx3/ast/nodes/properties/MI.py | 5 ++++ .../gerberx3/parser/pyparsing/grammar.py | 28 ++++++++++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MI.py b/src/pygerber/gerberx3/ast/nodes/properties/MI.py index f868ae72..5447fcba 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/MI.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/MI.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Callable +from pydantic import Field + from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: @@ -15,6 +17,9 @@ class MI(Node): """Represents MI Gerber extended command.""" + a_mirroring: int = Field(default=0) + b_mirroring: int = Field(default=0) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_mi(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 65bb6e1d..4169ba5d 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -110,6 +110,7 @@ from pygerber.gerberx3.ast.nodes.properties.FS import FS from pygerber.gerberx3.ast.nodes.properties.IP import IP from pygerber.gerberx3.ast.nodes.properties.IR import IR +from pygerber.gerberx3.ast.nodes.properties.MI import MI from pygerber.gerberx3.ast.nodes.properties.MO import MO from pygerber.gerberx3.ast.nodes.properties.OF import OF @@ -226,6 +227,11 @@ def integer(self) -> pp.ParserElement: """Create a parser element capable of parsing integers.""" return pp.Regex(r"[+-]?[0-9]+").set_results_name("integer") + @pp.cached_property + def boolean(self) -> pp.ParserElement: + """Create a parser element capable of parsing integers.""" + return pp.one_of(("0", "1")).set_results_name("boolean") + @pp.cached_property def aperture_identifier(self) -> pp.ParserElement: """Create a parser element capable of parsing aperture identifiers.""" @@ -1489,7 +1495,15 @@ def primitive( def properties(self) -> pp.ParserElement: """Create a parser element capable of parsing Properties-commands.""" return pp.MatchFirst( - [self.fs(), self.mo(), self.ip(), self.ir(), self.of(), self.as_()] + [ + self.fs(), + self.mo(), + self.ip(), + self.ir(), + self.of(), + self.as_(), + self.mi(), + ] ) def fs(self) -> pp.ParserElement: @@ -1563,3 +1577,15 @@ def as_(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(OF)) .set_name("OF") ) + + def mi(self) -> pp.ParserElement: + """Create a parser for the MI command.""" + return ( + self._extended_command( + pp.Literal("MI") + + pp.Opt(pp.Literal("A") + self.boolean.set_results_name("a_mirroring")) + + pp.Opt(pp.Literal("B") + self.boolean.set_results_name("b_mirroring")) + ) + .set_parse_action(self.make_unpack_callback(MI)) + .set_name("MI") + ) From 6c547d948d20d5b90c210918ac94bc23a18c2ca8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 14 Aug 2024 00:42:02 +0200 Subject: [PATCH 34/91] Add IN command parsing --- src/pygerber/gerberx3/ast/nodes/properties/IN.py | 4 ++++ src/pygerber/gerberx3/parser/pyparsing/grammar.py | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IN.py b/src/pygerber/gerberx3/ast/nodes/properties/IN.py index e3af38b1..e202b7c7 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IN.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IN.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Callable +from pydantic import Field + from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: @@ -15,6 +17,8 @@ class IN(Node): """Represents IN Gerber extended command.""" + name: str = Field(default="") + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_in(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 4169ba5d..ea3d0eaf 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -108,6 +108,7 @@ from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 from pygerber.gerberx3.ast.nodes.properties.FS import FS +from pygerber.gerberx3.ast.nodes.properties.IN import IN from pygerber.gerberx3.ast.nodes.properties.IP import IP from pygerber.gerberx3.ast.nodes.properties.IR import IR from pygerber.gerberx3.ast.nodes.properties.MI import MI @@ -1503,6 +1504,7 @@ def properties(self) -> pp.ParserElement: self.of(), self.as_(), self.mi(), + self.in_(), ] ) @@ -1589,3 +1591,13 @@ def mi(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(MI)) .set_name("MI") ) + + def in_(self) -> pp.ParserElement: + """Create a parser for the IN command.""" + return ( + self._extended_command( + pp.Literal("IN") + self.string.set_results_name("name") + ) + .set_parse_action(self.make_unpack_callback(IN)) + .set_name("IN") + ) From 3a2895c8aa4739c68ca845cabea71273dfad4564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 14 Aug 2024 00:47:29 +0200 Subject: [PATCH 35/91] Add SF command parsing --- src/pygerber/gerberx3/ast/nodes/properties/SF.py | 5 +++++ src/pygerber/gerberx3/parser/pyparsing/grammar.py | 14 ++++++++++++++ test/gerberx3/test_ast/test_ast_visitor.py | 6 +++--- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/SF.py b/src/pygerber/gerberx3/ast/nodes/properties/SF.py index 9f4987af..34618ea9 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/SF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/SF.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Callable +from pydantic import Field + from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: @@ -15,6 +17,9 @@ class SF(Node): """Represents SF Gerber extended command.""" + a_scale: float = Field(default=1.0) + b_scale: float = Field(default=1.0) + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_sf(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index ea3d0eaf..85e05680 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -114,6 +114,7 @@ from pygerber.gerberx3.ast.nodes.properties.MI import MI from pygerber.gerberx3.ast.nodes.properties.MO import MO from pygerber.gerberx3.ast.nodes.properties.OF import OF +from pygerber.gerberx3.ast.nodes.properties.SF import SF T = TypeVar("T", bound=Node) @@ -1505,6 +1506,7 @@ def properties(self) -> pp.ParserElement: self.as_(), self.mi(), self.in_(), + self.sf(), ] ) @@ -1601,3 +1603,15 @@ def in_(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(IN)) .set_name("IN") ) + + def sf(self) -> pp.ParserElement: + """Create a parser for the SF command.""" + return ( + self._extended_command( + pp.Literal("SF") + + pp.Opt(pp.Literal("A") + self.double.set_results_name("a_scale")) + + pp.Opt(pp.Literal("B") + self.double.set_results_name("b_scale")) + ) + .set_parse_action(self.make_unpack_callback(SF)) + .set_name("SF") + ) diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index df7869d2..c3095ef4 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -442,13 +442,13 @@ y_integral=4, y_decimal=5, ), - IN: IN(source="", location=0), + IN: IN(source="", location=0, name="name"), IP: IP(source="", location=0, polarity="D"), IR: IR(source="", location=0, rotation_degrees=90), - MI: MI(source="", location=0), + MI: MI(source="", location=0, a_mirroring=0, b_mirroring=1), MO: MO(source="", location=0, mode=UnitMode.METRIC), OF: OF(source="", location=0, a_offset=1, b_offset=2), - SF: SF(source="", location=0), + SF: SF(source="", location=0, a_scale=1.0, b_scale=1.0), File: File(source="", location=0, nodes=[]), } From ffe4457a3c437f612eb55dee0c53b32a91a710ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 14 Aug 2024 13:42:55 +0200 Subject: [PATCH 36/91] Add support for formatting G codes --- .../gerberx3/ast/nodes/aperture/ADC.py | 5 +- .../gerberx3/ast/nodes/aperture/ADO.py | 7 +- .../gerberx3/ast/nodes/aperture/ADP.py | 9 +- .../gerberx3/ast/nodes/aperture/ADR.py | 7 +- .../gerberx3/ast/nodes/aperture/ADmacro.py | 3 +- src/pygerber/gerberx3/ast/nodes/base.py | 2 +- src/pygerber/gerberx3/ast/nodes/types.py | 11 + src/pygerber/gerberx3/ast/visitor.py | 214 ++++-- src/pygerber/gerberx3/formatter.py | 723 ++++++++++++++++++ test/gerberx3/test_ast/test_ast_visitor.py | 26 +- test/gerberx3/test_formatter.py | 86 +++ 11 files changed, 1009 insertions(+), 84 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/types.py create mode 100644 src/pygerber/gerberx3/formatter.py create mode 100644 test/gerberx3/test_formatter.py diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py index 1b8365c6..ca920474 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py @@ -7,6 +7,7 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -18,8 +19,8 @@ class ADC(Node): """Represents AD Gerber extended command.""" aperture_identifier: str - diameter: str - hole_diameter: Optional[str] = Field(default=None) + diameter: Double + hole_diameter: Optional[Double] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py index 70f318c4..46b251fa 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py @@ -7,6 +7,7 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -18,9 +19,9 @@ class ADO(Node): """Represents AD obround Gerber extended command.""" aperture_identifier: str - width: str - height: str - hole_diameter: Optional[str] = Field(default=None) + width: Double + height: Double + hole_diameter: Optional[Double] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py index 08c29bbe..2d6b04b0 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py @@ -7,6 +7,7 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double, Integer if TYPE_CHECKING: from typing_extensions import Self @@ -18,10 +19,10 @@ class ADP(Node): """Represents AD polygon Gerber extended command.""" aperture_identifier: str - outer_diameter: str - vertices: str - rotation: Optional[str] = Field(default=None) - hole_diameter: Optional[str] = Field(default=None) + outer_diameter: Double + vertices: Integer + rotation: Optional[Double] = Field(default=None) + hole_diameter: Optional[Double] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py index 74b420cd..eb18b47a 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py @@ -7,6 +7,7 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -18,9 +19,9 @@ class ADR(Node): """Represents AD rectangle Gerber extended command.""" aperture_identifier: str - width: str - height: str - hole_diameter: Optional[str] = Field(default=None) + width: Double + height: Double + hole_diameter: Optional[Double] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py index 1459f12e..85705340 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py @@ -7,6 +7,7 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -19,7 +20,7 @@ class ADmacro(Node): aperture_identifier: str name: str - params: Optional[List[str]] = Field(default=None) + params: Optional[List[Double]] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py index ee9547c4..f59138a6 100644 --- a/src/pygerber/gerberx3/ast/nodes/base.py +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -19,7 +19,7 @@ class Node(ModelType): """Base class for all nodes.""" source: str = Field(repr=False, exclude=True) - location: int = Field(repr=False) + location: int = Field(repr=False, exclude=True) @abstractmethod def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/types.py b/src/pygerber/gerberx3/ast/nodes/types.py new file mode 100644 index 00000000..eb796fe6 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/types.py @@ -0,0 +1,11 @@ +"""Basic types for AST nodes.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +if TYPE_CHECKING: + from typing_extensions import TypeAlias + +Double: TypeAlias = float +Integer: TypeAlias = int diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 239ea3ef..6d54bbce 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -2,49 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING - -from pygerber.gerberx3.ast.nodes.attribute.TA import ( - TA_AperFunction, - TA_DrillTolerance, - TA_FlashText, - TA_UserName, -) -from pygerber.gerberx3.ast.nodes.attribute.TF import ( - TF_MD5, - TF_CreationDate, - TF_FileFunction, - TF_FilePolarity, - TF_GenerationSoftware, - TF_Part, - TF_ProjectId, - TF_SameCoordinates, - TF_UserName, -) -from pygerber.gerberx3.ast.nodes.attribute.TO import ( - TO_C, - TO_CMNP, - TO_N, - TO_P, - TO_CFtp, - TO_CHgt, - TO_CLbD, - TO_CLbN, - TO_CMfr, - TO_CMnt, - TO_CPgD, - TO_CPgN, - TO_CRot, - TO_CSup, - TO_CVal, - TO_UserName, -) -from pygerber.gerberx3.ast.nodes.other.coordinate import ( - CoordinateI, - CoordinateJ, - CoordinateX, - CoordinateY, -) +from typing import TYPE_CHECKING, List, Optional if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose @@ -58,10 +16,46 @@ from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen - from pygerber.gerberx3.ast.nodes.attribute.TA import TA + from pygerber.gerberx3.ast.nodes.attribute.TA import ( + TA, + TA_AperFunction, + TA_DrillTolerance, + TA_FlashText, + TA_UserName, + ) from pygerber.gerberx3.ast.nodes.attribute.TD import TD - from pygerber.gerberx3.ast.nodes.attribute.TF import TF - from pygerber.gerberx3.ast.nodes.attribute.TO import TO + from pygerber.gerberx3.ast.nodes.attribute.TF import ( + TF, + TF_MD5, + TF_CreationDate, + TF_FileFunction, + TF_FilePolarity, + TF_GenerationSoftware, + TF_Part, + TF_ProjectId, + TF_SameCoordinates, + TF_UserName, + ) + from pygerber.gerberx3.ast.nodes.attribute.TO import ( + TO, + TO_C, + TO_CMNP, + TO_N, + TO_P, + TO_CFtp, + TO_CHgt, + TO_CLbD, + TO_CLbN, + TO_CMfr, + TO_CMnt, + TO_CPgD, + TO_CPgN, + TO_CRot, + TO_CSup, + TO_CVal, + TO_UserName, + ) + from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 @@ -100,7 +94,13 @@ from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable - from pygerber.gerberx3.ast.nodes.other.coordinate import Coordinate + from pygerber.gerberx3.ast.nodes.other.coordinate import ( + Coordinate, + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, + ) from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 @@ -130,6 +130,10 @@ class AstVisitor: https://refactoring.guru/design-patterns/visitor """ + def __init__(self) -> None: + self._current_node_index = -1 + self._nodes: List[Node] = [] + # Aperture def on_ab_close(self, node: ABclose) -> None: @@ -299,32 +303,32 @@ def on_to_csup(self, node: TO_CSup) -> None: def on_d01(self, node: D01) -> None: """Handle `D01` node.""" - if node.x: + if node.x is not None: node.x.visit(self) - if node.y: + if node.y is not None: node.y.visit(self) - if node.i: + if node.i is not None: node.i.visit(self) - if node.j: + if node.j is not None: node.j.visit(self) def on_d02(self, node: D02) -> None: """Handle `D02` node.""" - if node.x: + if node.x is not None: node.x.visit(self) - if node.y: + if node.y is not None: node.y.visit(self) def on_d03(self, node: D03) -> None: """Handle `D03` node.""" - if node.x: + if node.x is not None: node.x.visit(self) - if node.y: + if node.y is not None: node.y.visit(self) def on_dnn(self, node: Dnn) -> None: @@ -410,38 +414,60 @@ def on_m02(self, node: M02) -> None: def on_add(self, node: Add) -> None: """Handle `Add` node.""" + self.on_expression(node) + for operand in node.operands: + operand.visit(self) def on_div(self, node: Div) -> None: """Handle `Div` node.""" + self.on_expression(node) + for operand in node.operands: + operand.visit(self) def on_mul(self, node: Mul) -> None: """Handle `Mul` node.""" + self.on_expression(node) + for operand in node.operands: + operand.visit(self) def on_sub(self, node: Sub) -> None: """Handle `Sub` node.""" + self.on_expression(node) + for operand in node.operands: + operand.visit(self) # Math :: Operators :: Unary def on_neg(self, node: Neg) -> None: """Handle `Neg` node.""" + self.on_expression(node) + node.operand.visit(self) def on_pos(self, node: Pos) -> None: """Handle `Pos` node.""" + self.on_expression(node) + node.operand.visit(self) def on_assignment(self, node: Assignment) -> None: """Handle `Assignment` node.""" + node.variable.visit(self) + node.expression.visit(self) def on_constant(self, node: Constant) -> None: """Handle `Constant` node.""" + self.on_expression(node) def on_expression(self, node: Expression) -> None: """Handle `Expression` node.""" def on_point(self, node: Point) -> None: """Handle `Point` node.""" + node.x.visit(self) + node.y.visit(self) def on_variable(self, node: Variable) -> None: """Handle `Variable` node.""" + self.on_expression(node) # Other @@ -471,30 +497,90 @@ def on_code_0(self, node: Code0) -> None: def on_code_1(self, node: Code1) -> None: """Handle `Code1` node.""" + node.exposure.visit(self) + node.diameter.visit(self) + node.center_x.visit(self) + node.center_y.visit(self) + if node.rotation is not None: + node.rotation.visit(self) def on_code_2(self, node: Code2) -> None: """Handle `Code2` node.""" + node.exposure.visit(self) + node.width.visit(self) + node.start_x.visit(self) + node.start_y.visit(self) + node.end_x.visit(self) + node.end_y.visit(self) + node.rotation.visit(self) def on_code_4(self, node: Code4) -> None: """Handle `Code4` node.""" + node.exposure.visit(self) + node.number_of_points.visit(self) + node.start_x.visit(self) + node.start_y.visit(self) + for point in node.points: + point.visit(self) + node.rotation.visit(self) def on_code_5(self, node: Code5) -> None: """Handle `Code5` node.""" + node.exposure.visit(self) + node.number_of_vertices.visit(self) + node.center_x.visit(self) + node.center_y.visit(self) + node.diameter.visit(self) + node.rotation.visit(self) def on_code_6(self, node: Code6) -> None: """Handle `Code6` node.""" + node.center_x.visit(self) + node.center_y.visit(self) + node.outer_diameter.visit(self) + node.ring_thickness.visit(self) + node.gap_between_rings.visit(self) + node.max_ring_count.visit(self) + node.crosshair_thickness.visit(self) + node.crosshair_length.visit(self) + node.rotation.visit(self) def on_code_7(self, node: Code7) -> None: """Handle `Code7` node.""" + node.center_x.visit(self) + node.center_y.visit(self) + node.outer_diameter.visit(self) + node.inner_diameter.visit(self) + node.gap_thickness.visit(self) + node.rotation.visit(self) def on_code_20(self, node: Code20) -> None: """Handle `Code20` node.""" + node.exposure.visit(self) + node.width.visit(self) + node.start_x.visit(self) + node.start_y.visit(self) + node.end_x.visit(self) + node.end_y.visit(self) + node.rotation.visit(self) def on_code_21(self, node: Code21) -> None: """Handle `Code21` node.""" + node.exposure.visit(self) + node.width.visit(self) + node.height.visit(self) + node.center_x.visit(self) + node.center_y.visit(self) + node.rotation.visit(self) def on_code_22(self, node: Code22) -> None: """Handle `Code22` node.""" + node.exposure.visit(self) + node.width.visit(self) + node.height.visit(self) + node.x_lower_left.visit(self) + node.y_lower_left.visit(self) + node.rotation.visit(self) # Properties @@ -529,5 +615,19 @@ def on_sf(self, node: SF) -> None: def on_file(self, node: File) -> None: """Handle `File` node.""" - for command in node.nodes: - command.visit(self) + self._current_node_index = 0 + self._nodes = node.nodes + try: + for command in node.nodes: + command.visit(self) + finally: + self._current_node_index = -1 + self._nodes = [] + + def get_next_node(self) -> Optional[Node]: + """Get next node from the iterator.""" + if -1 < self._current_node_index < len(self._nodes): + node = self._nodes[self._current_node_index] + self._current_node_index += 1 + return node + return None diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py new file mode 100644 index 00000000..075b595d --- /dev/null +++ b/src/pygerber/gerberx3/formatter.py @@ -0,0 +1,723 @@ +"""`pygerber.gerberx3.formatter` module contains implementation `Formatter` class +which implements configurable Gerber code formatting. +""" + +from __future__ import annotations + +from contextlib import contextmanager +from typing import TYPE_CHECKING, Generator, Literal, Optional + +from pyparsing import cached_property + +from pygerber.gerberx3.ast.visitor import AstVisitor + +if TYPE_CHECKING: + from io import StringIO + + from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose + from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen + from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC + from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro + from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO + from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP + from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR + from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose + from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen + from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose + from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen + from pygerber.gerberx3.ast.nodes.attribute.TA import ( + TA_AperFunction, + TA_DrillTolerance, + TA_FlashText, + TA_UserName, + ) + from pygerber.gerberx3.ast.nodes.attribute.TD import TD + from pygerber.gerberx3.ast.nodes.attribute.TF import ( + TF_MD5, + TF_CreationDate, + TF_FileFunction, + TF_FilePolarity, + TF_GenerationSoftware, + TF_Part, + TF_ProjectId, + TF_SameCoordinates, + TF_UserName, + ) + from pygerber.gerberx3.ast.nodes.attribute.TO import ( + TO_C, + TO_CMNP, + TO_N, + TO_P, + TO_CFtp, + TO_CHgt, + TO_CLbD, + TO_CLbN, + TO_CMfr, + TO_CMnt, + TO_CPgD, + TO_CPgN, + TO_CRot, + TO_CSup, + TO_CVal, + TO_UserName, + ) + from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 + from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 + from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 + from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn + from pygerber.gerberx3.ast.nodes.file import File + from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 + from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 + from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 + from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 + from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 + from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 + from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 + from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 + from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 + from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 + from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 + from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 + from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 + from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 + from pygerber.gerberx3.ast.nodes.load.LM import LM + from pygerber.gerberx3.ast.nodes.load.LN import LN + from pygerber.gerberx3.ast.nodes.load.LP import LP + from pygerber.gerberx3.ast.nodes.load.LR import LR + from pygerber.gerberx3.ast.nodes.load.LS import LS + from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 + from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 + from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 + from pygerber.gerberx3.ast.nodes.math.assignment import Assignment + from pygerber.gerberx3.ast.nodes.math.constant import Constant + from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add + from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div + from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul + from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub + from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg + from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos + from pygerber.gerberx3.ast.nodes.math.point import Point + from pygerber.gerberx3.ast.nodes.math.variable import Variable + from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, + ) + from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 + from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 + from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 + from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 + from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 + from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 + from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 + from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 + from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 + from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 + from pygerber.gerberx3.ast.nodes.properties.AS import AS + from pygerber.gerberx3.ast.nodes.properties.FS import FS + from pygerber.gerberx3.ast.nodes.properties.IN import IN + from pygerber.gerberx3.ast.nodes.properties.IP import IP + from pygerber.gerberx3.ast.nodes.properties.IR import IR + from pygerber.gerberx3.ast.nodes.properties.MI import MI + from pygerber.gerberx3.ast.nodes.properties.MO import MO + from pygerber.gerberx3.ast.nodes.properties.OF import OF + from pygerber.gerberx3.ast.nodes.properties.SF import SF + + +class FormatterError(Exception): + """Formatter error.""" + + +class Formatter(AstVisitor): + """Gerber X3 compatible formatter.""" + + def __init__( # noqa: PLR0913 + self, + *, + indent_character: Literal[" ", "\t"] = " ", + macro_body_indentation: str = "", + macro_split_mode: Literal["none", "primitives", "parameters"] = "primitives", + indent_block_aperture_body: str = "", + indent_step_and_repeat: str = "", + float_decimal_places: int = 6, + float_trim_trailing_zeros: bool = True, + indent_non_aperture_select_commands: bool = False, + split_format_select: bool = False, + split_aperture_definition: bool = False, + split_extended_command_boundaries: bool = False, + strip_whitespace_mode: bool = False, + macro_end_in_new_line: bool = False, + ) -> None: + r"""Initialize Formatter instance. + + Parameters + ---------- + indent_character: Literal[" ", "\t"], optional + Character used for indentation, by default " " + macro_body_indentation : str, optional + Indentation of macro body, by default "" + macro_split_mode : Literal["none", "primitives", "parameters"], optional + Changes how macro definitions are formatted, by default "none" + When "none" is selected, macro will be formatted as a single line. + ```gerber + %AMDonut*1,1,$1,$2,$3*$4=$1x0.75*1,0,$4,$2,$3*% + ``` + When "primitives" is selected, macro will be formatted with each primitive + on a new line. + ```gerber + %AMDonut* + 1,1,$1,$2,$3* + $4=$1x0.75* + 1,0,$4,$2,$3*% + ``` + indent_block_aperture_body : str, optional + _description_, by default "" + indent_step_and_repeat : str, optional + _description_, by default "" + float_decimal_places : int, optional + _description_, by default 6 + float_trim_trailing_zeros : bool, optional + _description_, by default True + indent_non_aperture_select_commands : bool, optional + _description_, by default False + split_format_select : bool, optional + _description_, by default False + split_aperture_definition : bool, optional + _description_, by default False + split_extended_command_boundaries : bool, optional + _description_, by default False + strip_whitespace_mode : bool, optional + _description_, by default False + macro_end_in_new_line: bool, optional + _description_, by default False + + """ + super().__init__() + self.indent_character = indent_character + self.macro_body_indentation = macro_body_indentation + self.macro_split_mode = macro_split_mode + self.indent_block_aperture_body = indent_block_aperture_body + self.indent_step_and_repeat = indent_step_and_repeat + self.float_decimal_places = float_decimal_places + + self.float_trim_trailing_zeros = float_trim_trailing_zeros + self.indent_non_aperture_select_commands = indent_non_aperture_select_commands + self.split_format_select = split_format_select + self.split_aperture_definition = split_aperture_definition + self.split_extended_command_boundaries = split_extended_command_boundaries + self.strip_whitespace_mode = strip_whitespace_mode + self.macro_end_in_new_line = macro_end_in_new_line + + self._output: Optional[StringIO] = None + + def format(self, source: File, output: StringIO) -> None: + """Format Gerber AST according to rules specified in Formatter constructor.""" + self._output = output + try: + self.on_file(source) + finally: + self._output = None + + @property + def output(self) -> StringIO: + """Get output buffer.""" + if self._output is None: + msg = "Output buffer is not set." + raise FormatterError(msg) + + return self._output + + def _fmt_double(self, value: float) -> str: + double = f"{value:.{self.float_decimal_places}f}" + if self.float_trim_trailing_zeros: + return double.rstrip("0").rstrip(".") + return double + + @cached_property + def lf(self) -> str: + """Get end of line character.""" + return "" if self.strip_whitespace_mode else "\n" + + @contextmanager + def _command(self, cmd: str) -> Generator[None, None, None]: + self._write(cmd) + yield + self._write(f"*{self.lf}") + + @contextmanager + def _extended_command(self, cmd: str) -> Generator[None, None, None]: + self._write(f"%{cmd}") + yield + self._write(f"*%{self.lf}") + + def _write(self, value: str) -> None: + self.output.write(value) + + def on_ab_close(self, node: ABclose) -> None: + """Handle `ABclose` node.""" + super().on_ab_close(node) + with self._extended_command("AB"): + pass + + def on_ab_open(self, node: ABopen) -> None: + """Handle `ABopen` node.""" + super().on_ab_open(node) + with self._extended_command("AB"): + self._write(node.aperture_identifier) + + def on_adc(self, node: ADC) -> None: + """Handle `AD` circle node.""" + super().on_adc(node) + with self._extended_command(f"AD{node.aperture_identifier}C,"): + self._write(self._fmt_double(node.diameter)) + + if node.hole_diameter is not None: + self._write(f"X{self._fmt_double(node.hole_diameter)}") + + def on_adr(self, node: ADR) -> None: + """Handle `AD` rectangle node.""" + super().on_adr(node) + + def on_ado(self, node: ADO) -> None: + """Handle `AD` obround node.""" + super().on_ado(node) + + def on_adp(self, node: ADP) -> None: + """Handle `AD` polygon node.""" + + def on_ad_macro(self, node: ADmacro) -> None: + """Handle `AD` macro node.""" + super().on_ad_macro(node) + + def on_am_close(self, node: AMclose) -> None: + """Handle `AMclose` node.""" + super().on_am_close(node) + + def on_am_open(self, node: AMopen) -> None: + """Handle `AMopen` node.""" + super().on_am_open(node) + + def on_sr_close(self, node: SRclose) -> None: + """Handle `SRclose` node.""" + super().on_sr_close(node) + + def on_sr_open(self, node: SRopen) -> None: + """Handle `SRopen` node.""" + super().on_sr_open(node) + + # Attribute + + def on_ta_user_name(self, node: TA_UserName) -> None: + """Handle `TA_UserName` node.""" + super().on_ta_user_name(node) + + def on_ta_aper_function(self, node: TA_AperFunction) -> None: + """Handle `TA_AperFunction` node.""" + super().on_ta_aper_function(node) + + def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: + """Handle `TA_DrillTolerance` node.""" + super().on_ta_drill_tolerance(node) + + def on_ta_flash_text(self, node: TA_FlashText) -> None: + """Handle `TA_FlashText` node.""" + super().on_ta_flash_text(node) + + def on_td(self, node: TD) -> None: + """Handle `TD` node.""" + super().on_td(node) + + def on_tf_user_name(self, node: TF_UserName) -> None: + """Handle `TF_UserName` node.""" + super().on_tf_user_name(node) + + def on_tf_part(self, node: TF_Part) -> None: + """Handle `TF_Part` node.""" + super().on_tf_part(node) + + def on_tf_file_function(self, node: TF_FileFunction) -> None: + """Handle `TF_FileFunction` node.""" + super().on_tf_file_function(node) + + def on_tf_file_polarity(self, node: TF_FilePolarity) -> None: + """Handle `TF_FilePolarity` node.""" + super().on_tf_file_polarity(node) + + def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: + """Handle `TF_SameCoordinates` node.""" + super().on_tf_same_coordinates(node) + + def on_tf_creation_date(self, node: TF_CreationDate) -> None: + """Handle `TF_CreationDate` node.""" + super().on_tf_creation_date(node) + + def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: + """Handle `TF_GenerationSoftware` node.""" + super().on_tf_generation_software(node) + + def on_tf_project_id(self, node: TF_ProjectId) -> None: + """Handle `TF_ProjectId` node.""" + super().on_tf_project_id(node) + + def on_tf_md5(self, node: TF_MD5) -> None: + """Handle `TF_MD5` node.""" + super().on_tf_md5(node) + + def on_to_user_name(self, node: TO_UserName) -> None: + """Handle `TO_UserName` node.""" + super().on_to_user_name(node) + + def on_to_n(self, node: TO_N) -> None: + """Handle `TO_N` node.""" + super().on_to_n(node) + + def on_to_p(self, node: TO_P) -> None: + """Handle `TO_P` node`.""" + super().on_to_p(node) + + def on_to_c(self, node: TO_C) -> None: + """Handle `TO_C` node.""" + super().on_to_c(node) + + def on_to_crot(self, node: TO_CRot) -> None: + """Handle `TO_CRot` node.""" + super().on_to_crot(node) + + def on_to_cmfr(self, node: TO_CMfr) -> None: + """Handle `TO_CMfr` node.""" + super().on_to_cmfr(node) + + def on_to_cmnp(self, node: TO_CMNP) -> None: + """Handle `TO_CMNP` node.""" + super().on_to_cmnp(node) + + def on_to_cval(self, node: TO_CVal) -> None: + """Handle `TO_CVal` node.""" + super().on_to_cval(node) + + def on_to_cmnt(self, node: TO_CMnt) -> None: + """Handle `TO_CVal` node.""" + super().on_to_cmnt(node) + + def on_to_cftp(self, node: TO_CFtp) -> None: + """Handle `TO_Cftp` node.""" + super().on_to_cftp(node) + + def on_to_cpgn(self, node: TO_CPgN) -> None: + """Handle `TO_CPgN` node.""" + super().on_to_cpgn(node) + + def on_to_cpgd(self, node: TO_CPgD) -> None: + """Handle `TO_CPgD` node.""" + super().on_to_cpgd(node) + + def on_to_chgt(self, node: TO_CHgt) -> None: + """Handle `TO_CHgt` node.""" + super().on_to_chgt(node) + + def on_to_clbn(self, node: TO_CLbN) -> None: + """Handle `TO_CLbN` node.""" + super().on_to_clbn(node) + + def on_to_clbd(self, node: TO_CLbD) -> None: + """Handle `TO_CLbD` node.""" + super().on_to_clbd(node) + + def on_to_csup(self, node: TO_CSup) -> None: + """Handle `TO_CSup` node.""" + super().on_to_csup(node) + + # D codes + + def on_d01(self, node: D01) -> None: + """Handle `D01` node.""" + super().on_d01(node) + with self._command("D01"): + pass + + def on_d02(self, node: D02) -> None: + """Handle `D02` node.""" + super().on_d02(node) + with self._command("D02"): + pass + + def on_d03(self, node: D03) -> None: + """Handle `D03` node.""" + super().on_d03(node) + with self._command("D03"): + pass + + def on_dnn(self, node: Dnn) -> None: + """Handle `Dnn` node.""" + super().on_dnn(node) + with self._command(node.value): + pass + + # G codes + + def on_g01(self, node: G01) -> None: + """Handle `G01` node.""" + super().on_g01(node) + self._write("G01*\n") + + def on_g02(self, node: G02) -> None: + """Handle `G02` node.""" + super().on_g02(node) + self._write("G02*\n") + + def on_g03(self, node: G03) -> None: + """Handle `G03` node.""" + super().on_g03(node) + self._write("G03*\n") + + def on_g04(self, node: G04) -> None: + """Handle `G04` node.""" + super().on_g04(node) + self._write(f"G04{node.string}*\n") + + def on_g36(self, node: G36) -> None: + """Handle `G36` node.""" + super().on_g36(node) + self._write("G36*\n") + + def on_g37(self, node: G37) -> None: + """Handle `G37` node.""" + super().on_g37(node) + self._write("G37*\n") + + def on_g54(self, node: G54) -> None: + """Handle `G54` node.""" + self._write("G54") + super().on_g54(node) + + def on_g55(self, node: G55) -> None: + """Handle `G55` node.""" + self._write("G55") + super().on_g55(node) + + def on_g70(self, node: G70) -> None: + """Handle `G70` node.""" + super().on_g70(node) + self._write("G70*\n") + + def on_g71(self, node: G71) -> None: + """Handle `G71` node.""" + super().on_g71(node) + self._write("G71*\n") + + def on_g74(self, node: G74) -> None: + """Handle `G74` node.""" + super().on_g74(node) + self._write("G74*\n") + + def on_g75(self, node: G75) -> None: + """Handle `G75` node.""" + super().on_g75(node) + self._write("G75*\n") + + def on_g90(self, node: G90) -> None: + """Handle `G90` node.""" + super().on_g90(node) + self._write("G90*\n") + + def on_g91(self, node: G91) -> None: + """Handle `G91` node.""" + super().on_g91(node) + self._write("G91*\n") + + # Load + + def on_lm(self, node: LM) -> None: + """Handle `LM` node.""" + super().on_lm(node) + + def on_ln(self, node: LN) -> None: + """Handle `LN` node.""" + super().on_ln(node) + + def on_lp(self, node: LP) -> None: + """Handle `LP` node.""" + super().on_lp(node) + + def on_lr(self, node: LR) -> None: + """Handle `LR` node.""" + super().on_lr(node) + + def on_ls(self, node: LS) -> None: + """Handle `LS` node.""" + super().on_ls(node) + + # M Codes + + def on_m00(self, node: M00) -> None: + """Handle `M00` node.""" + super().on_m00(node) + with self._command("M00"): + pass + + def on_m01(self, node: M01) -> None: + """Handle `M01` node.""" + super().on_m01(node) + with self._command("M01"): + pass + + def on_m02(self, node: M02) -> None: + """Handle `M02` node.""" + super().on_m02(node) + with self._command("M02"): + pass + + # Math + + # Math :: Operators :: Binary + + def on_add(self, node: Add) -> None: + """Handle `Add` node.""" + super().on_add(node) + + def on_div(self, node: Div) -> None: + """Handle `Div` node.""" + super().on_div(node) + + def on_mul(self, node: Mul) -> None: + """Handle `Mul` node.""" + super().on_mul(node) + + def on_sub(self, node: Sub) -> None: + """Handle `Sub` node.""" + super().on_sub(node) + + # Math :: Operators :: Unary + + def on_neg(self, node: Neg) -> None: + """Handle `Neg` node.""" + super().on_neg(node) + + def on_pos(self, node: Pos) -> None: + """Handle `Pos` node.""" + super().on_pos(node) + + def on_assignment(self, node: Assignment) -> None: + """Handle `Assignment` node.""" + super().on_assignment(node) + + def on_constant(self, node: Constant) -> None: + """Handle `Constant` node.""" + super().on_constant(node) + + def on_point(self, node: Point) -> None: + """Handle `Point` node.""" + super().on_point(node) + + def on_variable(self, node: Variable) -> None: + """Handle `Variable` node.""" + super().on_variable(node) + + # Other + + def on_coordinate_x(self, node: CoordinateX) -> None: + """Handle `Coordinate` node.""" + super().on_coordinate_x(node) + self._write(f"X{node.value}") + + def on_coordinate_y(self, node: CoordinateY) -> None: + """Handle `Coordinate` node.""" + super().on_coordinate_y(node) + self._write(f"Y{node.value}") + + def on_coordinate_i(self, node: CoordinateI) -> None: + """Handle `Coordinate` node.""" + super().on_coordinate_i(node) + self._write(f"I{node.value}") + + def on_coordinate_j(self, node: CoordinateJ) -> None: + """Handle `Coordinate` node.""" + super().on_coordinate_j(node) + self._write(f"J{node.value}") + + # Primitives + + def on_code_0(self, node: Code0) -> None: + """Handle `Code0` node.""" + super().on_code_0(node) + + def on_code_1(self, node: Code1) -> None: + """Handle `Code1` node.""" + super().on_code_1(node) + + def on_code_2(self, node: Code2) -> None: + """Handle `Code2` node.""" + super().on_code_2(node) + + def on_code_4(self, node: Code4) -> None: + """Handle `Code4` node.""" + super().on_code_4(node) + + def on_code_5(self, node: Code5) -> None: + """Handle `Code5` node.""" + super().on_code_5(node) + + def on_code_6(self, node: Code6) -> None: + """Handle `Code6` node.""" + super().on_code_6(node) + + def on_code_7(self, node: Code7) -> None: + """Handle `Code7` node.""" + super().on_code_7(node) + + def on_code_20(self, node: Code20) -> None: + """Handle `Code20` node.""" + super().on_code_20(node) + + def on_code_21(self, node: Code21) -> None: + """Handle `Code21` node.""" + super().on_code_21(node) + + def on_code_22(self, node: Code22) -> None: + """Handle `Code22` node.""" + super().on_code_22(node) + + # Properties + + def on_as(self, node: AS) -> None: + """Handle `AS` node.""" + super().on_as(node) + + def on_fs(self, node: FS) -> None: + """Handle `FS` node.""" + super().on_fs(node) + + def on_in(self, node: IN) -> None: + """Handle `IN` node.""" + super().on_in(node) + + def on_ip(self, node: IP) -> None: + """Handle `IP` node.""" + super().on_ip(node) + + def on_ir(self, node: IR) -> None: + """Handle `IR` node.""" + super().on_ir(node) + + def on_mi(self, node: MI) -> None: + """Handle `MI` node.""" + super().on_mi(node) + + def on_mo(self, node: MO) -> None: + """Handle `MO` node.""" + super().on_mo(node) + + def on_of(self, node: OF) -> None: + """Handle `OF` node.""" + super().on_of(node) + + def on_sf(self, node: SF) -> None: + """Handle `SF` node.""" + super().on_sf(node) + + # Root node + + def on_file(self, node: File) -> None: + """Handle `File` node.""" + super().on_file(node) diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index c3095ef4..df228759 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -130,40 +130,40 @@ source="", location=0, aperture_identifier="D11", - diameter="0.1", - hole_diameter="0.05", + diameter=0.1, + hole_diameter=0.05, ), ADmacro: ADmacro( source="", location=0, aperture_identifier="D11", name="macro", - params=["1", "2"], + params=[1, 2], ), ADO: ADO( source="", location=0, aperture_identifier="D11", - width="0.1", - height="0.05", - hole_diameter="0.05", + width=0.1, + height=0.05, + hole_diameter=0.05, ), ADR: ADR( source="", location=0, aperture_identifier="D11", - width="0.1", - height="0.05", - hole_diameter="0.05", + width=0.1, + height=0.05, + hole_diameter=0.05, ), ADP: ADP( source="", location=0, aperture_identifier="D11", - outer_diameter="0.1", - vertices="4", - rotation="0.1", - hole_diameter="0.05", + outer_diameter=0.1, + vertices=4, + rotation=0.1, + hole_diameter=0.05, ), AMclose: AMclose(source="", location=0), AMopen: AMopen(source="", location=0, name="macro"), diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py new file mode 100644 index 00000000..49958e1b --- /dev/null +++ b/test/gerberx3/test_formatter.py @@ -0,0 +1,86 @@ +from __future__ import annotations + +from dataclasses import dataclass +from io import StringIO + +import pytest + +from pygerber.gerberx3.formatter import Formatter +from pygerber.gerberx3.parser.pyparsing.parser import Parser +from test.gerberx3.common import ( + GERBER_ASSETS_INDEX, + Asset, + CaseGenerator, + ConfigBase, +) + + +@dataclass +class Config(ConfigBase): + """Configuration for the test.""" + + dpmm: int = 20 + as_expression: bool = False + compare_with_reference: bool = True + + +common_case_generator_config = { + "macro.*": Config(dpmm=100), + "incomplete.*": Config(skip=True), +} + + +parametrize = CaseGenerator( + GERBER_ASSETS_INDEX, + { + "A64-OLinuXino-rev-G.*": Config(dpmm=40), + "flashes.*": Config(dpmm=40), + "flashes.00_circle+h_4_tbh.grb": Config( + xfail=True, + xfail_message="Should warn, no mechanism implemented yet.", + ), + "ucamco.4.9.1.*": Config(dpmm=100), + "ucamco.4.9.6.*": Config(dpmm=300), + "ucamco.4.10.4.9.*": Config(dpmm=50), + "ucamco.4.11.4.*": Config(dpmm=1), + "expressions.*": Config(as_expression=True), + **common_case_generator_config, + }, + Config, +).parametrize + + +parametrize = CaseGenerator( + GERBER_ASSETS_INDEX, + { + "A64-OLinuXino-rev-G.*": Config(skip=True), + "A64-OLinuXino-rev-G.A64-OlinuXino_Rev_G-B_Cu.gbr": Config(skip=False), + "ATMEGA328-Motor-Board.*": Config(skip=True), + "ATMEGA328-Motor-Board.ATMEGA328_Motor_Board-B.Cu.gbl": Config(skip=False), + "expressions.*": Config(as_expression=True), + **common_case_generator_config, + }, + Config, +).parametrize + + +@parametrize +def test_formatter(asset: Asset, config: Config) -> None: + if config.skip: + pytest.skip(reason=config.skip_reason) + + if config.xfail: + pytest.xfail(config.xfail_message) + + source = asset.absolute_path.read_text() + parser = Parser() + + ast = parser.parse(source) + output_buffer = StringIO() + Formatter().format(ast, output_buffer) + + output_buffer.seek(0) + formatted_source = output_buffer.read() + formatted_ast = parser.parse(formatted_source) + + assert formatted_ast.model_dump_json() == ast.model_dump_json() From bf359f352044ad379cf70d4e66d6adc7ed00a308 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 14 Aug 2024 22:46:32 +0200 Subject: [PATCH 37/91] Add macro formatting --- .../gerberx3/ast/nodes/math/constant.py | 3 +- src/pygerber/gerberx3/formatter.py | 272 ++++++++++++++---- test/gerberx3/test_ast/test_ast_visitor.py | 144 +++++----- 3 files changed, 286 insertions(+), 133 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/math/constant.py b/src/pygerber/gerberx3/ast/nodes/math/constant.py index faae9c8c..50c84bba 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/constant.py +++ b/src/pygerber/gerberx3/ast/nodes/math/constant.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.math.expression import Expression +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -15,7 +16,7 @@ class Constant(Expression): """Represents math expression constant.""" - constant: str + constant: Double def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 075b595d..9442f996 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -136,8 +136,10 @@ def __init__( # noqa: PLR0913 self, *, indent_character: Literal[" ", "\t"] = " ", - macro_body_indentation: str = "", + macro_body_indentation: str | int = 4, + macro_param_indentation: str | int = 4, macro_split_mode: Literal["none", "primitives", "parameters"] = "primitives", + macro_end_in_new_line: bool = False, indent_block_aperture_body: str = "", indent_step_and_repeat: str = "", float_decimal_places: int = 6, @@ -147,7 +149,6 @@ def __init__( # noqa: PLR0913 split_aperture_definition: bool = False, split_extended_command_boundaries: bool = False, strip_whitespace_mode: bool = False, - macro_end_in_new_line: bool = False, ) -> None: r"""Initialize Formatter instance. @@ -155,8 +156,11 @@ def __init__( # noqa: PLR0913 ---------- indent_character: Literal[" ", "\t"], optional Character used for indentation, by default " " - macro_body_indentation : str, optional - Indentation of macro body, by default "" + macro_body_indentation : str | int, optional + Indentation of macro body, by default 4 + macro_param_indentation: str | int, optional + Indentation of macro parameters, by default 4 + This indentation is added on top of macro body indentation. macro_split_mode : Literal["none", "primitives", "parameters"], optional Changes how macro definitions are formatted, by default "none" When "none" is selected, macro will be formatted as a single line. @@ -195,8 +199,17 @@ def __init__( # noqa: PLR0913 """ super().__init__() self.indent_character = indent_character + + if isinstance(macro_body_indentation, int): + macro_body_indentation = indent_character * macro_body_indentation self.macro_body_indentation = macro_body_indentation + + if isinstance(macro_param_indentation, int): + macro_param_indentation = indent_character * macro_param_indentation + self.macro_param_indentation = macro_param_indentation + self.macro_split_mode = macro_split_mode + self.macro_end_in_new_line = macro_end_in_new_line self.indent_block_aperture_body = indent_block_aperture_body self.indent_step_and_repeat = indent_step_and_repeat self.float_decimal_places = float_decimal_places @@ -207,7 +220,6 @@ def __init__( # noqa: PLR0913 self.split_aperture_definition = split_aperture_definition self.split_extended_command_boundaries = split_extended_command_boundaries self.strip_whitespace_mode = strip_whitespace_mode - self.macro_end_in_new_line = macro_end_in_new_line self._output: Optional[StringIO] = None @@ -293,10 +305,14 @@ def on_ad_macro(self, node: ADmacro) -> None: def on_am_close(self, node: AMclose) -> None: """Handle `AMclose` node.""" super().on_am_close(node) + if self.macro_end_in_new_line: + self._write(f"{self.lf}") + self._write(f"%{self.lf}") def on_am_open(self, node: AMopen) -> None: """Handle `AMopen` node.""" super().on_am_open(node) + self._write(f"%AM{node.name}*") def on_sr_close(self, node: SRclose) -> None: """Handle `SRclose` node.""" @@ -450,40 +466,33 @@ def on_d03(self, node: D03) -> None: def on_dnn(self, node: Dnn) -> None: """Handle `Dnn` node.""" - super().on_dnn(node) with self._command(node.value): pass # G codes - def on_g01(self, node: G01) -> None: + def on_g01(self, node: G01) -> None: # noqa: ARG002 """Handle `G01` node.""" - super().on_g01(node) self._write("G01*\n") - def on_g02(self, node: G02) -> None: + def on_g02(self, node: G02) -> None: # noqa: ARG002 """Handle `G02` node.""" - super().on_g02(node) self._write("G02*\n") - def on_g03(self, node: G03) -> None: + def on_g03(self, node: G03) -> None: # noqa: ARG002 """Handle `G03` node.""" - super().on_g03(node) self._write("G03*\n") def on_g04(self, node: G04) -> None: """Handle `G04` node.""" - super().on_g04(node) self._write(f"G04{node.string}*\n") - def on_g36(self, node: G36) -> None: + def on_g36(self, node: G36) -> None: # noqa: ARG002 """Handle `G36` node.""" - super().on_g36(node) self._write("G36*\n") - def on_g37(self, node: G37) -> None: + def on_g37(self, node: G37) -> None: # noqa: ARG002 """Handle `G37` node.""" - super().on_g37(node) self._write("G37*\n") def on_g54(self, node: G54) -> None: @@ -496,34 +505,28 @@ def on_g55(self, node: G55) -> None: self._write("G55") super().on_g55(node) - def on_g70(self, node: G70) -> None: + def on_g70(self, node: G70) -> None: # noqa: ARG002 """Handle `G70` node.""" - super().on_g70(node) self._write("G70*\n") - def on_g71(self, node: G71) -> None: + def on_g71(self, node: G71) -> None: # noqa: ARG002 """Handle `G71` node.""" - super().on_g71(node) self._write("G71*\n") - def on_g74(self, node: G74) -> None: + def on_g74(self, node: G74) -> None: # noqa: ARG002 """Handle `G74` node.""" - super().on_g74(node) self._write("G74*\n") - def on_g75(self, node: G75) -> None: + def on_g75(self, node: G75) -> None: # noqa: ARG002 """Handle `G75` node.""" - super().on_g75(node) self._write("G75*\n") - def on_g90(self, node: G90) -> None: + def on_g90(self, node: G90) -> None: # noqa: ARG002 """Handle `G90` node.""" - super().on_g90(node) self._write("G90*\n") - def on_g91(self, node: G91) -> None: + def on_g91(self, node: G91) -> None: # noqa: ARG002 """Handle `G91` node.""" - super().on_g91(node) self._write("G91*\n") # Load @@ -550,133 +553,282 @@ def on_ls(self, node: LS) -> None: # M Codes - def on_m00(self, node: M00) -> None: + def on_m00(self, node: M00) -> None: # noqa: ARG002 """Handle `M00` node.""" - super().on_m00(node) with self._command("M00"): pass - def on_m01(self, node: M01) -> None: + def on_m01(self, node: M01) -> None: # noqa: ARG002 """Handle `M01` node.""" - super().on_m01(node) with self._command("M01"): pass - def on_m02(self, node: M02) -> None: + def on_m02(self, node: M02) -> None: # noqa: ARG002 """Handle `M02` node.""" - super().on_m02(node) with self._command("M02"): pass # Math # Math :: Operators :: Binary - def on_add(self, node: Add) -> None: """Handle `Add` node.""" - super().on_add(node) + self._write("(") + for i, operand in enumerate(node.operands): + operand.visit(self) + if i < len(node.operands) - 1: + self._write("+") + self._write(")") def on_div(self, node: Div) -> None: """Handle `Div` node.""" - super().on_div(node) + self._write("(") + for i, operand in enumerate(node.operands): + operand.visit(self) + if i < len(node.operands) - 1: + self._write("/") + self._write(")") def on_mul(self, node: Mul) -> None: """Handle `Mul` node.""" - super().on_mul(node) + self._write("(") + for i, operand in enumerate(node.operands): + operand.visit(self) + if i < len(node.operands) - 1: + self._write("x") + self._write(")") def on_sub(self, node: Sub) -> None: """Handle `Sub` node.""" - super().on_sub(node) + self._write("(") + for i, operand in enumerate(node.operands): + operand.visit(self) + if i < len(node.operands) - 1: + self._write("-") + self._write(")") # Math :: Operators :: Unary def on_neg(self, node: Neg) -> None: """Handle `Neg` node.""" - super().on_neg(node) + self._write("-") + node.operand.visit(self) def on_pos(self, node: Pos) -> None: """Handle `Pos` node.""" - super().on_pos(node) + self._write("+") + node.operand.visit(self) def on_assignment(self, node: Assignment) -> None: """Handle `Assignment` node.""" - super().on_assignment(node) + self._write(self._macro_primitive_lf) + node.variable.visit(self) + self._write("=") + node.expression.visit(self) + self._write("*") def on_constant(self, node: Constant) -> None: """Handle `Constant` node.""" - super().on_constant(node) + self._write(self._fmt_double(node.constant)) def on_point(self, node: Point) -> None: """Handle `Point` node.""" - super().on_point(node) + node.x.visit(self) + self._write(",") + node.y.visit(self) def on_variable(self, node: Variable) -> None: """Handle `Variable` node.""" - super().on_variable(node) + self._write(node.variable) # Other def on_coordinate_x(self, node: CoordinateX) -> None: """Handle `Coordinate` node.""" - super().on_coordinate_x(node) self._write(f"X{node.value}") def on_coordinate_y(self, node: CoordinateY) -> None: """Handle `Coordinate` node.""" - super().on_coordinate_y(node) self._write(f"Y{node.value}") def on_coordinate_i(self, node: CoordinateI) -> None: """Handle `Coordinate` node.""" - super().on_coordinate_i(node) self._write(f"I{node.value}") def on_coordinate_j(self, node: CoordinateJ) -> None: """Handle `Coordinate` node.""" - super().on_coordinate_j(node) self._write(f"J{node.value}") # Primitives def on_code_0(self, node: Code0) -> None: """Handle `Code0` node.""" - super().on_code_0(node) + primitive_lf = ( + self.lf if self.macro_split_mode in ("primitives", "parameters") else "" + ) + self._write(f"{primitive_lf}0{node.string}*") def on_code_1(self, node: Code1) -> None: """Handle `Code1` node.""" - super().on_code_1(node) + self._write(f"{self._macro_primitive_lf}1,{self._macro_param_lf}") + node.exposure.visit(self) + self._write(f",{self._macro_param_lf}") + node.diameter.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_y.visit(self) + if node.rotation is not None: + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") + + @property + def _macro_primitive_lf(self) -> str: + return self.lf if self.macro_split_mode in ("primitives", "parameters") else "" + + @property + def _macro_param_lf(self) -> str: + return self.lf if self.macro_split_mode == "parameters" else "" def on_code_2(self, node: Code2) -> None: """Handle `Code2` node.""" - super().on_code_2(node) + self._write(f"{self._macro_primitive_lf}2,{self._macro_param_lf}") + node.exposure.visit(self) + self._write(f",{self._macro_param_lf}") + node.width.visit(self) + self._write(f",{self._macro_param_lf}") + node.start_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.start_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.end_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.end_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") def on_code_4(self, node: Code4) -> None: """Handle `Code4` node.""" - super().on_code_4(node) + self._write(f"{self._macro_primitive_lf}4,{self._macro_param_lf}") + node.exposure.visit(self) + self._write(f",{self._macro_param_lf}") + node.number_of_points.visit(self) + self._write(f",{self._macro_param_lf}") + node.start_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.start_y.visit(self) + for point in node.points: + self._write(f",{self._macro_param_lf}") + point.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") def on_code_5(self, node: Code5) -> None: """Handle `Code5` node.""" - super().on_code_5(node) + self._write(f"{self._macro_primitive_lf}5,{self._macro_param_lf}") + node.exposure.visit(self) + self._write(f",{self._macro_param_lf}") + node.number_of_vertices.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.diameter.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") def on_code_6(self, node: Code6) -> None: """Handle `Code6` node.""" - super().on_code_6(node) + self._write(f"{self._macro_primitive_lf}6,{self._macro_param_lf}") + node.center_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.outer_diameter.visit(self) + self._write(f",{self._macro_param_lf}") + node.ring_thickness.visit(self) + self._write(f",{self._macro_param_lf}") + node.gap_between_rings.visit(self) + self._write(f",{self._macro_param_lf}") + node.max_ring_count.visit(self) + self._write(f",{self._macro_param_lf}") + node.crosshair_thickness.visit(self) + self._write(f",{self._macro_param_lf}") + node.crosshair_length.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") def on_code_7(self, node: Code7) -> None: """Handle `Code7` node.""" - super().on_code_7(node) + self._write(f"{self._macro_primitive_lf}7,{self._macro_param_lf}") + node.center_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.outer_diameter.visit(self) + self._write(f",{self._macro_param_lf}") + node.inner_diameter.visit(self) + self._write(f",{self._macro_param_lf}") + node.gap_thickness.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") def on_code_20(self, node: Code20) -> None: """Handle `Code20` node.""" - super().on_code_20(node) + self._write(f"{self._macro_primitive_lf}20,{self._macro_param_lf}") + node.exposure.visit(self) + self._write(f",{self._macro_param_lf}") + node.width.visit(self) + self._write(f",{self._macro_param_lf}") + node.start_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.start_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.end_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.end_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") def on_code_21(self, node: Code21) -> None: """Handle `Code21` node.""" - super().on_code_21(node) + self._write(f"{self._macro_primitive_lf}21,{self._macro_param_lf}") + node.exposure.visit(self) + self._write(f",{self._macro_param_lf}") + node.width.visit(self) + self._write(f",{self._macro_param_lf}") + node.height.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_x.visit(self) + self._write(f",{self._macro_param_lf}") + node.center_y.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") def on_code_22(self, node: Code22) -> None: """Handle `Code22` node.""" - super().on_code_22(node) + self._write(f"{self._macro_primitive_lf}22,{self._macro_param_lf}") + node.exposure.visit(self) + self._write(f",{self._macro_param_lf}") + node.width.visit(self) + self._write(f",{self._macro_param_lf}") + node.height.visit(self) + self._write(f",{self._macro_param_lf}") + node.x_lower_left.visit(self) + self._write(f",{self._macro_param_lf}") + node.y_lower_left.visit(self) + self._write(f",{self._macro_param_lf}") + node.rotation.visit(self) + self._write("*") # Properties diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index df228759..3a335689 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -274,56 +274,56 @@ source="", location=0, operands=[ - Constant(source="", location=0, constant="1"), - Constant(source="", location=0, constant="2"), + Constant(source="", location=0, constant=1), + Constant(source="", location=0, constant=2), ], ), Div: Div( source="", location=0, operands=[ - Constant(source="", location=0, constant="1"), - Constant(source="", location=0, constant="2"), + Constant(source="", location=0, constant=1), + Constant(source="", location=0, constant=2), ], ), Mul: Mul( source="", location=0, operands=[ - Constant(source="", location=0, constant="1"), - Constant(source="", location=0, constant="2"), + Constant(source="", location=0, constant=1), + Constant(source="", location=0, constant=2), ], ), Sub: Sub( source="", location=0, operands=[ - Constant(source="", location=0, constant="1"), - Constant(source="", location=0, constant="2"), + Constant(source="", location=0, constant=1), + Constant(source="", location=0, constant=2), ], ), Neg: Neg( source="", location=0, - operand=Constant(source="", location=0, constant="2"), + operand=Constant(source="", location=0, constant=2), ), Pos: Pos( source="", location=0, - operand=Constant(source="", location=0, constant="2"), + operand=Constant(source="", location=0, constant=2), ), Assignment: Assignment( source="", location=0, variable=Variable(source="", location=0, variable="$1"), - expression=Constant(source="", location=0, constant="1"), + expression=Constant(source="", location=0, constant=1), ), - Constant: Constant(source="", location=0, constant="2"), + Constant: Constant(source="", location=0, constant=2), Point: Point( source="", location=0, - x=Constant(source="", location=0, constant="1"), - y=Constant(source="", location=0, constant="2"), + x=Constant(source="", location=0, constant=1), + y=Constant(source="", location=0, constant=2), ), Variable: Variable(source="", location=0, variable="$1"), CoordinateX: CoordinateX(source="", location=0, value="1"), @@ -334,102 +334,102 @@ Code1: Code1( source="", location=0, - exposure=Constant(source="", location=0, constant="1"), - diameter=Constant(source="", location=0, constant="2"), - center_x=Constant(source="", location=0, constant="3"), - center_y=Constant(source="", location=0, constant="4"), + exposure=Constant(source="", location=0, constant=1), + diameter=Constant(source="", location=0, constant=2), + center_x=Constant(source="", location=0, constant=3), + center_y=Constant(source="", location=0, constant=4), ), Code2: Code2( source="", location=0, - exposure=Constant(source="", location=0, constant="1"), - width=Constant(source="", location=0, constant="2"), - start_x=Constant(source="", location=0, constant="3"), - start_y=Constant(source="", location=0, constant="4"), - end_x=Constant(source="", location=0, constant="5"), - end_y=Constant(source="", location=0, constant="6"), - rotation=Constant(source="", location=0, constant="7"), + exposure=Constant(source="", location=0, constant=1), + width=Constant(source="", location=0, constant=2), + start_x=Constant(source="", location=0, constant=3), + start_y=Constant(source="", location=0, constant=4), + end_x=Constant(source="", location=0, constant=5), + end_y=Constant(source="", location=0, constant=6), + rotation=Constant(source="", location=0, constant=7), ), Code4: Code4( source="", location=0, - exposure=Constant(source="", location=0, constant="1"), - number_of_points=Constant(source="", location=0, constant="2"), - start_x=Constant(source="", location=0, constant="3"), - start_y=Constant(source="", location=0, constant="4"), + exposure=Constant(source="", location=0, constant=1), + number_of_points=Constant(source="", location=0, constant=2), + start_x=Constant(source="", location=0, constant=3), + start_y=Constant(source="", location=0, constant=4), points=[ Point( source="", location=0, - x=Constant(source="", location=0, constant="1"), - y=Constant(source="", location=0, constant="2"), + x=Constant(source="", location=0, constant=1), + y=Constant(source="", location=0, constant=2), ) ], - rotation=Constant(source="", location=0, constant="5"), + rotation=Constant(source="", location=0, constant=5), ), Code5: Code5( source="", location=0, - exposure=Constant(source="", location=0, constant="1"), - number_of_vertices=Constant(source="", location=0, constant="2"), - center_x=Constant(source="", location=0, constant="3"), - center_y=Constant(source="", location=0, constant="4"), - diameter=Constant(source="", location=0, constant="5"), - rotation=Constant(source="", location=0, constant="6"), + exposure=Constant(source="", location=0, constant=1), + number_of_vertices=Constant(source="", location=0, constant=2), + center_x=Constant(source="", location=0, constant=3), + center_y=Constant(source="", location=0, constant=4), + diameter=Constant(source="", location=0, constant=5), + rotation=Constant(source="", location=0, constant=6), ), Code6: Code6( source="", location=0, - center_x=Constant(source="", location=0, constant="3"), - center_y=Constant(source="", location=0, constant="4"), - outer_diameter=Constant(source="", location=0, constant="5"), - ring_thickness=Constant(source="", location=0, constant="1"), - gap_between_rings=Constant(source="", location=0, constant="1"), - max_ring_count=Constant(source="", location=0, constant="4"), - crosshair_thickness=Constant(source="", location=0, constant="4"), - crosshair_length=Constant(source="", location=0, constant="4"), - rotation=Constant(source="", location=0, constant="6"), + center_x=Constant(source="", location=0, constant=3), + center_y=Constant(source="", location=0, constant=4), + outer_diameter=Constant(source="", location=0, constant=5), + ring_thickness=Constant(source="", location=0, constant=1), + gap_between_rings=Constant(source="", location=0, constant=1), + max_ring_count=Constant(source="", location=0, constant=4), + crosshair_thickness=Constant(source="", location=0, constant=4), + crosshair_length=Constant(source="", location=0, constant=4), + rotation=Constant(source="", location=0, constant=6), ), Code7: Code7( source="", location=0, - center_x=Constant(source="", location=0, constant="3"), - center_y=Constant(source="", location=0, constant="4"), - outer_diameter=Constant(source="", location=0, constant="5"), - inner_diameter=Constant(source="", location=0, constant="1"), - gap_thickness=Constant(source="", location=0, constant="1"), - rotation=Constant(source="", location=0, constant="6"), + center_x=Constant(source="", location=0, constant=3), + center_y=Constant(source="", location=0, constant=4), + outer_diameter=Constant(source="", location=0, constant=5), + inner_diameter=Constant(source="", location=0, constant=1), + gap_thickness=Constant(source="", location=0, constant=1), + rotation=Constant(source="", location=0, constant=6), ), Code20: Code20( source="", location=0, - exposure=Constant(source="", location=0, constant="1"), - width=Constant(source="", location=0, constant="2"), - start_x=Constant(source="", location=0, constant="3"), - start_y=Constant(source="", location=0, constant="4"), - end_x=Constant(source="", location=0, constant="5"), - end_y=Constant(source="", location=0, constant="6"), - rotation=Constant(source="", location=0, constant="7"), + exposure=Constant(source="", location=0, constant=1), + width=Constant(source="", location=0, constant=2), + start_x=Constant(source="", location=0, constant=3), + start_y=Constant(source="", location=0, constant=4), + end_x=Constant(source="", location=0, constant=5), + end_y=Constant(source="", location=0, constant=6), + rotation=Constant(source="", location=0, constant=7), ), Code21: Code21( source="", location=0, - exposure=Constant(source="", location=0, constant="1"), - width=Constant(source="", location=0, constant="2"), - height=Constant(source="", location=0, constant="3"), - center_x=Constant(source="", location=0, constant="4"), - center_y=Constant(source="", location=0, constant="5"), - rotation=Constant(source="", location=0, constant="6"), + exposure=Constant(source="", location=0, constant=1), + width=Constant(source="", location=0, constant=2), + height=Constant(source="", location=0, constant=3), + center_x=Constant(source="", location=0, constant=4), + center_y=Constant(source="", location=0, constant=5), + rotation=Constant(source="", location=0, constant=6), ), Code22: Code22( source="", location=0, - exposure=Constant(source="", location=0, constant="1"), - width=Constant(source="", location=0, constant="2"), - height=Constant(source="", location=0, constant="3"), - x_lower_left=Constant(source="", location=0, constant="4"), - y_lower_left=Constant(source="", location=0, constant="5"), - rotation=Constant(source="", location=0, constant="6"), + exposure=Constant(source="", location=0, constant=1), + width=Constant(source="", location=0, constant=2), + height=Constant(source="", location=0, constant=3), + x_lower_left=Constant(source="", location=0, constant=4), + y_lower_left=Constant(source="", location=0, constant=5), + rotation=Constant(source="", location=0, constant=6), ), AS: AS(source="", location=0, correspondence=AxisCorrespondence.AX_BY), FS: FS( From d6f95fd2856c23297c100cf632dcd70eaaa79e54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 14 Aug 2024 23:06:31 +0200 Subject: [PATCH 38/91] Add properties formatting --- .../gerberx3/ast/nodes/properties/FS.py | 6 +-- src/pygerber/gerberx3/formatter.py | 37 ++++++++++++++----- 2 files changed, 31 insertions(+), 12 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py index ec4d1247..7bb3a09c 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/FS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, Literal from pygerber.gerberx3.ast.nodes.base import Node @@ -15,8 +15,8 @@ class FS(Node): """Represents FS Gerber extended command.""" - zeros: str - coordinate_mode: str + zeros: Literal["L", "T"] + coordinate_mode: Literal["A", "I"] x_integral: int x_decimal: int diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 9442f996..6a82406b 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -834,39 +834,58 @@ def on_code_22(self, node: Code22) -> None: def on_as(self, node: AS) -> None: """Handle `AS` node.""" - super().on_as(node) + with self._extended_command("AS"): + self._write(node.correspondence.value) def on_fs(self, node: FS) -> None: """Handle `FS` node.""" - super().on_fs(node) + with self._extended_command("FS"): + self._write(node.zeros) + self._write(node.coordinate_mode) + self._write(f"X{node.x_integral}{node.x_decimal}") + self._write(f"Y{node.y_integral}{node.y_decimal}") def on_in(self, node: IN) -> None: """Handle `IN` node.""" - super().on_in(node) + with self._extended_command("IN"): + self._write(node.name) def on_ip(self, node: IP) -> None: """Handle `IP` node.""" - super().on_ip(node) + with self._extended_command("IP"): + self._write(node.polarity) def on_ir(self, node: IR) -> None: """Handle `IR` node.""" - super().on_ir(node) + with self._extended_command("IR"): + self._write(self._fmt_double(node.rotation_degrees)) def on_mi(self, node: MI) -> None: """Handle `MI` node.""" - super().on_mi(node) + with self._extended_command("MI"): + self._write(f"A{node.a_mirroring}") + self._write(f"B{node.b_mirroring}") def on_mo(self, node: MO) -> None: """Handle `MO` node.""" - super().on_mo(node) + with self._extended_command("MO"): + self._write(node.mode.value) def on_of(self, node: OF) -> None: """Handle `OF` node.""" - super().on_of(node) + with self._extended_command("OF"): + if node.a_offset is not None: + self._write(f"A{node.a_offset}") + if node.b_offset is not None: + self._write(f"B{node.b_offset}") def on_sf(self, node: SF) -> None: """Handle `SF` node.""" - super().on_sf(node) + with self._extended_command("SF"): + self._write("A") + self._write(self._fmt_double(node.a_scale)) + self._write("B") + self._write(self._fmt_double(node.b_scale)) # Root node From 8420bb4e5558d75bc725794a44ad4a26cef89e4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 15 Aug 2024 01:34:33 +0200 Subject: [PATCH 39/91] Add aperture nodes formatting --- src/pygerber/gerberx3/formatter.py | 53 ++++++++++++++++++++++++------ test/gerberx3/common.py | 6 ++-- test/gerberx3/test_formatter.py | 4 ++- 3 files changed, 50 insertions(+), 13 deletions(-) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 6a82406b..7c8ead2f 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -266,21 +266,18 @@ def _extended_command(self, cmd: str) -> Generator[None, None, None]: def _write(self, value: str) -> None: self.output.write(value) - def on_ab_close(self, node: ABclose) -> None: + def on_ab_close(self, node: ABclose) -> None: # noqa: ARG002 """Handle `ABclose` node.""" - super().on_ab_close(node) with self._extended_command("AB"): pass def on_ab_open(self, node: ABopen) -> None: """Handle `ABopen` node.""" - super().on_ab_open(node) with self._extended_command("AB"): self._write(node.aperture_identifier) def on_adc(self, node: ADC) -> None: """Handle `AD` circle node.""" - super().on_adc(node) with self._extended_command(f"AD{node.aperture_identifier}C,"): self._write(self._fmt_double(node.diameter)) @@ -289,18 +286,42 @@ def on_adc(self, node: ADC) -> None: def on_adr(self, node: ADR) -> None: """Handle `AD` rectangle node.""" - super().on_adr(node) + with self._extended_command(f"AD{node.aperture_identifier}R,"): + self._write(self._fmt_double(node.width)) + self._write(f"X{self._fmt_double(node.height)}") + + if node.hole_diameter is not None: + self._write(f"X{self._fmt_double(node.hole_diameter)}") def on_ado(self, node: ADO) -> None: """Handle `AD` obround node.""" - super().on_ado(node) + with self._extended_command(f"AD{node.aperture_identifier}O,"): + self._write(self._fmt_double(node.width)) + self._write(f"X{self._fmt_double(node.height)}") + + if node.hole_diameter is not None: + self._write(f"X{self._fmt_double(node.hole_diameter)}") def on_adp(self, node: ADP) -> None: """Handle `AD` polygon node.""" + with self._extended_command(f"AD{node.aperture_identifier}P,"): + self._write(self._fmt_double(node.outer_diameter)) + self._write(f"X{node.vertices}") + + if node.rotation is not None: + self._write(f"X{self._fmt_double(node.rotation)}") + + if node.hole_diameter is not None: + self._write(f"X{self._fmt_double(node.hole_diameter)}") def on_ad_macro(self, node: ADmacro) -> None: """Handle `AD` macro node.""" - super().on_ad_macro(node) + with self._extended_command(f"AD{node.aperture_identifier}{node.name}"): + if node.params is not None: + first, *rest = node.params + self._write(f",{first}") + for param in rest: + self._write(f"X{param}") def on_am_close(self, node: AMclose) -> None: """Handle `AMclose` node.""" @@ -314,13 +335,25 @@ def on_am_open(self, node: AMopen) -> None: super().on_am_open(node) self._write(f"%AM{node.name}*") - def on_sr_close(self, node: SRclose) -> None: + def on_sr_close(self, node: SRclose) -> None: # noqa: ARG002 """Handle `SRclose` node.""" - super().on_sr_close(node) + with self._extended_command("SR"): + pass def on_sr_open(self, node: SRopen) -> None: """Handle `SRopen` node.""" - super().on_sr_open(node) + with self._extended_command("SR"): + if node.x is not None: + self._write(f"X{node.x}") + + if node.x is not None: + self._write(f"Y{node.y}") + + if node.x is not None: + self._write(f"I{node.i}") + + if node.x is not None: + self._write(f"J{node.j}") # Attribute diff --git a/test/gerberx3/common.py b/test/gerberx3/common.py index 814254b4..109f2111 100644 --- a/test/gerberx3/common.py +++ b/test/gerberx3/common.py @@ -205,8 +205,10 @@ def _prepare_repository(self) -> None: self.repository_directory.as_posix(), ) repository = Repo(self.repository_directory.as_posix()) - porcelain.fetch(repository, "origin") - porcelain.checkout_branch(repository, self.sha) + sha = repository.head().decode(encoding="utf-8") + if sha != self.sha: + porcelain.fetch(repository, "origin") + porcelain.checkout_branch(repository, self.sha) def get_asset_path(self, tag: str, relative_path: Path) -> Path: return self.repository_directory / f".reference{tag}" / relative_path diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 49958e1b..0b405f1b 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -83,4 +83,6 @@ def test_formatter(asset: Asset, config: Config) -> None: formatted_source = output_buffer.read() formatted_ast = parser.parse(formatted_source) - assert formatted_ast.model_dump_json() == ast.model_dump_json() + assert formatted_ast.model_dump_json(serialize_as_any=True) == ast.model_dump_json( + serialize_as_any=True + ) From d2881b72d3a5e662142a1715bd358902c8c3fdc2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 15 Aug 2024 15:36:24 +0200 Subject: [PATCH 40/91] Add TA & TD formatting --- src/pygerber/gerberx3/formatter.py | 62 +++++++++++++++++-- .../gerberx3/parser/pyparsing/grammar.py | 8 ++- 2 files changed, 62 insertions(+), 8 deletions(-) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 7c8ead2f..f6eee787 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -359,23 +359,75 @@ def on_sr_open(self, node: SRopen) -> None: def on_ta_user_name(self, node: TA_UserName) -> None: """Handle `TA_UserName` node.""" - super().on_ta_user_name(node) + with self._extended_command(f"TA{node.user_name}"): + for field in node.fields: + self._write(",") + self._write(field) def on_ta_aper_function(self, node: TA_AperFunction) -> None: """Handle `TA_AperFunction` node.""" - super().on_ta_aper_function(node) + with self._extended_command("TA.AperFunction"): + if node.function is not None: + self._write(",") + self._write(node.function.value) + + for field in node.fields: + self._write(",") + self._write(field) def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: """Handle `TA_DrillTolerance` node.""" - super().on_ta_drill_tolerance(node) + with self._extended_command("TA.DrillTolerance"): + if node.plus_tolerance is not None: + self._write(",") + self._write(self._fmt_double(node.plus_tolerance)) + + if node.minus_tolerance is not None: + self._write(",") + self._write(self._fmt_double(node.minus_tolerance)) def on_ta_flash_text(self, node: TA_FlashText) -> None: """Handle `TA_FlashText` node.""" - super().on_ta_flash_text(node) + with self._extended_command("TA.FlashText"): + self._write(",") + self._write(node.string) + + self._write(",") + self._write(node.mode) + + self._write(",") + self._write(node.mirroring) + + if len(node.comments) == 0: + if node.font is not None: + self._write(",") + self._write(node.font) + + if node.size is not None: + self._write(",") + self._write(node.size) + + for comment in node.comments: + self._write(",") + self._write(comment) + else: + self._write(",") + if node.font is not None: + self._write(node.font) + + self._write(",") + if node.size is not None: + self._write(node.size) + + for comment in node.comments: + self._write(",") + self._write(comment) def on_td(self, node: TD) -> None: """Handle `TD` node.""" - super().on_td(node) + with self._extended_command("TD"): + if node.name is not None: + self._write(node.name) def on_tf_user_name(self, node: TF_UserName) -> None: """Handle `TF_UserName` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 85e05680..33d10eff 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -527,12 +527,14 @@ def _ta_flash_text(self) -> pp.ParserElement: + self.comma + pp.Opt(pp.one_of(list("RM")).set_results_name("mirroring")) + self.comma - + pp.Opt(self.field.set_results_name("font")) + + pp.Opt(self.field).set_results_name("font") + self.comma - + pp.Opt(self.field.set_results_name("size")) + + pp.Opt(self.field).set_results_name("size") + pp.ZeroOrMore( self.comma - + self.field.set_results_name("comments", list_all_matches=True) + + pp.Opt(self.field).set_results_name( + "comments", list_all_matches=True + ) ) ) .set_name("TA.FlashText") From 11e4255c404fa1d778d606b63876c64cc8218eb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 16 Aug 2024 01:34:16 +0200 Subject: [PATCH 41/91] Add TF formatting --- src/pygerber/gerberx3/formatter.py | 62 ++++++++++++++++--- .../attribute/TF_GenerationSoftware.grb | 3 + 2 files changed, 56 insertions(+), 9 deletions(-) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index f6eee787..d9424038 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -431,39 +431,83 @@ def on_td(self, node: TD) -> None: def on_tf_user_name(self, node: TF_UserName) -> None: """Handle `TF_UserName` node.""" - super().on_tf_user_name(node) + with self._extended_command(f"TF{node.user_name}"): + for field in node.fields: + self._write(",") + self._write(field) def on_tf_part(self, node: TF_Part) -> None: """Handle `TF_Part` node.""" - super().on_tf_part(node) + with self._extended_command("TF.Part,"): + self._write(node.part.value) + if len(node.fields) != 0: + for field in node.fields: + self._write(",") + self._write(field) def on_tf_file_function(self, node: TF_FileFunction) -> None: """Handle `TF_FileFunction` node.""" - super().on_tf_file_function(node) + with self._extended_command("TF.FileFunction,"): + self._write(node.file_function.value) + if len(node.fields) != 0: + for field in node.fields: + self._write(",") + self._write(field) def on_tf_file_polarity(self, node: TF_FilePolarity) -> None: """Handle `TF_FilePolarity` node.""" - super().on_tf_file_polarity(node) + with self._extended_command("TF.FilePolarity,"): + self._write(node.polarity) def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: """Handle `TF_SameCoordinates` node.""" - super().on_tf_same_coordinates(node) + with self._extended_command("TF.SameCoordinates"): + if node.identifier is not None: + self._write(",") + self._write(node.identifier) def on_tf_creation_date(self, node: TF_CreationDate) -> None: """Handle `TF_CreationDate` node.""" - super().on_tf_creation_date(node) + with self._extended_command("TF.CreationDate"): + if node.creation_date is not None: + self._write(",") + self._write(node.creation_date.isoformat()) def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: """Handle `TF_GenerationSoftware` node.""" - super().on_tf_generation_software(node) + with self._extended_command("TF.GenerationSoftware"): + self._write(",") + if node.vendor is not None: + self._write(node.vendor) + + self._write(",") + if node.application is not None: + self._write(node.application) + + self._write(",") + if node.version is not None: + self._write(node.version) def on_tf_project_id(self, node: TF_ProjectId) -> None: """Handle `TF_ProjectId` node.""" - super().on_tf_project_id(node) + with self._extended_command("TF.ProjectId"): + self._write(",") + if node.name is not None: + self._write(node.name) + + self._write(",") + if node.guid is not None: + self._write(node.guid) + + self._write(",") + if node.revision is not None: + self._write(node.revision) def on_tf_md5(self, node: TF_MD5) -> None: """Handle `TF_MD5` node.""" - super().on_tf_md5(node) + with self._extended_command("TF.MD5"): + self._write(",") + self._write(node.md5) def on_to_user_name(self, node: TO_UserName) -> None: """Handle `TO_UserName` node.""" diff --git a/test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb b/test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb index 7781380d..816b74ac 100644 --- a/test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb +++ b/test/assets/gerberx3/tokens/attribute/TF_GenerationSoftware.grb @@ -3,3 +3,6 @@ %TF.GenerationSoftware,Altium Limited,Altium Designer,23.5.1 (21)*% %TF.GenerationSoftware,Ucamco,UcamX,2016.04-160425*% %TF.GenerationSoftware,Ucamco,UcamX,2017.04*% +%TF.GenerationSoftware,,UcamX,2017.04*% +%TF.GenerationSoftware,,,2017.04*% +%TF.GenerationSoftware,,,*% From 55d450b2d68e432502eec2f44668a01080e46030 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 16 Aug 2024 01:51:58 +0200 Subject: [PATCH 42/91] Add TO formatting --- .../gerberx3/ast/nodes/attribute/TO.py | 5 +- src/pygerber/gerberx3/formatter.py | 79 +++++++++++++++---- 2 files changed, 65 insertions(+), 19 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py index a6ba05cc..fdb25887 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py @@ -8,6 +8,7 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -89,7 +90,7 @@ def get_visitor_callback_function( class TO_CRot(TO): # noqa: N801 """Represents TO Gerber extended command with .CRot attribute.""" - angle: float + angle: Double def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" @@ -226,7 +227,7 @@ def get_visitor_callback_function( class TO_CHgt(TO): # noqa: N801 """Represents TO Gerber extended command with .CHgt attribute.""" - height: float + height: Double def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index d9424038..b7e2d2a8 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -9,6 +9,7 @@ from pyparsing import cached_property +from pygerber.gerberx3.ast.nodes.types import Double from pygerber.gerberx3.ast.visitor import AstVisitor if TYPE_CHECKING: @@ -240,7 +241,7 @@ def output(self) -> StringIO: return self._output - def _fmt_double(self, value: float) -> str: + def _fmt_double(self, value: Double) -> str: double = f"{value:.{self.float_decimal_places}f}" if self.float_trim_trailing_zeros: return double.rstrip("0").rstrip(".") @@ -511,67 +512,111 @@ def on_tf_md5(self, node: TF_MD5) -> None: def on_to_user_name(self, node: TO_UserName) -> None: """Handle `TO_UserName` node.""" - super().on_to_user_name(node) + with self._extended_command(f"TO{node.user_name}"): + for field in node.fields: + self._write(",") + self._write(field) def on_to_n(self, node: TO_N) -> None: """Handle `TO_N` node.""" - super().on_to_n(node) + with self._extended_command("TO.N"): + for field in node.net_names: + self._write(",") + self._write(field) def on_to_p(self, node: TO_P) -> None: """Handle `TO_P` node`.""" - super().on_to_p(node) + with self._extended_command("TO.P"): + self._write(",") + self._write(node.refdes) + self._write(",") + self._write(node.number) + if node.function is not None: + self._write(",") + self._write(node.function) def on_to_c(self, node: TO_C) -> None: """Handle `TO_C` node.""" - super().on_to_c(node) + with self._extended_command("TO.C"): + self._write(",") + self._write(node.refdes) def on_to_crot(self, node: TO_CRot) -> None: """Handle `TO_CRot` node.""" - super().on_to_crot(node) + with self._extended_command("TO.CRot"): + self._write(",") + self._write(self._fmt_double(node.angle)) def on_to_cmfr(self, node: TO_CMfr) -> None: """Handle `TO_CMfr` node.""" - super().on_to_cmfr(node) + with self._extended_command("TO.CMfr"): + self._write(",") + self._write(node.manufacturer) def on_to_cmnp(self, node: TO_CMNP) -> None: """Handle `TO_CMNP` node.""" - super().on_to_cmnp(node) + with self._extended_command("TO.CMPN"): + self._write(",") + self._write(node.part_number) def on_to_cval(self, node: TO_CVal) -> None: """Handle `TO_CVal` node.""" - super().on_to_cval(node) + with self._extended_command("TO.CVal"): + self._write(",") + self._write(node.value) def on_to_cmnt(self, node: TO_CMnt) -> None: """Handle `TO_CVal` node.""" - super().on_to_cmnt(node) + with self._extended_command("TO.CMnt"): + self._write(",") + self._write(node.mount.value) def on_to_cftp(self, node: TO_CFtp) -> None: """Handle `TO_Cftp` node.""" - super().on_to_cftp(node) + with self._extended_command("TO.CFtp"): + self._write(",") + self._write(node.footprint) def on_to_cpgn(self, node: TO_CPgN) -> None: """Handle `TO_CPgN` node.""" - super().on_to_cpgn(node) + with self._extended_command("TO.CPgN"): + self._write(",") + self._write(node.name) def on_to_cpgd(self, node: TO_CPgD) -> None: """Handle `TO_CPgD` node.""" - super().on_to_cpgd(node) + with self._extended_command("TO.CPgD"): + self._write(",") + self._write(node.description) def on_to_chgt(self, node: TO_CHgt) -> None: """Handle `TO_CHgt` node.""" - super().on_to_chgt(node) + with self._extended_command("TO.CHgt"): + self._write(",") + self._write(self._fmt_double(node.height)) def on_to_clbn(self, node: TO_CLbN) -> None: """Handle `TO_CLbN` node.""" - super().on_to_clbn(node) + with self._extended_command("TO.CLbn"): + self._write(",") + self._write(node.name) def on_to_clbd(self, node: TO_CLbD) -> None: """Handle `TO_CLbD` node.""" - super().on_to_clbd(node) + with self._extended_command("TO.CLbD"): + self._write(",") + self._write(node.description) def on_to_csup(self, node: TO_CSup) -> None: """Handle `TO_CSup` node.""" - super().on_to_csup(node) + with self._extended_command("TO.CSup"): + self._write(",") + self._write(node.supplier) + self._write(",") + self._write(node.supplier_part) + for field in node.other_suppliers: + self._write(",") + self._write(field) # D codes From bf14e2c65ee630317ac05781e5decc1bdc13d354 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 16 Aug 2024 01:53:51 +0200 Subject: [PATCH 43/91] Fix G04 formatting --- src/pygerber/gerberx3/formatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index b7e2d2a8..ac1d06ea 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -659,7 +659,7 @@ def on_g03(self, node: G03) -> None: # noqa: ARG002 def on_g04(self, node: G04) -> None: """Handle `G04` node.""" - self._write(f"G04{node.string}*\n") + self._write(f"G04{node.string or ''}*\n") def on_g36(self, node: G36) -> None: # noqa: ARG002 """Handle `G36` node.""" From f4cba790044639f5b6fd32e9c1597acf74d0c6bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 17 Aug 2024 16:27:03 +0200 Subject: [PATCH 44/91] Add macro related formatting options --- pyproject.toml | 1 + src/pygerber/gerberx3/formatter.py | 77 +++++++++----- test/conftest.py | 3 +- test/gerberx3/common.py | 2 +- test/gerberx3/test_formatter.py | 159 +++++++++++++++++++++++++++++ 5 files changed, 215 insertions(+), 27 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 189cbcaf..4952cf9d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,6 +150,7 @@ lint.ignore = [ "ISC001", # Checks for the absence of trailing commas. Conflicts with ruff format. "COM812", # Checks for implicitly concatenated strings on a single line. Conflicts with ruff format. "S101", # Use of assert detected. The enclosed code will be removed when compiling to optimised byte code. + "EXE002", # Breaks on file systems which do not support executable permissions. ] show-fixes = true target-version = "py38" diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index ac1d06ea..f8173927 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -5,6 +5,7 @@ from __future__ import annotations from contextlib import contextmanager +from enum import Enum from typing import TYPE_CHECKING, Generator, Literal, Optional from pyparsing import cached_property @@ -133,13 +134,20 @@ class FormatterError(Exception): class Formatter(AstVisitor): """Gerber X3 compatible formatter.""" + class MacroSplitMode(Enum): + """Macro split mode.""" + + NONE = "none" + PRIMITIVES = "primitives" + PARAMETERS = "parameters" + def __init__( # noqa: PLR0913 self, *, indent_character: Literal[" ", "\t"] = " ", - macro_body_indentation: str | int = 4, - macro_param_indentation: str | int = 4, - macro_split_mode: Literal["none", "primitives", "parameters"] = "primitives", + macro_body_indent: str | int = 4, + macro_param_indent: str | int = 4, + macro_split_mode: MacroSplitMode = MacroSplitMode.PRIMITIVES, macro_end_in_new_line: bool = False, indent_block_aperture_body: str = "", indent_step_and_repeat: str = "", @@ -149,7 +157,7 @@ def __init__( # noqa: PLR0913 split_format_select: bool = False, split_aperture_definition: bool = False, split_extended_command_boundaries: bool = False, - strip_whitespace_mode: bool = False, + strip_whitespace: bool = False, ) -> None: r"""Initialize Formatter instance. @@ -157,9 +165,9 @@ def __init__( # noqa: PLR0913 ---------- indent_character: Literal[" ", "\t"], optional Character used for indentation, by default " " - macro_body_indentation : str | int, optional + macro_body_indent : str | int, optional Indentation of macro body, by default 4 - macro_param_indentation: str | int, optional + macro_param_indent: str | int, optional Indentation of macro parameters, by default 4 This indentation is added on top of macro body indentation. macro_split_mode : Literal["none", "primitives", "parameters"], optional @@ -176,6 +184,8 @@ def __init__( # noqa: PLR0913 $4=$1x0.75* 1,0,$4,$2,$3*% ``` + macro_end_in_new_line: bool, optional + _description_, by default False indent_block_aperture_body : str, optional _description_, by default "" indent_step_and_repeat : str, optional @@ -192,25 +202,24 @@ def __init__( # noqa: PLR0913 _description_, by default False split_extended_command_boundaries : bool, optional _description_, by default False - strip_whitespace_mode : bool, optional - _description_, by default False - macro_end_in_new_line: bool, optional + strip_whitespace : bool, optional _description_, by default False """ super().__init__() self.indent_character = indent_character - if isinstance(macro_body_indentation, int): - macro_body_indentation = indent_character * macro_body_indentation - self.macro_body_indentation = macro_body_indentation + if isinstance(macro_body_indent, int): + macro_body_indent = indent_character * macro_body_indent + self.macro_body_indent = macro_body_indent - if isinstance(macro_param_indentation, int): - macro_param_indentation = indent_character * macro_param_indentation - self.macro_param_indentation = macro_param_indentation + if isinstance(macro_param_indent, int): + macro_param_indent = indent_character * macro_param_indent + self.macro_param_indent = macro_param_indent self.macro_split_mode = macro_split_mode self.macro_end_in_new_line = macro_end_in_new_line + self.indent_block_aperture_body = indent_block_aperture_body self.indent_step_and_repeat = indent_step_and_repeat self.float_decimal_places = float_decimal_places @@ -220,7 +229,7 @@ def __init__( # noqa: PLR0913 self.split_format_select = split_format_select self.split_aperture_definition = split_aperture_definition self.split_extended_command_boundaries = split_extended_command_boundaries - self.strip_whitespace_mode = strip_whitespace_mode + self.strip_whitespace = strip_whitespace self._output: Optional[StringIO] = None @@ -250,7 +259,7 @@ def _fmt_double(self, value: Double) -> str: @cached_property def lf(self) -> str: """Get end of line character.""" - return "" if self.strip_whitespace_mode else "\n" + return "" if self.strip_whitespace else "\n" @contextmanager def _command(self, cmd: str) -> Generator[None, None, None]: @@ -837,10 +846,7 @@ def on_coordinate_j(self, node: CoordinateJ) -> None: def on_code_0(self, node: Code0) -> None: """Handle `Code0` node.""" - primitive_lf = ( - self.lf if self.macro_split_mode in ("primitives", "parameters") else "" - ) - self._write(f"{primitive_lf}0{node.string}*") + self._write(f"{self._macro_primitive_lf}0{node.string}*") def on_code_1(self, node: Code1) -> None: """Handle `Code1` node.""" @@ -857,13 +863,34 @@ def on_code_1(self, node: Code1) -> None: node.rotation.visit(self) self._write("*") - @property + @cached_property def _macro_primitive_lf(self) -> str: - return self.lf if self.macro_split_mode in ("primitives", "parameters") else "" + if self.macro_split_mode == self.MacroSplitMode.NONE or self.strip_whitespace: + return "" - @property + if self.macro_split_mode in ( + self.MacroSplitMode.PRIMITIVES, + self.MacroSplitMode.PARAMETERS, + ): + return self.lf + self.macro_body_indent + + msg = f"Unsupported macro split mode: {self.macro_split_mode}" + raise NotImplementedError(msg) + + @cached_property def _macro_param_lf(self) -> str: - return self.lf if self.macro_split_mode == "parameters" else "" + if ( + self.macro_split_mode + in (self.MacroSplitMode.NONE, self.MacroSplitMode.PRIMITIVES) + or self.strip_whitespace + ): + return "" + + if self.macro_split_mode == self.MacroSplitMode.PARAMETERS: + return self.lf + self.macro_param_indent + self.macro_body_indent + + msg = f"Unsupported macro split mode: {self.macro_split_mode}" + raise NotImplementedError(msg) def on_code_2(self, node: Code2) -> None: """Handle `Code2` node.""" diff --git a/test/conftest.py b/test/conftest.py index 64798572..aab18708 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -81,9 +81,10 @@ def asset_loader() -> AssetLoader: start_time = datetime.datetime.now(tz=tzlocal.get_localzone()) log_file_path = ( - Path.cwd() / "log" / "test" / f"{start_time.isoformat(timespec='hours')}.log" + Path.cwd() / "log" / "test" / f"{start_time.strftime("%Y_%m_%d_%H_%M_%S")}.log" ) log_file_path.parent.mkdir(0o777, parents=True, exist_ok=True) +log_file_path.touch(exist_ok=True) file_handler = logging.FileHandler(log_file_path.as_posix(), encoding="utf-8") file_handler.setLevel(logging.DEBUG) logger.addHandler(file_handler) diff --git a/test/gerberx3/common.py b/test/gerberx3/common.py index 109f2111..165a09ea 100644 --- a/test/gerberx3/common.py +++ b/test/gerberx3/common.py @@ -202,7 +202,7 @@ def _prepare_repository(self) -> None: if not self.repository_directory.exists(): porcelain.clone( "https://github.com/Argmaster/pygerber-reference-assets", - self.repository_directory.as_posix(), + self.repository_directory, ) repository = Repo(self.repository_directory.as_posix()) sha = repository.head().decode(encoding="utf-8") diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 0b405f1b..4d2ef861 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from io import StringIO +from typing import Any import pytest @@ -86,3 +87,161 @@ def test_formatter(asset: Asset, config: Config) -> None: assert formatted_ast.model_dump_json(serialize_as_any=True) == ast.model_dump_json( serialize_as_any=True ) + + +DONUT_MACRO_SOURCE = """%AMDonut* +1,1,$1,$2,$3* +$4=$1x0.75* +1,0,$4,$2,$3* +% +""" + + +def test_indent_character_space() -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character=" ", + macro_body_indent=4, + macro_split_mode=Formatter.MacroSplitMode.PRIMITIVES, + macro_end_in_new_line=False, + ) + assert ( + formatted_source + == """%AMDonut* + 1,1,$1,$2,$3* + $4=($1x0.75)* + 1,0,$4,$2,$3*% +""" + ) + + +def _format(source: str, **kwargs: Any) -> str: + ast = Parser().parse(source) + output_buffer = StringIO() + Formatter(**kwargs).format(ast, output_buffer) + + output_buffer.seek(0) + return output_buffer.read() + + +def test_indent_character_tab() -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character="\t", + macro_body_indent=1, + macro_split_mode=Formatter.MacroSplitMode.PRIMITIVES, + macro_end_in_new_line=False, + ) + assert ( + formatted_source + == """%AMDonut* +\t1,1,$1,$2,$3* +\t$4=($1x0.75)* +\t1,0,$4,$2,$3*% +""" + ) + + +class TestMacroSplitMode: + + def test_none(self) -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character=" ", + macro_body_indent=4, + macro_split_mode=Formatter.MacroSplitMode.NONE, + macro_end_in_new_line=False, + ) + assert ( + formatted_source + == """%AMDonut*1,1,$1,$2,$3*$4=($1x0.75)*1,0,$4,$2,$3*% +""" + ) + + def test_parameters(self) -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character=" ", + macro_body_indent=4, + macro_param_indent=4, + macro_split_mode=Formatter.MacroSplitMode.PARAMETERS, + macro_end_in_new_line=False, + ) + assert ( + formatted_source + == """%AMDonut* + 1, + 1, + $1, + $2, + $3* + $4=($1x0.75)* + 1, + 0, + $4, + $2, + $3*% +""" + ) + + def test_none__macro_end_in_new_line(self) -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character=" ", + macro_body_indent=4, + macro_param_indent=4, + macro_split_mode=Formatter.MacroSplitMode.NONE, + macro_end_in_new_line=True, + ) + assert ( + formatted_source + == """%AMDonut*1,1,$1,$2,$3*$4=($1x0.75)*1,0,$4,$2,$3* +% +""" + ) + + def test_primitives__macro_end_in_new_line(self) -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character=" ", + macro_body_indent=4, + macro_param_indent=4, + macro_split_mode=Formatter.MacroSplitMode.PRIMITIVES, + macro_end_in_new_line=True, + ) + assert ( + formatted_source + == """%AMDonut* + 1,1,$1,$2,$3* + $4=($1x0.75)* + 1,0,$4,$2,$3* +% +""" + ) + + def test_parameters_macro_end_in_new_line(self) -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character=" ", + macro_body_indent=4, + macro_param_indent=4, + macro_split_mode=Formatter.MacroSplitMode.PARAMETERS, + macro_end_in_new_line=True, + ) + assert ( + formatted_source + == """%AMDonut* + 1, + 1, + $1, + $2, + $3* + $4=($1x0.75)* + 1, + 0, + $4, + $2, + $3* +% +""" + ) From 471a1731cfb7d84f0d6abe4734a9b638f4cbfc42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 17 Aug 2024 19:56:39 +0200 Subject: [PATCH 45/91] Add intendtation options implementation --- src/pygerber/gerberx3/formatter.py | 382 ++++++++++++++++++++++++----- test/gerberx3/test_formatter.py | 115 +++++++++ 2 files changed, 434 insertions(+), 63 deletions(-) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index f8173927..dec93dea 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -6,9 +6,18 @@ from contextlib import contextmanager from enum import Enum -from typing import TYPE_CHECKING, Generator, Literal, Optional +from functools import wraps +from typing import ( + TYPE_CHECKING, + Callable, + Generator, + Literal, + Optional, + TypeVar, +) from pyparsing import cached_property +from typing_extensions import ParamSpec from pygerber.gerberx3.ast.nodes.types import Double from pygerber.gerberx3.ast.visitor import AstVisitor @@ -131,6 +140,10 @@ class FormatterError(Exception): """Formatter error.""" +ParamT = ParamSpec("ParamT") +ReturnT = TypeVar("ReturnT") + + class Formatter(AstVisitor): """Gerber X3 compatible formatter.""" @@ -145,18 +158,18 @@ def __init__( # noqa: PLR0913 self, *, indent_character: Literal[" ", "\t"] = " ", - macro_body_indent: str | int = 4, - macro_param_indent: str | int = 4, + macro_body_indent: str | int = 0, + macro_param_indent: str | int = 0, macro_split_mode: MacroSplitMode = MacroSplitMode.PRIMITIVES, macro_end_in_new_line: bool = False, - indent_block_aperture_body: str = "", - indent_step_and_repeat: str = "", - float_decimal_places: int = 6, + block_aperture_body_indent: str | int = 0, + step_and_repeat_body_indent: str | int = 0, + float_decimal_places: int = 8, float_trim_trailing_zeros: bool = True, - indent_non_aperture_select_commands: bool = False, - split_format_select: bool = False, - split_aperture_definition: bool = False, - split_extended_command_boundaries: bool = False, + d01_indent: int | str = 0, + d02_indent: int | str = 0, + d03_indent: int | str = 0, + line_end: Literal["\n", "\r\n"] = "\n", strip_whitespace: bool = False, ) -> None: r"""Initialize Formatter instance. @@ -166,17 +179,18 @@ def __init__( # noqa: PLR0913 indent_character: Literal[" ", "\t"], optional Character used for indentation, by default " " macro_body_indent : str | int, optional - Indentation of macro body, by default 4 + Indentation of macro body, by default 0 macro_param_indent: str | int, optional - Indentation of macro parameters, by default 4 + Indentation of macro parameters, by default 0 This indentation is added on top of macro body indentation. - macro_split_mode : Literal["none", "primitives", "parameters"], optional - Changes how macro definitions are formatted, by default "none" - When "none" is selected, macro will be formatted as a single line. + This has effect only when `macro_split_mode` is `PARAMETERS`. + macro_split_mode : `Formatter.MacroSplitMode`, optional + Changes how macro definitions are formatted, by default `NONE` + When `NONE` is selected, macro will be formatted as a single line. ```gerber %AMDonut*1,1,$1,$2,$3*$4=$1x0.75*1,0,$4,$2,$3*% ``` - When "primitives" is selected, macro will be formatted with each primitive + When `PRIMITIVES` is selected, macro will be formatted with each primitive on a new line. ```gerber %AMDonut* @@ -184,26 +198,49 @@ def __init__( # noqa: PLR0913 $4=$1x0.75* 1,0,$4,$2,$3*% ``` + When `PARAMETERS` is selected, macro will be formatted with each primitive + on a new line and each parameter of a primitive on a new line. + ```gerber + %AMDonut* + 1, + 1, + $1, + $2, + $3* + $4=$1x0.75* + 1, + 0, + $4, + $2, + $3*% + ``` + Use `macro_body_indent` and `macro_param_indent` to control indentation. macro_end_in_new_line: bool, optional - _description_, by default False - indent_block_aperture_body : str, optional - _description_, by default "" - indent_step_and_repeat : str, optional - _description_, by default "" + Place % sign which marks the end of macro in new line, by default False + block_aperture_body_indent : str | int, optional + Indentation of block aperture definition body, by default 0 + This indentations stacks for nested block apertures. + step_and_repeat_body_indent : str | int, optional + Indentation of step and repeat definition body, by default 0 + This indentations stacks for nested step and repeat blocks. float_decimal_places : int, optional - _description_, by default 6 + Limit number of decimal places shown for float values, by default 8 float_trim_trailing_zeros : bool, optional - _description_, by default True - indent_non_aperture_select_commands : bool, optional - _description_, by default False - split_format_select : bool, optional - _description_, by default False - split_aperture_definition : bool, optional - _description_, by default False - split_extended_command_boundaries : bool, optional - _description_, by default False + Remove trailing zeros from floats, by default True + When this is enabled, after floating point number is formatted with respect + to `float_decimal_places`, trailing zeros are removed. If all zeros after + decimal point are removed, decimal point is also removed. + d01_indent : str | int, optional + Custom indentation of D01 command, by default 0 + d02_indent : str | int, optional + Custom indentation of D02 command, by default 0 + d03_indent : str | int, optional + Custom indentation of D03 command, by default 0 strip_whitespace : bool, optional - _description_, by default False + Remove all semantically insignificant whitespace, by default False + line_end : Literal["\n", "\r\n"], optional + Line ending character, Unix or Windows style, by default "\n" (Unix style) + If `strip_whitespace` is enabled, no line end will be used. """ super().__init__() @@ -220,18 +257,47 @@ def __init__( # noqa: PLR0913 self.macro_split_mode = macro_split_mode self.macro_end_in_new_line = macro_end_in_new_line - self.indent_block_aperture_body = indent_block_aperture_body - self.indent_step_and_repeat = indent_step_and_repeat + if isinstance(block_aperture_body_indent, int): + block_aperture_body_indent = indent_character * block_aperture_body_indent + self.block_aperture_body_indent = block_aperture_body_indent + + if isinstance(step_and_repeat_body_indent, int): + step_and_repeat_body_indent = indent_character * step_and_repeat_body_indent + self.step_and_repeat_body_indent = step_and_repeat_body_indent + self.float_decimal_places = float_decimal_places self.float_trim_trailing_zeros = float_trim_trailing_zeros - self.indent_non_aperture_select_commands = indent_non_aperture_select_commands - self.split_format_select = split_format_select - self.split_aperture_definition = split_aperture_definition - self.split_extended_command_boundaries = split_extended_command_boundaries + + if isinstance(d01_indent, int): + d01_indent = indent_character * d01_indent + self.d01_indent = d01_indent + + if isinstance(d02_indent, int): + d02_indent = indent_character * d02_indent + self.d02_indent = d02_indent + + if isinstance(d03_indent, int): + d03_indent = indent_character * d03_indent + self.d03_indent = d03_indent + self.strip_whitespace = strip_whitespace + self.lf = line_end + + if self.strip_whitespace: + self.lf = "" + self.indent_character = "" + self.macro_body_indent = "" + self.macro_param_indent = "" + self.block_aperture_body_indent = "" + self.step_and_repeat_body_indent = "" + self.d01_indent = "" + self.d02_indent = "" + self.d03_indent = "" + self._output: Optional[StringIO] = None + self._base_indent: str = "" def format(self, source: File, output: StringIO) -> None: """Format Gerber AST according to rules specified in Formatter constructor.""" @@ -240,6 +306,7 @@ def format(self, source: File, output: StringIO) -> None: self.on_file(source) finally: self._output = None + self._base_indent = "" @property def output(self) -> StringIO: @@ -256,16 +323,98 @@ def _fmt_double(self, value: Double) -> str: return double.rstrip("0").rstrip(".") return double - @cached_property - def lf(self) -> str: - """Get end of line character.""" - return "" if self.strip_whitespace else "\n" + @staticmethod + def _insert_base_indent( + function: Callable[ParamT, ReturnT] + ) -> Callable[ParamT, ReturnT]: + + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + self._write(self._base_indent) + return function(*args, **kwargs) + + return _ + + @staticmethod + def _insert_extra_indent( + variable_name: str, + ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: + + def _decorator( + function: Callable[ParamT, ReturnT] + ) -> Callable[ParamT, ReturnT]: + + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + + self._write(getattr(self, variable_name)) + + return function(*args, **kwargs) + + return _ + + return _decorator + + @staticmethod + def _increase_base_indent( + variable_name: str, + ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: + + def _decorator( + function: Callable[ParamT, ReturnT] + ) -> Callable[ParamT, ReturnT]: + + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + + self._base_indent += getattr(self, variable_name) + + return function(*args, **kwargs) + + return _ + + return _decorator + + @staticmethod + def _decrease_base_indent( + variable_name: str, + ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: + + def _decorator( + function: Callable[ParamT, ReturnT] + ) -> Callable[ParamT, ReturnT]: + + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + + indent_delta = getattr(self, variable_name) + if self._base_indent.endswith(indent_delta): + self._base_indent = self._base_indent[: -len(indent_delta)] + + return function(*args, **kwargs) + + return _ + + return _decorator @contextmanager - def _command(self, cmd: str) -> Generator[None, None, None]: + def _command( + self, cmd: str, *, asterisk: bool = True, lf: bool = True + ) -> Generator[None, None, None]: self._write(cmd) yield - self._write(f"*{self.lf}") + if asterisk: + self._write("*") + if lf: + self._write(self.lf) @contextmanager def _extended_command(self, cmd: str) -> Generator[None, None, None]: @@ -276,16 +425,21 @@ def _extended_command(self, cmd: str) -> Generator[None, None, None]: def _write(self, value: str) -> None: self.output.write(value) + @_decrease_base_indent("block_aperture_body_indent") + @_insert_base_indent def on_ab_close(self, node: ABclose) -> None: # noqa: ARG002 """Handle `ABclose` node.""" with self._extended_command("AB"): pass + @_insert_base_indent + @_increase_base_indent("block_aperture_body_indent") def on_ab_open(self, node: ABopen) -> None: """Handle `ABopen` node.""" with self._extended_command("AB"): self._write(node.aperture_identifier) + @_insert_base_indent def on_adc(self, node: ADC) -> None: """Handle `AD` circle node.""" with self._extended_command(f"AD{node.aperture_identifier}C,"): @@ -294,6 +448,7 @@ def on_adc(self, node: ADC) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") + @_insert_base_indent def on_adr(self, node: ADR) -> None: """Handle `AD` rectangle node.""" with self._extended_command(f"AD{node.aperture_identifier}R,"): @@ -303,6 +458,7 @@ def on_adr(self, node: ADR) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") + @_insert_base_indent def on_ado(self, node: ADO) -> None: """Handle `AD` obround node.""" with self._extended_command(f"AD{node.aperture_identifier}O,"): @@ -312,6 +468,7 @@ def on_ado(self, node: ADO) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") + @_insert_base_indent def on_adp(self, node: ADP) -> None: """Handle `AD` polygon node.""" with self._extended_command(f"AD{node.aperture_identifier}P,"): @@ -324,6 +481,7 @@ def on_adp(self, node: ADP) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") + @_insert_base_indent def on_ad_macro(self, node: ADmacro) -> None: """Handle `AD` macro node.""" with self._extended_command(f"AD{node.aperture_identifier}{node.name}"): @@ -333,6 +491,7 @@ def on_ad_macro(self, node: ADmacro) -> None: for param in rest: self._write(f"X{param}") + @_insert_base_indent def on_am_close(self, node: AMclose) -> None: """Handle `AMclose` node.""" super().on_am_close(node) @@ -340,16 +499,21 @@ def on_am_close(self, node: AMclose) -> None: self._write(f"{self.lf}") self._write(f"%{self.lf}") + @_insert_base_indent def on_am_open(self, node: AMopen) -> None: """Handle `AMopen` node.""" super().on_am_open(node) self._write(f"%AM{node.name}*") + @_decrease_base_indent("step_and_repeat_body_indent") + @_insert_base_indent def on_sr_close(self, node: SRclose) -> None: # noqa: ARG002 """Handle `SRclose` node.""" with self._extended_command("SR"): pass + @_insert_base_indent + @_increase_base_indent("step_and_repeat_body_indent") def on_sr_open(self, node: SRopen) -> None: """Handle `SRopen` node.""" with self._extended_command("SR"): @@ -367,6 +531,7 @@ def on_sr_open(self, node: SRopen) -> None: # Attribute + @_insert_base_indent def on_ta_user_name(self, node: TA_UserName) -> None: """Handle `TA_UserName` node.""" with self._extended_command(f"TA{node.user_name}"): @@ -374,6 +539,7 @@ def on_ta_user_name(self, node: TA_UserName) -> None: self._write(",") self._write(field) + @_insert_base_indent def on_ta_aper_function(self, node: TA_AperFunction) -> None: """Handle `TA_AperFunction` node.""" with self._extended_command("TA.AperFunction"): @@ -385,6 +551,7 @@ def on_ta_aper_function(self, node: TA_AperFunction) -> None: self._write(",") self._write(field) + @_insert_base_indent def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: """Handle `TA_DrillTolerance` node.""" with self._extended_command("TA.DrillTolerance"): @@ -396,6 +563,7 @@ def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: self._write(",") self._write(self._fmt_double(node.minus_tolerance)) + @_insert_base_indent def on_ta_flash_text(self, node: TA_FlashText) -> None: """Handle `TA_FlashText` node.""" with self._extended_command("TA.FlashText"): @@ -433,12 +601,14 @@ def on_ta_flash_text(self, node: TA_FlashText) -> None: self._write(",") self._write(comment) + @_insert_base_indent def on_td(self, node: TD) -> None: """Handle `TD` node.""" with self._extended_command("TD"): if node.name is not None: self._write(node.name) + @_insert_base_indent def on_tf_user_name(self, node: TF_UserName) -> None: """Handle `TF_UserName` node.""" with self._extended_command(f"TF{node.user_name}"): @@ -446,6 +616,7 @@ def on_tf_user_name(self, node: TF_UserName) -> None: self._write(",") self._write(field) + @_insert_base_indent def on_tf_part(self, node: TF_Part) -> None: """Handle `TF_Part` node.""" with self._extended_command("TF.Part,"): @@ -455,6 +626,7 @@ def on_tf_part(self, node: TF_Part) -> None: self._write(",") self._write(field) + @_insert_base_indent def on_tf_file_function(self, node: TF_FileFunction) -> None: """Handle `TF_FileFunction` node.""" with self._extended_command("TF.FileFunction,"): @@ -464,11 +636,13 @@ def on_tf_file_function(self, node: TF_FileFunction) -> None: self._write(",") self._write(field) + @_insert_base_indent def on_tf_file_polarity(self, node: TF_FilePolarity) -> None: """Handle `TF_FilePolarity` node.""" with self._extended_command("TF.FilePolarity,"): self._write(node.polarity) + @_insert_base_indent def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: """Handle `TF_SameCoordinates` node.""" with self._extended_command("TF.SameCoordinates"): @@ -476,6 +650,7 @@ def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: self._write(",") self._write(node.identifier) + @_insert_base_indent def on_tf_creation_date(self, node: TF_CreationDate) -> None: """Handle `TF_CreationDate` node.""" with self._extended_command("TF.CreationDate"): @@ -483,6 +658,7 @@ def on_tf_creation_date(self, node: TF_CreationDate) -> None: self._write(",") self._write(node.creation_date.isoformat()) + @_insert_base_indent def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: """Handle `TF_GenerationSoftware` node.""" with self._extended_command("TF.GenerationSoftware"): @@ -498,6 +674,7 @@ def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: if node.version is not None: self._write(node.version) + @_insert_base_indent def on_tf_project_id(self, node: TF_ProjectId) -> None: """Handle `TF_ProjectId` node.""" with self._extended_command("TF.ProjectId"): @@ -513,12 +690,14 @@ def on_tf_project_id(self, node: TF_ProjectId) -> None: if node.revision is not None: self._write(node.revision) + @_insert_base_indent def on_tf_md5(self, node: TF_MD5) -> None: """Handle `TF_MD5` node.""" with self._extended_command("TF.MD5"): self._write(",") self._write(node.md5) + @_insert_base_indent def on_to_user_name(self, node: TO_UserName) -> None: """Handle `TO_UserName` node.""" with self._extended_command(f"TO{node.user_name}"): @@ -526,6 +705,7 @@ def on_to_user_name(self, node: TO_UserName) -> None: self._write(",") self._write(field) + @_insert_base_indent def on_to_n(self, node: TO_N) -> None: """Handle `TO_N` node.""" with self._extended_command("TO.N"): @@ -533,6 +713,7 @@ def on_to_n(self, node: TO_N) -> None: self._write(",") self._write(field) + @_insert_base_indent def on_to_p(self, node: TO_P) -> None: """Handle `TO_P` node`.""" with self._extended_command("TO.P"): @@ -544,78 +725,91 @@ def on_to_p(self, node: TO_P) -> None: self._write(",") self._write(node.function) + @_insert_base_indent def on_to_c(self, node: TO_C) -> None: """Handle `TO_C` node.""" with self._extended_command("TO.C"): self._write(",") self._write(node.refdes) + @_insert_base_indent def on_to_crot(self, node: TO_CRot) -> None: """Handle `TO_CRot` node.""" with self._extended_command("TO.CRot"): self._write(",") self._write(self._fmt_double(node.angle)) + @_insert_base_indent def on_to_cmfr(self, node: TO_CMfr) -> None: """Handle `TO_CMfr` node.""" with self._extended_command("TO.CMfr"): self._write(",") self._write(node.manufacturer) + @_insert_base_indent def on_to_cmnp(self, node: TO_CMNP) -> None: """Handle `TO_CMNP` node.""" with self._extended_command("TO.CMPN"): self._write(",") self._write(node.part_number) + @_insert_base_indent def on_to_cval(self, node: TO_CVal) -> None: """Handle `TO_CVal` node.""" with self._extended_command("TO.CVal"): self._write(",") self._write(node.value) + @_insert_base_indent def on_to_cmnt(self, node: TO_CMnt) -> None: """Handle `TO_CVal` node.""" with self._extended_command("TO.CMnt"): self._write(",") self._write(node.mount.value) + @_insert_base_indent def on_to_cftp(self, node: TO_CFtp) -> None: """Handle `TO_Cftp` node.""" with self._extended_command("TO.CFtp"): self._write(",") self._write(node.footprint) + @_insert_base_indent def on_to_cpgn(self, node: TO_CPgN) -> None: """Handle `TO_CPgN` node.""" with self._extended_command("TO.CPgN"): self._write(",") self._write(node.name) + @_insert_base_indent def on_to_cpgd(self, node: TO_CPgD) -> None: """Handle `TO_CPgD` node.""" with self._extended_command("TO.CPgD"): self._write(",") self._write(node.description) + @_insert_base_indent def on_to_chgt(self, node: TO_CHgt) -> None: """Handle `TO_CHgt` node.""" with self._extended_command("TO.CHgt"): self._write(",") self._write(self._fmt_double(node.height)) + @_insert_base_indent def on_to_clbn(self, node: TO_CLbN) -> None: """Handle `TO_CLbN` node.""" with self._extended_command("TO.CLbn"): self._write(",") self._write(node.name) + @_insert_base_indent def on_to_clbd(self, node: TO_CLbD) -> None: """Handle `TO_CLbD` node.""" with self._extended_command("TO.CLbD"): self._write(",") self._write(node.description) + @_insert_base_indent def on_to_csup(self, node: TO_CSup) -> None: """Handle `TO_CSup` node.""" with self._extended_command("TO.CSup"): @@ -629,24 +823,31 @@ def on_to_csup(self, node: TO_CSup) -> None: # D codes + @_insert_base_indent + @_insert_extra_indent("d01_indent") def on_d01(self, node: D01) -> None: """Handle `D01` node.""" super().on_d01(node) with self._command("D01"): pass + @_insert_base_indent + @_insert_extra_indent("d02_indent") def on_d02(self, node: D02) -> None: """Handle `D02` node.""" super().on_d02(node) with self._command("D02"): pass + @_insert_base_indent + @_insert_extra_indent("d03_indent") def on_d03(self, node: D03) -> None: """Handle `D03` node.""" super().on_d03(node) with self._command("D03"): pass + @_insert_base_indent def on_dnn(self, node: Dnn) -> None: """Handle `Dnn` node.""" with self._command(node.value): @@ -654,98 +855,134 @@ def on_dnn(self, node: Dnn) -> None: # G codes + @_insert_base_indent def on_g01(self, node: G01) -> None: # noqa: ARG002 """Handle `G01` node.""" - self._write("G01*\n") + with self._command("G01"): + pass + @_insert_base_indent def on_g02(self, node: G02) -> None: # noqa: ARG002 """Handle `G02` node.""" - self._write("G02*\n") + with self._command("G02"): + pass + @_insert_base_indent def on_g03(self, node: G03) -> None: # noqa: ARG002 """Handle `G03` node.""" - self._write("G03*\n") + with self._command("G03"): + pass + @_insert_base_indent def on_g04(self, node: G04) -> None: """Handle `G04` node.""" - self._write(f"G04{node.string or ''}*\n") + with self._command(f"G04{node.string or ''}"): + pass + @_insert_base_indent def on_g36(self, node: G36) -> None: # noqa: ARG002 """Handle `G36` node.""" - self._write("G36*\n") + with self._command("G36"): + pass + @_insert_base_indent def on_g37(self, node: G37) -> None: # noqa: ARG002 """Handle `G37` node.""" - self._write("G37*\n") + with self._command("G37"): + pass + @_insert_base_indent def on_g54(self, node: G54) -> None: """Handle `G54` node.""" - self._write("G54") + with self._command("G54", asterisk=False, lf=False): + pass super().on_g54(node) + @_insert_base_indent def on_g55(self, node: G55) -> None: """Handle `G55` node.""" - self._write("G55") + with self._command("G55", asterisk=False, lf=False): + pass super().on_g55(node) + @_insert_base_indent def on_g70(self, node: G70) -> None: # noqa: ARG002 """Handle `G70` node.""" - self._write("G70*\n") + with self._command("G70"): + pass + @_insert_base_indent def on_g71(self, node: G71) -> None: # noqa: ARG002 """Handle `G71` node.""" - self._write("G71*\n") + with self._command("G71"): + pass + @_insert_base_indent def on_g74(self, node: G74) -> None: # noqa: ARG002 """Handle `G74` node.""" - self._write("G74*\n") + with self._command("G74"): + pass + @_insert_base_indent def on_g75(self, node: G75) -> None: # noqa: ARG002 """Handle `G75` node.""" - self._write("G75*\n") + with self._command("G75"): + pass + @_insert_base_indent def on_g90(self, node: G90) -> None: # noqa: ARG002 """Handle `G90` node.""" - self._write("G90*\n") + with self._command("G90"): + pass + @_insert_base_indent def on_g91(self, node: G91) -> None: # noqa: ARG002 """Handle `G91` node.""" - self._write("G91*\n") + with self._command("G91"): + pass # Load + @_insert_base_indent def on_lm(self, node: LM) -> None: """Handle `LM` node.""" super().on_lm(node) + @_insert_base_indent def on_ln(self, node: LN) -> None: """Handle `LN` node.""" super().on_ln(node) + @_insert_base_indent def on_lp(self, node: LP) -> None: """Handle `LP` node.""" super().on_lp(node) + @_insert_base_indent def on_lr(self, node: LR) -> None: """Handle `LR` node.""" super().on_lr(node) + @_insert_base_indent def on_ls(self, node: LS) -> None: """Handle `LS` node.""" super().on_ls(node) # M Codes + @_insert_base_indent def on_m00(self, node: M00) -> None: # noqa: ARG002 """Handle `M00` node.""" with self._command("M00"): pass + @_insert_base_indent def on_m01(self, node: M01) -> None: # noqa: ARG002 """Handle `M01` node.""" with self._command("M01"): pass + @_insert_base_indent def on_m02(self, node: M02) -> None: # noqa: ARG002 """Handle `M02` node.""" with self._command("M02"): @@ -802,6 +1039,7 @@ def on_pos(self, node: Pos) -> None: self._write("+") node.operand.visit(self) + @_insert_base_indent def on_assignment(self, node: Assignment) -> None: """Handle `Assignment` node.""" self._write(self._macro_primitive_lf) @@ -844,10 +1082,12 @@ def on_coordinate_j(self, node: CoordinateJ) -> None: # Primitives + @_insert_base_indent def on_code_0(self, node: Code0) -> None: """Handle `Code0` node.""" self._write(f"{self._macro_primitive_lf}0{node.string}*") + @_insert_base_indent def on_code_1(self, node: Code1) -> None: """Handle `Code1` node.""" self._write(f"{self._macro_primitive_lf}1,{self._macro_param_lf}") @@ -865,7 +1105,7 @@ def on_code_1(self, node: Code1) -> None: @cached_property def _macro_primitive_lf(self) -> str: - if self.macro_split_mode == self.MacroSplitMode.NONE or self.strip_whitespace: + if self.macro_split_mode == self.MacroSplitMode.NONE: return "" if self.macro_split_mode in ( @@ -879,10 +1119,9 @@ def _macro_primitive_lf(self) -> str: @cached_property def _macro_param_lf(self) -> str: - if ( - self.macro_split_mode - in (self.MacroSplitMode.NONE, self.MacroSplitMode.PRIMITIVES) - or self.strip_whitespace + if self.macro_split_mode in ( + self.MacroSplitMode.NONE, + self.MacroSplitMode.PRIMITIVES, ): return "" @@ -892,6 +1131,7 @@ def _macro_param_lf(self) -> str: msg = f"Unsupported macro split mode: {self.macro_split_mode}" raise NotImplementedError(msg) + @_insert_base_indent def on_code_2(self, node: Code2) -> None: """Handle `Code2` node.""" self._write(f"{self._macro_primitive_lf}2,{self._macro_param_lf}") @@ -910,6 +1150,7 @@ def on_code_2(self, node: Code2) -> None: node.rotation.visit(self) self._write("*") + @_insert_base_indent def on_code_4(self, node: Code4) -> None: """Handle `Code4` node.""" self._write(f"{self._macro_primitive_lf}4,{self._macro_param_lf}") @@ -927,6 +1168,7 @@ def on_code_4(self, node: Code4) -> None: node.rotation.visit(self) self._write("*") + @_insert_base_indent def on_code_5(self, node: Code5) -> None: """Handle `Code5` node.""" self._write(f"{self._macro_primitive_lf}5,{self._macro_param_lf}") @@ -943,6 +1185,7 @@ def on_code_5(self, node: Code5) -> None: node.rotation.visit(self) self._write("*") + @_insert_base_indent def on_code_6(self, node: Code6) -> None: """Handle `Code6` node.""" self._write(f"{self._macro_primitive_lf}6,{self._macro_param_lf}") @@ -965,6 +1208,7 @@ def on_code_6(self, node: Code6) -> None: node.rotation.visit(self) self._write("*") + @_insert_base_indent def on_code_7(self, node: Code7) -> None: """Handle `Code7` node.""" self._write(f"{self._macro_primitive_lf}7,{self._macro_param_lf}") @@ -981,6 +1225,7 @@ def on_code_7(self, node: Code7) -> None: node.rotation.visit(self) self._write("*") + @_insert_base_indent def on_code_20(self, node: Code20) -> None: """Handle `Code20` node.""" self._write(f"{self._macro_primitive_lf}20,{self._macro_param_lf}") @@ -999,6 +1244,7 @@ def on_code_20(self, node: Code20) -> None: node.rotation.visit(self) self._write("*") + @_insert_base_indent def on_code_21(self, node: Code21) -> None: """Handle `Code21` node.""" self._write(f"{self._macro_primitive_lf}21,{self._macro_param_lf}") @@ -1015,6 +1261,7 @@ def on_code_21(self, node: Code21) -> None: node.rotation.visit(self) self._write("*") + @_insert_base_indent def on_code_22(self, node: Code22) -> None: """Handle `Code22` node.""" self._write(f"{self._macro_primitive_lf}22,{self._macro_param_lf}") @@ -1033,11 +1280,13 @@ def on_code_22(self, node: Code22) -> None: # Properties + @_insert_base_indent def on_as(self, node: AS) -> None: """Handle `AS` node.""" with self._extended_command("AS"): self._write(node.correspondence.value) + @_insert_base_indent def on_fs(self, node: FS) -> None: """Handle `FS` node.""" with self._extended_command("FS"): @@ -1046,32 +1295,38 @@ def on_fs(self, node: FS) -> None: self._write(f"X{node.x_integral}{node.x_decimal}") self._write(f"Y{node.y_integral}{node.y_decimal}") + @_insert_base_indent def on_in(self, node: IN) -> None: """Handle `IN` node.""" with self._extended_command("IN"): self._write(node.name) + @_insert_base_indent def on_ip(self, node: IP) -> None: """Handle `IP` node.""" with self._extended_command("IP"): self._write(node.polarity) + @_insert_base_indent def on_ir(self, node: IR) -> None: """Handle `IR` node.""" with self._extended_command("IR"): self._write(self._fmt_double(node.rotation_degrees)) + @_insert_base_indent def on_mi(self, node: MI) -> None: """Handle `MI` node.""" with self._extended_command("MI"): self._write(f"A{node.a_mirroring}") self._write(f"B{node.b_mirroring}") + @_insert_base_indent def on_mo(self, node: MO) -> None: """Handle `MO` node.""" with self._extended_command("MO"): self._write(node.mode.value) + @_insert_base_indent def on_of(self, node: OF) -> None: """Handle `OF` node.""" with self._extended_command("OF"): @@ -1080,6 +1335,7 @@ def on_of(self, node: OF) -> None: if node.b_offset is not None: self._write(f"B{node.b_offset}") + @_insert_base_indent def on_sf(self, node: SF) -> None: """Handle `SF` node.""" with self._extended_command("SF"): diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 4d2ef861..2d2a116b 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -2,6 +2,7 @@ from dataclasses import dataclass from io import StringIO +from pathlib import Path from typing import Any import pytest @@ -158,6 +159,23 @@ def test_none(self) -> None: """ ) + def test_primitives(self) -> None: + formatted_source = _format( + DONUT_MACRO_SOURCE, + indent_character=" ", + macro_body_indent=4, + macro_split_mode=Formatter.MacroSplitMode.PRIMITIVES, + macro_end_in_new_line=False, + ) + assert ( + formatted_source + == """%AMDonut* + 1,1,$1,$2,$3* + $4=($1x0.75)* + 1,0,$4,$2,$3*% +""" + ) + def test_parameters(self) -> None: formatted_source = _format( DONUT_MACRO_SOURCE, @@ -245,3 +263,100 @@ def test_parameters_macro_end_in_new_line(self) -> None: % """ ) + + +APERTURE_BLOCK_SOURCE = """%ABD12*% +%ADD11C,0.5*% +D10* +G01* +X-2500000Y-1000000D03* +Y1000000D03* +D11* +X-2500000Y-1000000D03* +X-500000Y-1000000D02* +X2500000D01* +G75* +G03* +X500000Y1000000I-2000000J0D01* +G01* +%AB*% +""" + +APERTURE_BLOCK_NESTED_SOURCE = """%ABD102*% +G04 Define nested block aperture 101, consisting of 2x2 flashes of aperture 100* +%ABD101*% +D100* +X0Y0D03* +X0Y70000000D03* +X100000000Y0D03* +X100000000Y70000000D03* +%AB*% +D101* +X0Y0D03* +X0Y160000000D03* +X0Y320000000D03* +X230000000Y0D03* +X230000000Y160000000D03* +X230000000Y320000000D03* +D12* +X19500000Y-10000000D03* +%AB*% +""" + + +def test_block_aperture_indent() -> None: + formatted_source = _format( + APERTURE_BLOCK_SOURCE, + indent_character=" ", + block_aperture_body_indent=4, + ) + assert ( + formatted_source + == """%ABD12*% + %ADD11C,0.5*% + D10* + G01* + X-2500000Y-1000000D03* + Y1000000D03* + D11* + X-2500000Y-1000000D03* + X-500000Y-1000000D02* + X2500000D01* + G75* + G03* + X500000Y1000000I-2000000J0D01* + G01* +%AB*% +""" + ) + + +def test_nested_block_aperture_indent() -> None: + formatted_source = _format( + APERTURE_BLOCK_NESTED_SOURCE, + indent_character=" ", + block_aperture_body_indent=4, + ) + assert ( + formatted_source + == """%ABD102*% + G04 Define nested block aperture 101, consisting of 2x2 flashes of aperture 100* + %ABD101*% + D100* + X0Y0D03* + X0Y70000000D03* + X100000000Y0D03* + X100000000Y70000000D03* + %AB*% + D101* + X0Y0D03* + X0Y160000000D03* + X0Y320000000D03* + X230000000Y0D03* + X230000000Y160000000D03* + X230000000Y320000000D03* + D12* + X19500000Y-10000000D03* +%AB*% +""" + ) From 1a1b0cf69f3097a835601a29d32ecc112459a895 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 17 Aug 2024 19:57:08 +0200 Subject: [PATCH 46/91] Add typing-extensions package to dependencies --- poetry.lock | 2 +- pyproject.toml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/poetry.lock b/poetry.lock index 8003e61d..91089b3a 100644 --- a/poetry.lock +++ b/poetry.lock @@ -3137,4 +3137,4 @@ svg = ["drawsvg"] [metadata] lock-version = "2.0" python-versions = "^3.8,<3.13" -content-hash = "71466227e1c8699154c674d49b812733ea306be3bc6f92ec7f0d09f5ba1b7387" +content-hash = "b626484dbfb7283f1967797f070661aa8b38e697a54f6a18933b78defb999273" diff --git a/pyproject.toml b/pyproject.toml index 4952cf9d..80da9301 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -58,6 +58,7 @@ numpy = [ pygls = { version = "^1.0.2", optional = true } lsprotocol = { version = "^2023.0.0a3", optional = true } drawsvg = { version = "^2.3.0", optional = true } +typing-extensions = "^4.12.2" [tool.poetry.group.dev.dependencies] mypy = "^1.6.1" From 5bbd4521c7ea8a9c31b9f26b322e86e8544b7a62 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 17 Aug 2024 22:06:45 +0200 Subject: [PATCH 47/91] Replace test logging handler with RotatingFileHandler --- test/conftest.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/test/conftest.py b/test/conftest.py index 64798572..3826ebea 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -5,6 +5,7 @@ import datetime import json import logging +import logging.handlers import os from contextlib import contextmanager, suppress from pathlib import Path @@ -80,11 +81,14 @@ def asset_loader() -> AssetLoader: logger.setLevel(logging.DEBUG) start_time = datetime.datetime.now(tz=tzlocal.get_localzone()) -log_file_path = ( - Path.cwd() / "log" / "test" / f"{start_time.isoformat(timespec='hours')}.log" -) +log_file_path = Path.cwd() / "log" / "test" / "pygerber_pytest.log" log_file_path.parent.mkdir(0o777, parents=True, exist_ok=True) -file_handler = logging.FileHandler(log_file_path.as_posix(), encoding="utf-8") +file_handler = logging.handlers.RotatingFileHandler( + log_file_path.as_posix(), + encoding="utf-8", + backupCount=16, + maxBytes=16 * 1024**2, # Max 16 MB +) file_handler.setLevel(logging.DEBUG) logger.addHandler(file_handler) From 125d9ca0447817235792c88f73b43189db09bd3f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 17 Aug 2024 22:51:31 +0200 Subject: [PATCH 48/91] Add load commands parsing --- src/pygerber/gerberx3/ast/nodes/load/LM.py | 12 +++++ src/pygerber/gerberx3/ast/nodes/load/LP.py | 10 ++++ src/pygerber/gerberx3/ast/nodes/load/LR.py | 2 + src/pygerber/gerberx3/ast/nodes/load/LS.py | 2 + .../gerberx3/parser/pyparsing/grammar.py | 47 ++++++++++++++++++- test/gerberx3/test_ast/test_ast_visitor.py | 12 ++--- 6 files changed, 78 insertions(+), 7 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LM.py b/src/pygerber/gerberx3/ast/nodes/load/LM.py index 59795d2f..759cce76 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LM.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LM.py @@ -2,6 +2,7 @@ from __future__ import annotations +from enum import Enum from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node @@ -12,9 +13,20 @@ from pygerber.gerberx3.ast.visitor import AstVisitor +class Mirroring(Enum): + """Mirroring enum.""" + + None_ = "N" + X = "X" + Y = "Y" + XY = "XY" + + class LM(Node): """Represents LM Gerber extended command.""" + mirroring: Mirroring + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_lm(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LP.py b/src/pygerber/gerberx3/ast/nodes/load/LP.py index 6984ec29..f9f048e2 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LP.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LP.py @@ -2,6 +2,7 @@ from __future__ import annotations +from enum import Enum from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node @@ -12,9 +13,18 @@ from pygerber.gerberx3.ast.visitor import AstVisitor +class Polarity(Enum): + """Polarity enum.""" + + Clear = "C" + Dark = "D" + + class LP(Node): """Represents LP Gerber extended command.""" + polarity: Polarity + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_lp(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LR.py b/src/pygerber/gerberx3/ast/nodes/load/LR.py index fa840ac9..084a2e2a 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LR.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LR.py @@ -15,6 +15,8 @@ class LR(Node): """Represents LR Gerber extended command.""" + rotation: float + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_lr(self) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LS.py b/src/pygerber/gerberx3/ast/nodes/load/LS.py index 2b765267..30a79c8f 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LS.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LS.py @@ -15,6 +15,8 @@ class LS(Node): """Represents LS Gerber extended command.""" + scale: float + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ls(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 85e05680..0769d4d4 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -76,7 +76,11 @@ from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 +from pygerber.gerberx3.ast.nodes.load.LM import LM from pygerber.gerberx3.ast.nodes.load.LN import LN +from pygerber.gerberx3.ast.nodes.load.LP import LP +from pygerber.gerberx3.ast.nodes.load.LR import LR +from pygerber.gerberx3.ast.nodes.load.LS import LS from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 @@ -1099,7 +1103,7 @@ def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: def load_commands(self) -> pp.ParserElement: """Create a parser element capable of parsing Load-commands.""" - return pp.MatchFirst([self.ln()]) + return pp.MatchFirst([self.ln(), self.lp(), self.lr(), self.ls(), self.lm()]) def ln(self) -> pp.ParserElement: """Create a parser for the LN command.""" @@ -1111,6 +1115,47 @@ def ln(self) -> pp.ParserElement: .set_name("LN") ) + def lp(self) -> pp.ParserElement: + """Create a parser for the LP command.""" + return ( + self._extended_command( + pp.Literal("LP") + pp.one_of(["C", "D"]).set_results_name("polarity") + ) + .set_parse_action(self.make_unpack_callback(LP)) + .set_name("LP") + ) + + def lr(self) -> pp.ParserElement: + """Create a parser for the LR command.""" + return ( + self._extended_command( + pp.Literal("LR") + self.double.set_results_name("rotation") + ) + .set_parse_action(self.make_unpack_callback(LR)) + .set_name("LR") + ) + + def ls(self) -> pp.ParserElement: + """Create a parser for the LS command.""" + return ( + self._extended_command( + pp.Literal("LS") + self.double.set_results_name("scale") + ) + .set_parse_action(self.make_unpack_callback(LS)) + .set_name("LS") + ) + + def lm(self) -> pp.ParserElement: + """Create a parser for the LM command.""" + return ( + self._extended_command( + pp.Literal("LM") + + pp.one_of(["N", "XY", "X", "Y"]).set_results_name("mirroring") + ) + .set_parse_action(self.make_unpack_callback(LM)) + .set_name("LM") + ) + # ███ ███ █████ ███████ ██████ ███████ ███████ # ████ ████ ██ ██ ██ ██ ██ ██ ██ # ██ ████ ██ ██ ██ ██ ██ ██ █████ ███████ diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index c3095ef4..67e6e1d0 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -78,9 +78,9 @@ from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 -from pygerber.gerberx3.ast.nodes.load.LM import LM +from pygerber.gerberx3.ast.nodes.load.LM import LM, Mirroring from pygerber.gerberx3.ast.nodes.load.LN import LN -from pygerber.gerberx3.ast.nodes.load.LP import LP +from pygerber.gerberx3.ast.nodes.load.LP import LP, Polarity from pygerber.gerberx3.ast.nodes.load.LR import LR from pygerber.gerberx3.ast.nodes.load.LS import LS from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 @@ -262,11 +262,11 @@ G75: G75(source="", location=0), G90: G90(source="", location=0), G91: G91(source="", location=0), - LM: LM(source="", location=0), + LM: LM(source="", location=0, mirroring=Mirroring.XY), LN: LN(source="", location=0, name="name"), - LP: LP(source="", location=0), - LR: LR(source="", location=0), - LS: LS(source="", location=0), + LP: LP(source="", location=0, polarity=Polarity.Clear), + LR: LR(source="", location=0, rotation=0.1), + LS: LS(source="", location=0, scale=0.1), M00: M00(source="", location=0), M01: M01(source="", location=0), M02: M02(source="", location=0), From 9f28a3e2aeb72d54c2699476ff3e677f9f7fd7da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 18 Aug 2024 00:03:54 +0200 Subject: [PATCH 49/91] Add support for non-standalone G codes --- src/pygerber/gerberx3/ast/nodes/d_codes/D.py | 34 +++++ .../gerberx3/ast/nodes/d_codes/D01.py | 4 +- .../gerberx3/ast/nodes/d_codes/D02.py | 12 +- .../gerberx3/ast/nodes/d_codes/D03.py | 4 +- .../gerberx3/ast/nodes/d_codes/Dnn.py | 4 +- src/pygerber/gerberx3/ast/nodes/g_codes/G.py | 32 +++++ .../gerberx3/ast/nodes/g_codes/G01.py | 4 +- .../gerberx3/ast/nodes/g_codes/G02.py | 4 +- .../gerberx3/ast/nodes/g_codes/G03.py | 4 +- .../gerberx3/ast/nodes/g_codes/G04.py | 4 +- .../gerberx3/ast/nodes/g_codes/G36.py | 4 +- .../gerberx3/ast/nodes/g_codes/G37.py | 4 +- .../gerberx3/ast/nodes/g_codes/G54.py | 7 +- .../gerberx3/ast/nodes/g_codes/G55.py | 7 +- .../gerberx3/ast/nodes/g_codes/G70.py | 4 +- .../gerberx3/ast/nodes/g_codes/G71.py | 4 +- .../gerberx3/ast/nodes/g_codes/G74.py | 4 +- .../gerberx3/ast/nodes/g_codes/G75.py | 4 +- .../gerberx3/ast/nodes/g_codes/G90.py | 4 +- .../gerberx3/ast/nodes/g_codes/G91.py | 4 +- src/pygerber/gerberx3/ast/visitor.py | 18 ++- .../gerberx3/parser/pyparsing/grammar.py | 127 +++++++++--------- test/gerberx3/test_ast/test_ast_visitor.py | 4 +- 23 files changed, 183 insertions(+), 118 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/d_codes/D.py create mode 100644 src/pygerber/gerberx3/ast/nodes/g_codes/G.py diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D.py new file mode 100644 index 00000000..753fed70 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D.py @@ -0,0 +1,34 @@ +"""`pygerber.nodes.d_codes.DNN` module contains definition of `DNN` class.""" + +from __future__ import annotations + +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.base import Node + + +class D(Node): + """Base class for all Dxx commands.""" + + is_standalone: bool = Field(default=True) + """Flag indicating if the node is standalone, ie. it is not prefixed with + G code with no asterisk. + + This is necessary as some legacy Gerber files use redundant G codes to prefix + pretty much every D01/D02/D03 command. To make it possible to keep the original + layout of the file, we need to know if the D code was directly prefixed by + such redundant G code. + + Example: + + ```gerber + G70D02* + G54D16* + G01X5440Y5650D03* + G01X5440Y6900D03* + G01X6800Y2200D03* + G01X5550Y2200D03* + G01X17720Y6860D03* + G01X17720Y5610D03* + ``` + """ diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py index 6de7c76d..79657aba 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.d_codes.D import D from pygerber.gerberx3.ast.nodes.other.coordinate import ( CoordinateI, CoordinateJ, @@ -20,7 +20,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class D01(Node): +class D01(D): """Represents D01 Gerber command.""" x: Optional[CoordinateX] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py index 34c5007d..83e98e3f 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py @@ -2,9 +2,11 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, Optional -from pygerber.gerberx3.ast.nodes.base import Node +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.d_codes.D import D from pygerber.gerberx3.ast.nodes.other.coordinate import ( CoordinateX, CoordinateY, @@ -16,11 +18,11 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class D02(Node): +class D02(D): """Represents D02 Gerber command.""" - x: CoordinateX - y: CoordinateY + x: Optional[CoordinateX] = Field(default=None) + y: Optional[CoordinateY] = Field(default=None) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py index 2b6a621f..783b7cf8 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.d_codes.D import D from pygerber.gerberx3.ast.nodes.other.coordinate import ( CoordinateX, CoordinateY, @@ -18,7 +18,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class D03(Node): +class D03(D): """Represents D03 Gerber command.""" x: Optional[CoordinateX] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py index 24409833..8dbedb79 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.d_codes.D import D if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class Dnn(Node): +class Dnn(D): """Represents DNN Gerber command.""" value: str diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G.py new file mode 100644 index 00000000..2c802fa7 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G.py @@ -0,0 +1,32 @@ +"""`pygerber.nodes.g_codes.G01` module contains definition of `G01` class.""" + +from __future__ import annotations + +from pydantic import Field + +from pygerber.gerberx3.ast.nodes.base import Node + + +class G(Node): + """Base class for all Gxx nodes.""" + + is_standalone: bool = Field(default=True) + """Flag indicating if the node is standalone, ie. it should include * at the end. + + This is necessary as some legacy Gerber files use redundant G codes to prefix + pretty much every D01/D02/D03 command. To make it possible to keep the original + layout of the file, we need to know if the G code was directly followed by a D code. + + Example: + + ```gerber + G70D02* + G54D16* + G01X5440Y5650D03* + G01X5440Y6900D03* + G01X6800Y2200D03* + G01X5550Y2200D03* + G01X17720Y6860D03* + G01X17720Y5610D03* + ``` + """ diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py index ce77f2f3..2c2de900 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G01(Node): +class G01(G): """Represents G01 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py index ede70972..036454a2 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G02(Node): +class G02(G): """Represents G02 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py index 1f2fc629..c142ade4 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G03(Node): +class G03(G): """Represents G03 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py index 27169be7..6bc9fe22 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -14,7 +14,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G04(Node): +class G04(G): """Represents G04 Gerber command.""" string: Optional[str] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py index 30c72344..847757c4 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G36(Node): +class G36(G): """Represents G36 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py index 472a6b94..ecd559a9 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G37(Node): +class G37(G): """Represents G37 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py index 1efff402..e3f4f4a9 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py @@ -4,8 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -13,11 +12,9 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G54(Node): +class G54(G): """Represents G54 Gerber command.""" - dnn: Dnn - def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g54(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py index 0cdf5ab1..0020e45b 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py @@ -4,8 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -13,11 +12,9 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G55(Node): +class G55(G): """Represents G55 Gerber command.""" - flash: D03 - def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_g55(self) diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py index b08f05d1..83c342eb 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G70(Node): +class G70(G): """Represents G70 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py index 72adc8d4..594fbe0d 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G71(Node): +class G71(G): """Represents G71 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py index 1dc50cf1..a26fc376 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G74(Node): +class G74(G): """Represents G74 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py index 50ae8bdb..100f0415 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G75(Node): +class G75(G): """Represents G75 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py index 6c6b255c..aac6cc47 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G90(Node): +class G90(G): """Represents G90 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py index cd7911df..3f093a41 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py @@ -4,7 +4,7 @@ from typing import TYPE_CHECKING, Callable -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.g_codes.G import G if TYPE_CHECKING: from typing_extensions import Self @@ -12,7 +12,7 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class G91(Node): +class G91(G): """Represents G91 Gerber command.""" def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 239ea3ef..d2e63692 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -299,32 +299,32 @@ def on_to_csup(self, node: TO_CSup) -> None: def on_d01(self, node: D01) -> None: """Handle `D01` node.""" - if node.x: + if node.x is not None: node.x.visit(self) - if node.y: + if node.y is not None: node.y.visit(self) - if node.i: + if node.i is not None: node.i.visit(self) - if node.j: + if node.j is not None: node.j.visit(self) def on_d02(self, node: D02) -> None: """Handle `D02` node.""" - if node.x: + if node.x is not None: node.x.visit(self) - if node.y: + if node.y is not None: node.y.visit(self) def on_d03(self, node: D03) -> None: """Handle `D03` node.""" - if node.x: + if node.x is not None: node.x.visit(self) - if node.y: + if node.y is not None: node.y.visit(self) def on_dnn(self, node: Dnn) -> None: @@ -352,11 +352,9 @@ def on_g37(self, node: G37) -> None: def on_g54(self, node: G54) -> None: """Handle `G54` node.""" - node.dnn.visit(self) def on_g55(self, node: G55) -> None: """Handle `G55` node.""" - node.flash.visit(self) def on_g70(self, node: G70) -> None: """Handle `G70` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 0769d4d4..f65011f3 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -5,7 +5,7 @@ from __future__ import annotations from enum import IntFlag -from typing import Callable, List, Literal, Type, TypeVar, cast +from typing import Any, Callable, List, Literal, Type, TypeVar, cast import pyparsing as pp @@ -161,7 +161,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: self.g_codes(), self.load_commands(), self.m_codes(), - self.d_codes(), + self.d_codes(is_standalone=True), self.properties(), ] ) @@ -244,12 +244,16 @@ def aperture_identifier(self) -> pp.ParserElement: return pp.Regex(r"D[0]*[1-9][0-9]+").set_results_name("aperture_identifier") def make_unpack_callback( - self, node_type: Type[Node] + self, + node_type: Type[Node], + **kwargs: Any, ) -> Callable[[str, int, pp.ParseResults], Node]: """Create a callback for unpacking the results of the parser.""" def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: - return self.get_cls(node_type)(source=s, location=loc, **tokens.as_dict()) + return self.get_cls(node_type)( + source=s, location=loc, **tokens.as_dict(), **kwargs + ) return _ @@ -973,27 +977,32 @@ def _to(self) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██████ █████ ███████ ██████ ███████ ███████ - def d_codes(self) -> pp.ParserElement: - """Create a parser element capable of parsing D-codes.""" + def d_codes(self, *, is_standalone: bool) -> pp.ParserElement: + """Create a parser element capable of parsing D-codes. + + `is_standalone` parameter is used to determine if the D-code is standalone, ie. + not prefixed by a G-code with no asterisk at the end. See `D.is_standalone` or + `G.is_standalone` for more information. + """ return pp.MatchFirst( [ - self._dnn, - self._d01, - self._d02, - self._d03, + self._dnn(is_standalone=is_standalone), + self._d01(is_standalone=is_standalone), + self._d02(is_standalone=is_standalone), + self._d03(is_standalone=is_standalone), ] ) - @pp.cached_property - def _dnn(self) -> pp.ParserElement: + def _dnn(self, *, is_standalone: bool) -> pp.ParserElement: return ( self._command(self.aperture_identifier.set_results_name("value")) - .set_parse_action(self.make_unpack_callback(Dnn)) + .set_parse_action( + self.make_unpack_callback(Dnn, is_standalone=is_standalone) + ) .set_name("Dnn") ) - @pp.cached_property - def _d01(self) -> pp.ParserElement: + def _d01(self, *, is_standalone: bool) -> pp.ParserElement: return ( self._command( pp.Opt(self._coordinate_x.set_results_name("x")) @@ -1002,31 +1011,35 @@ def _d01(self) -> pp.ParserElement: + pp.Opt(self._coordinate_j.set_results_name("j")) + pp.Regex(r"D0*1") ) - .set_parse_action(self.make_unpack_callback(D01)) + .set_parse_action( + self.make_unpack_callback(D01, is_standalone=is_standalone) + ) .set_name("D01") ) - @pp.cached_property - def _d02(self) -> pp.ParserElement: + def _d02(self, *, is_standalone: bool) -> pp.ParserElement: return ( self._command( - self._coordinate_x.set_results_name("x") - + self._coordinate_y.set_results_name("y") + pp.Opt(self._coordinate_x.set_results_name("x")) + + pp.Opt(self._coordinate_y.set_results_name("y")) + pp.Regex(r"D0*2") ) - .set_parse_action(self.make_unpack_callback(D02)) + .set_parse_action( + self.make_unpack_callback(D02, is_standalone=is_standalone) + ) .set_name("D02") ) - @pp.cached_property - def _d03(self) -> pp.ParserElement: + def _d03(self, *, is_standalone: bool) -> pp.ParserElement: return ( self._command( pp.Opt(self._coordinate_x.set_results_name("x")) + pp.Opt(self._coordinate_y.set_results_name("y")) + pp.Regex(r"D0*3") ) - .set_parse_action(self.make_unpack_callback(D03)) + .set_parse_action( + self.make_unpack_callback(D03, is_standalone=is_standalone) + ) .set_name("D03") ) @@ -1047,54 +1060,46 @@ def g_codes(self) -> pp.ParserElement: else: g04_comment = g04_comment.set_parse_action(self.make_unpack_callback(G04)) - g54 = ( - (pp.Regex(r"G0*54") + self._dnn.set_results_name("dnn")) - .set_name("G54") - .set_parse_action(self.make_unpack_callback(G54)) - ) - g55 = ( - (pp.Regex(r"G0*55") + self._d03.set_results_name("flash")) - .set_name("G55") - .set_parse_action(self.make_unpack_callback(G55)) - ) + def _(cls: Type[Node]) -> pp.ParserElement: + code = int(cls.__qualname__.lstrip("G")) + return ( + self._command(pp.Regex(f"G0*{code}")) + .set_name(cls.__qualname__) + .set_parse_action(self.make_unpack_callback(cls, is_standalone=True)) + ) | ( # We have to account for legacy cases like `G70D02*`, see + # `G.is_standalone` docstring for more information. + (pp.Regex(f"G0*{code}")) + .set_name(cls.__qualname__) + .set_parse_action(self.make_unpack_callback(cls, is_standalone=False)) + + self.d_codes(is_standalone=False) + ) + return pp.MatchFirst( [ g04_comment, - g54, - g55, *( - self.g( - value, - self.get_cls(cls), # type: ignore[arg-type, type-abstract] - ) - for (value, cls) in reversed( + _(cls) + for cls in reversed( ( - (1, G01), - (2, G02), - (3, G03), - (36, G36), - (37, G37), - (54, G54), - (70, G70), - (71, G71), - (74, G74), - (75, G75), - (90, G90), - (91, G91), + G01, + G02, + G03, + G36, + G37, + G54, + G55, + G70, + G71, + G74, + G75, + G90, + G91, ) ) ), ] ) - def g(self, value: int, cls: Type[Node]) -> pp.ParserElement: - """Create a parser element capable of parsing particular G-code.""" - return ( - self._command(pp.Regex(r"G0*" + str(value))) - .set_name(f"G{value}") - .set_parse_action(self.make_unpack_callback(cls)) - ) - # ██ ██████ █████ ██████ █████ ███████ ███ ███ ███ ███ █████ ███ ██ ██████ ███████ # noqa: E501 # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ ████ ████ ████ ██ ██ ████ ██ ██ ██ ██ # noqa: E501 # ██ ██ ██ ███████ ██ ██ ██ ██ ██ ██ ████ ██ ██ ████ ██ ███████ ██ ██ ██ ██ ██ ███████ # noqa: E501 diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 67e6e1d0..f88ccecd 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -254,8 +254,8 @@ G04: G04(source="", location=0, string="comment"), G36: G36(source="", location=0), G37: G37(source="", location=0), - G54: G54(source="", location=0, dnn=Dnn(source="", location=0, value="D11")), - G55: G55(source="", location=0, flash=D03(source="", location=0)), + G54: G54(source="", location=0), + G55: G55(source="", location=0), G70: G70(source="", location=0), G71: G71(source="", location=0), G74: G74(source="", location=0), From 8b8910a5c9d2192b61015793be2859e1d6124cf6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 18 Aug 2024 01:01:36 +0200 Subject: [PATCH 50/91] Add formatter options for non standalone codes --- src/pygerber/gerberx3/ast/nodes/__init__.py | 218 +++++++ src/pygerber/gerberx3/ast/nodes/load/LR.py | 3 +- src/pygerber/gerberx3/ast/nodes/load/LS.py | 3 +- .../gerberx3/ast/nodes/properties/SF.py | 5 +- src/pygerber/gerberx3/formatter.py | 556 +++++++++--------- .../gerberx3/parser/pyparsing/grammar.py | 43 +- test/gerberx3/test_formatter.py | 8 +- 7 files changed, 546 insertions(+), 290 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/__init__.py b/src/pygerber/gerberx3/ast/nodes/__init__.py index b81b39e6..8492268c 100644 --- a/src/pygerber/gerberx3/ast/nodes/__init__.py +++ b/src/pygerber/gerberx3/ast/nodes/__init__.py @@ -1,3 +1,221 @@ """`pygerber.gerberx3.ast.nodes` package contains all the node container classes generated by the Gerber X3 parser. """ + +from __future__ import annotations + +from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose +from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen +from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC +from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro +from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO +from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP +from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR +from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose +from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen +from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose +from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen +from pygerber.gerberx3.ast.nodes.attribute.TA import ( + TA_AperFunction, + TA_DrillTolerance, + TA_FlashText, + TA_UserName, +) +from pygerber.gerberx3.ast.nodes.attribute.TD import TD +from pygerber.gerberx3.ast.nodes.attribute.TF import ( + TF_MD5, + TF_CreationDate, + TF_FileFunction, + TF_FilePolarity, + TF_GenerationSoftware, + TF_Part, + TF_ProjectId, + TF_SameCoordinates, + TF_UserName, +) +from pygerber.gerberx3.ast.nodes.attribute.TO import ( + TO_C, + TO_CMNP, + TO_N, + TO_P, + TO_CFtp, + TO_CHgt, + TO_CLbD, + TO_CLbN, + TO_CMfr, + TO_CMnt, + TO_CPgD, + TO_CPgN, + TO_CRot, + TO_CSup, + TO_CVal, + TO_UserName, +) +from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 +from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 +from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 +from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn +from pygerber.gerberx3.ast.nodes.file import File +from pygerber.gerberx3.ast.nodes.g_codes.G import G +from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 +from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 +from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 +from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 +from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 +from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 +from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 +from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 +from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 +from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 +from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 +from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 +from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 +from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 +from pygerber.gerberx3.ast.nodes.load.LM import LM +from pygerber.gerberx3.ast.nodes.load.LN import LN +from pygerber.gerberx3.ast.nodes.load.LP import LP +from pygerber.gerberx3.ast.nodes.load.LR import LR +from pygerber.gerberx3.ast.nodes.load.LS import LS +from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 +from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 +from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 +from pygerber.gerberx3.ast.nodes.math.assignment import Assignment +from pygerber.gerberx3.ast.nodes.math.constant import Constant +from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add +from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div +from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul +from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub +from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg +from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos +from pygerber.gerberx3.ast.nodes.math.point import Point +from pygerber.gerberx3.ast.nodes.math.variable import Variable +from pygerber.gerberx3.ast.nodes.other.coordinate import ( + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, +) +from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 +from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 +from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 +from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 +from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 +from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 +from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 +from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 +from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 +from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 +from pygerber.gerberx3.ast.nodes.properties.AS import AS +from pygerber.gerberx3.ast.nodes.properties.FS import FS +from pygerber.gerberx3.ast.nodes.properties.IN import IN +from pygerber.gerberx3.ast.nodes.properties.IP import IP +from pygerber.gerberx3.ast.nodes.properties.IR import IR +from pygerber.gerberx3.ast.nodes.properties.MI import MI +from pygerber.gerberx3.ast.nodes.properties.MO import MO +from pygerber.gerberx3.ast.nodes.properties.OF import OF +from pygerber.gerberx3.ast.nodes.properties.SF import SF + +__all__ = [ + "ABclose", + "ABopen", + "ADC", + "ADmacro", + "ADO", + "ADP", + "ADR", + "AMclose", + "AMopen", + "SRclose", + "SRopen", + "TA_AperFunction", + "TA_DrillTolerance", + "TA_FlashText", + "TA_UserName", + "TD", + "TF_MD5", + "TF_CreationDate", + "TF_FileFunction", + "TF_FilePolarity", + "TF_GenerationSoftware", + "TF_Part", + "TF_ProjectId", + "TF_SameCoordinates", + "TF_UserName", + "TO_C", + "TO_CMNP", + "TO_N", + "TO_P", + "TO_CFtp", + "TO_CHgt", + "TO_CLbD", + "TO_CLbN", + "TO_CMfr", + "TO_CMnt", + "TO_CPgD", + "TO_CPgN", + "TO_CRot", + "TO_CSup", + "TO_CVal", + "TO_UserName", + "D01", + "D02", + "D03", + "Dnn", + "File", + "G", + "G01", + "G02", + "G03", + "G04", + "G36", + "G37", + "G54", + "G55", + "G70", + "G71", + "G74", + "G75", + "G90", + "G91", + "LM", + "LN", + "LP", + "LR", + "LS", + "M00", + "M01", + "M02", + "Assignment", + "Constant", + "Add", + "Div", + "Mul", + "Sub", + "Neg", + "Pos", + "Point", + "Variable", + "CoordinateI", + "CoordinateJ", + "CoordinateX", + "CoordinateY", + "Code0", + "Code1", + "Code2", + "Code4", + "Code5", + "Code6", + "Code7", + "Code20", + "Code21", + "Code22", + "AS", + "FS", + "IN", + "IP", + "IR", + "MI", + "MO", + "OF", + "SF", +] diff --git a/src/pygerber/gerberx3/ast/nodes/load/LR.py b/src/pygerber/gerberx3/ast/nodes/load/LR.py index 084a2e2a..ec6ee728 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LR.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LR.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -15,7 +16,7 @@ class LR(Node): """Represents LR Gerber extended command.""" - rotation: float + rotation: Double def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/load/LS.py b/src/pygerber/gerberx3/ast/nodes/load/LS.py index 30a79c8f..67fcb2ad 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LS.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LS.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -15,7 +16,7 @@ class LS(Node): """Represents LS Gerber extended command.""" - scale: float + scale: Double def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/properties/SF.py b/src/pygerber/gerberx3/ast/nodes/properties/SF.py index 34618ea9..17314be9 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/SF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/SF.py @@ -7,6 +7,7 @@ from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: from typing_extensions import Self @@ -17,8 +18,8 @@ class SF(Node): """Represents SF Gerber extended command.""" - a_scale: float = Field(default=1.0) - b_scale: float = Field(default=1.0) + a_scale: Double = Field(default=1.0) + b_scale: Double = Field(default=1.0) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index dec93dea..d2cba129 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -13,128 +13,123 @@ Generator, Literal, Optional, + Type, TypeVar, ) from pyparsing import cached_property from typing_extensions import ParamSpec +from pygerber.gerberx3.ast.nodes import ( + ADC, + ADO, + ADP, + ADR, + AS, + D01, + D02, + D03, + FS, + G01, + G02, + G03, + G04, + G36, + G37, + G54, + G55, + G70, + G71, + G74, + G75, + G90, + G91, + IN, + IP, + IR, + LM, + LN, + LP, + LR, + LS, + M00, + M01, + M02, + MI, + MO, + OF, + SF, + TD, + TF_MD5, + TO_C, + TO_CMNP, + TO_N, + TO_P, + ABclose, + ABopen, + Add, + ADmacro, + AMclose, + AMopen, + Assignment, + Code0, + Code1, + Code2, + Code4, + Code5, + Code6, + Code7, + Code20, + Code21, + Code22, + Constant, + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, + Div, + Dnn, + File, + G, + Mul, + Neg, + Point, + Pos, + SRclose, + SRopen, + Sub, + TA_AperFunction, + TA_DrillTolerance, + TA_FlashText, + TA_UserName, + TF_CreationDate, + TF_FileFunction, + TF_FilePolarity, + TF_GenerationSoftware, + TF_Part, + TF_ProjectId, + TF_SameCoordinates, + TF_UserName, + TO_CFtp, + TO_CHgt, + TO_CLbD, + TO_CLbN, + TO_CMfr, + TO_CMnt, + TO_CPgD, + TO_CPgN, + TO_CRot, + TO_CSup, + TO_CVal, + TO_UserName, + Variable, +) from pygerber.gerberx3.ast.nodes.types import Double from pygerber.gerberx3.ast.visitor import AstVisitor if TYPE_CHECKING: from io import StringIO - from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose - from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen - from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC - from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro - from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO - from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP - from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR - from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose - from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen - from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose - from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen - from pygerber.gerberx3.ast.nodes.attribute.TA import ( - TA_AperFunction, - TA_DrillTolerance, - TA_FlashText, - TA_UserName, - ) - from pygerber.gerberx3.ast.nodes.attribute.TD import TD - from pygerber.gerberx3.ast.nodes.attribute.TF import ( - TF_MD5, - TF_CreationDate, - TF_FileFunction, - TF_FilePolarity, - TF_GenerationSoftware, - TF_Part, - TF_ProjectId, - TF_SameCoordinates, - TF_UserName, - ) - from pygerber.gerberx3.ast.nodes.attribute.TO import ( - TO_C, - TO_CMNP, - TO_N, - TO_P, - TO_CFtp, - TO_CHgt, - TO_CLbD, - TO_CLbN, - TO_CMfr, - TO_CMnt, - TO_CPgD, - TO_CPgN, - TO_CRot, - TO_CSup, - TO_CVal, - TO_UserName, - ) - from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 - from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 - from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 - from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn - from pygerber.gerberx3.ast.nodes.file import File - from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 - from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 - from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 - from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 - from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 - from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 - from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 - from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 - from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 - from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 - from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 - from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 - from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 - from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 - from pygerber.gerberx3.ast.nodes.load.LM import LM - from pygerber.gerberx3.ast.nodes.load.LN import LN - from pygerber.gerberx3.ast.nodes.load.LP import LP - from pygerber.gerberx3.ast.nodes.load.LR import LR - from pygerber.gerberx3.ast.nodes.load.LS import LS - from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 - from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 - from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 - from pygerber.gerberx3.ast.nodes.math.assignment import Assignment - from pygerber.gerberx3.ast.nodes.math.constant import Constant - from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add - from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div - from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul - from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub - from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg - from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos - from pygerber.gerberx3.ast.nodes.math.point import Point - from pygerber.gerberx3.ast.nodes.math.variable import Variable - from pygerber.gerberx3.ast.nodes.other.coordinate import ( - CoordinateI, - CoordinateJ, - CoordinateX, - CoordinateY, - ) - from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 - from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 - from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 - from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 - from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 - from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 - from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 - from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 - from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 - from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 - from pygerber.gerberx3.ast.nodes.properties.AS import AS - from pygerber.gerberx3.ast.nodes.properties.FS import FS - from pygerber.gerberx3.ast.nodes.properties.IN import IN - from pygerber.gerberx3.ast.nodes.properties.IP import IP - from pygerber.gerberx3.ast.nodes.properties.IR import IR - from pygerber.gerberx3.ast.nodes.properties.MI import MI - from pygerber.gerberx3.ast.nodes.properties.MO import MO - from pygerber.gerberx3.ast.nodes.properties.OF import OF - from pygerber.gerberx3.ast.nodes.properties.SF import SF - class FormatterError(Exception): """Formatter error.""" @@ -170,6 +165,10 @@ def __init__( # noqa: PLR0913 d02_indent: int | str = 0, d03_indent: int | str = 0, line_end: Literal["\n", "\r\n"] = "\n", + empty_line_before_polarity_switch: bool = False, + keep_non_standalone_codes: bool = True, + remove_g54: bool = False, + remove_g55: bool = False, strip_whitespace: bool = False, ) -> None: r"""Initialize Formatter instance. @@ -236,11 +235,28 @@ def __init__( # noqa: PLR0913 Custom indentation of D02 command, by default 0 d03_indent : str | int, optional Custom indentation of D03 command, by default 0 - strip_whitespace : bool, optional - Remove all semantically insignificant whitespace, by default False line_end : Literal["\n", "\r\n"], optional Line ending character, Unix or Windows style, by default "\n" (Unix style) If `strip_whitespace` is enabled, no line end will be used. + empty_line_before_polarity_switch : bool, optional + Add empty line before polarity switch, by default False + This enhances visibility of sequences of commands with different + polarities. + keep_non_standalone_codes: bool, optional + Keep non-standalone codes in the output, by default True + If this option is disabled, codes that are not standalone, ie. `G70D02*` + will be divided into two separate commands, `G70*` and `D02*`, otherwise + they will be kept as is. + remove_g54: bool, optional + Remove G54 code from output, by default False + G54 code has no effect on the output, it was used in legacy files to + prefix select aperture command. + remove_g55: bool, optional + Remove G55 code from output, by default False + G55 code has no effect on the output, it was used in legacy files to + prefix flash command. + strip_whitespace : bool, optional + Remove all semantically insignificant whitespace, by default False """ super().__init__() @@ -281,13 +297,18 @@ def __init__( # noqa: PLR0913 d03_indent = indent_character * d03_indent self.d03_indent = d03_indent - self.strip_whitespace = strip_whitespace - self.lf = line_end + self.empty_line_before_polarity_switch = ( + self.lf if empty_line_before_polarity_switch else "" + ) + self.keep_non_standalone_codes = keep_non_standalone_codes + self.remove_g54 = remove_g54 + self.remove_g55 = remove_g55 + self.strip_whitespace = strip_whitespace if self.strip_whitespace: - self.lf = "" - self.indent_character = "" + self.lf = "" # type: ignore[assignment] + self.indent_character = "" # type: ignore[assignment] self.macro_body_indent = "" self.macro_param_indent = "" self.block_aperture_body_indent = "" @@ -295,6 +316,7 @@ def __init__( # noqa: PLR0913 self.d01_indent = "" self.d02_indent = "" self.d03_indent = "" + self.empty_line_before_polarity_switch = "" self._output: Optional[StringIO] = None self._base_indent: str = "" @@ -324,50 +346,31 @@ def _fmt_double(self, value: Double) -> str: return double @staticmethod - def _insert_base_indent( - function: Callable[ParamT, ReturnT] + def _decorator_insert_base_indent( + function: Callable[ParamT, ReturnT], ) -> Callable[ParamT, ReturnT]: - @wraps(function) def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: self = args[0] assert isinstance(self, Formatter) - self._write(self._base_indent) + self._insert_base_indent() return function(*args, **kwargs) return _ - @staticmethod - def _insert_extra_indent( - variable_name: str, - ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: - - def _decorator( - function: Callable[ParamT, ReturnT] - ) -> Callable[ParamT, ReturnT]: - - @wraps(function) - def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: - self = args[0] - assert isinstance(self, Formatter) - - self._write(getattr(self, variable_name)) - - return function(*args, **kwargs) - - return _ + def _insert_base_indent(self) -> None: + self._write(self._base_indent) - return _decorator + def _insert_extra_indent(self, value: str) -> None: + self._write(value) @staticmethod def _increase_base_indent( variable_name: str, ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: - def _decorator( - function: Callable[ParamT, ReturnT] + function: Callable[ParamT, ReturnT], ) -> Callable[ParamT, ReturnT]: - @wraps(function) def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: self = args[0] @@ -385,11 +388,9 @@ def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: def _decrease_base_indent( variable_name: str, ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: - def _decorator( - function: Callable[ParamT, ReturnT] + function: Callable[ParamT, ReturnT], ) -> Callable[ParamT, ReturnT]: - @wraps(function) def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: self = args[0] @@ -426,20 +427,20 @@ def _write(self, value: str) -> None: self.output.write(value) @_decrease_base_indent("block_aperture_body_indent") - @_insert_base_indent + @_decorator_insert_base_indent def on_ab_close(self, node: ABclose) -> None: # noqa: ARG002 """Handle `ABclose` node.""" with self._extended_command("AB"): pass - @_insert_base_indent + @_decorator_insert_base_indent @_increase_base_indent("block_aperture_body_indent") def on_ab_open(self, node: ABopen) -> None: """Handle `ABopen` node.""" with self._extended_command("AB"): self._write(node.aperture_identifier) - @_insert_base_indent + @_decorator_insert_base_indent def on_adc(self, node: ADC) -> None: """Handle `AD` circle node.""" with self._extended_command(f"AD{node.aperture_identifier}C,"): @@ -448,7 +449,7 @@ def on_adc(self, node: ADC) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") - @_insert_base_indent + @_decorator_insert_base_indent def on_adr(self, node: ADR) -> None: """Handle `AD` rectangle node.""" with self._extended_command(f"AD{node.aperture_identifier}R,"): @@ -458,7 +459,7 @@ def on_adr(self, node: ADR) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") - @_insert_base_indent + @_decorator_insert_base_indent def on_ado(self, node: ADO) -> None: """Handle `AD` obround node.""" with self._extended_command(f"AD{node.aperture_identifier}O,"): @@ -468,7 +469,7 @@ def on_ado(self, node: ADO) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") - @_insert_base_indent + @_decorator_insert_base_indent def on_adp(self, node: ADP) -> None: """Handle `AD` polygon node.""" with self._extended_command(f"AD{node.aperture_identifier}P,"): @@ -481,7 +482,7 @@ def on_adp(self, node: ADP) -> None: if node.hole_diameter is not None: self._write(f"X{self._fmt_double(node.hole_diameter)}") - @_insert_base_indent + @_decorator_insert_base_indent def on_ad_macro(self, node: ADmacro) -> None: """Handle `AD` macro node.""" with self._extended_command(f"AD{node.aperture_identifier}{node.name}"): @@ -491,7 +492,7 @@ def on_ad_macro(self, node: ADmacro) -> None: for param in rest: self._write(f"X{param}") - @_insert_base_indent + @_decorator_insert_base_indent def on_am_close(self, node: AMclose) -> None: """Handle `AMclose` node.""" super().on_am_close(node) @@ -499,20 +500,20 @@ def on_am_close(self, node: AMclose) -> None: self._write(f"{self.lf}") self._write(f"%{self.lf}") - @_insert_base_indent + @_decorator_insert_base_indent def on_am_open(self, node: AMopen) -> None: """Handle `AMopen` node.""" super().on_am_open(node) self._write(f"%AM{node.name}*") @_decrease_base_indent("step_and_repeat_body_indent") - @_insert_base_indent + @_decorator_insert_base_indent def on_sr_close(self, node: SRclose) -> None: # noqa: ARG002 """Handle `SRclose` node.""" with self._extended_command("SR"): pass - @_insert_base_indent + @_decorator_insert_base_indent @_increase_base_indent("step_and_repeat_body_indent") def on_sr_open(self, node: SRopen) -> None: """Handle `SRopen` node.""" @@ -531,7 +532,7 @@ def on_sr_open(self, node: SRopen) -> None: # Attribute - @_insert_base_indent + @_decorator_insert_base_indent def on_ta_user_name(self, node: TA_UserName) -> None: """Handle `TA_UserName` node.""" with self._extended_command(f"TA{node.user_name}"): @@ -539,7 +540,7 @@ def on_ta_user_name(self, node: TA_UserName) -> None: self._write(",") self._write(field) - @_insert_base_indent + @_decorator_insert_base_indent def on_ta_aper_function(self, node: TA_AperFunction) -> None: """Handle `TA_AperFunction` node.""" with self._extended_command("TA.AperFunction"): @@ -551,7 +552,7 @@ def on_ta_aper_function(self, node: TA_AperFunction) -> None: self._write(",") self._write(field) - @_insert_base_indent + @_decorator_insert_base_indent def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: """Handle `TA_DrillTolerance` node.""" with self._extended_command("TA.DrillTolerance"): @@ -563,7 +564,7 @@ def on_ta_drill_tolerance(self, node: TA_DrillTolerance) -> None: self._write(",") self._write(self._fmt_double(node.minus_tolerance)) - @_insert_base_indent + @_decorator_insert_base_indent def on_ta_flash_text(self, node: TA_FlashText) -> None: """Handle `TA_FlashText` node.""" with self._extended_command("TA.FlashText"): @@ -601,14 +602,14 @@ def on_ta_flash_text(self, node: TA_FlashText) -> None: self._write(",") self._write(comment) - @_insert_base_indent + @_decorator_insert_base_indent def on_td(self, node: TD) -> None: """Handle `TD` node.""" with self._extended_command("TD"): if node.name is not None: self._write(node.name) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_user_name(self, node: TF_UserName) -> None: """Handle `TF_UserName` node.""" with self._extended_command(f"TF{node.user_name}"): @@ -616,7 +617,7 @@ def on_tf_user_name(self, node: TF_UserName) -> None: self._write(",") self._write(field) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_part(self, node: TF_Part) -> None: """Handle `TF_Part` node.""" with self._extended_command("TF.Part,"): @@ -626,7 +627,7 @@ def on_tf_part(self, node: TF_Part) -> None: self._write(",") self._write(field) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_file_function(self, node: TF_FileFunction) -> None: """Handle `TF_FileFunction` node.""" with self._extended_command("TF.FileFunction,"): @@ -636,13 +637,13 @@ def on_tf_file_function(self, node: TF_FileFunction) -> None: self._write(",") self._write(field) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_file_polarity(self, node: TF_FilePolarity) -> None: """Handle `TF_FilePolarity` node.""" with self._extended_command("TF.FilePolarity,"): self._write(node.polarity) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: """Handle `TF_SameCoordinates` node.""" with self._extended_command("TF.SameCoordinates"): @@ -650,7 +651,7 @@ def on_tf_same_coordinates(self, node: TF_SameCoordinates) -> None: self._write(",") self._write(node.identifier) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_creation_date(self, node: TF_CreationDate) -> None: """Handle `TF_CreationDate` node.""" with self._extended_command("TF.CreationDate"): @@ -658,7 +659,7 @@ def on_tf_creation_date(self, node: TF_CreationDate) -> None: self._write(",") self._write(node.creation_date.isoformat()) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: """Handle `TF_GenerationSoftware` node.""" with self._extended_command("TF.GenerationSoftware"): @@ -674,7 +675,7 @@ def on_tf_generation_software(self, node: TF_GenerationSoftware) -> None: if node.version is not None: self._write(node.version) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_project_id(self, node: TF_ProjectId) -> None: """Handle `TF_ProjectId` node.""" with self._extended_command("TF.ProjectId"): @@ -690,14 +691,14 @@ def on_tf_project_id(self, node: TF_ProjectId) -> None: if node.revision is not None: self._write(node.revision) - @_insert_base_indent + @_decorator_insert_base_indent def on_tf_md5(self, node: TF_MD5) -> None: """Handle `TF_MD5` node.""" with self._extended_command("TF.MD5"): self._write(",") self._write(node.md5) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_user_name(self, node: TO_UserName) -> None: """Handle `TO_UserName` node.""" with self._extended_command(f"TO{node.user_name}"): @@ -705,7 +706,7 @@ def on_to_user_name(self, node: TO_UserName) -> None: self._write(",") self._write(field) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_n(self, node: TO_N) -> None: """Handle `TO_N` node.""" with self._extended_command("TO.N"): @@ -713,7 +714,7 @@ def on_to_n(self, node: TO_N) -> None: self._write(",") self._write(field) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_p(self, node: TO_P) -> None: """Handle `TO_P` node`.""" with self._extended_command("TO.P"): @@ -725,91 +726,91 @@ def on_to_p(self, node: TO_P) -> None: self._write(",") self._write(node.function) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_c(self, node: TO_C) -> None: """Handle `TO_C` node.""" with self._extended_command("TO.C"): self._write(",") self._write(node.refdes) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_crot(self, node: TO_CRot) -> None: """Handle `TO_CRot` node.""" with self._extended_command("TO.CRot"): self._write(",") self._write(self._fmt_double(node.angle)) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_cmfr(self, node: TO_CMfr) -> None: """Handle `TO_CMfr` node.""" with self._extended_command("TO.CMfr"): self._write(",") self._write(node.manufacturer) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_cmnp(self, node: TO_CMNP) -> None: """Handle `TO_CMNP` node.""" with self._extended_command("TO.CMPN"): self._write(",") self._write(node.part_number) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_cval(self, node: TO_CVal) -> None: """Handle `TO_CVal` node.""" with self._extended_command("TO.CVal"): self._write(",") self._write(node.value) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_cmnt(self, node: TO_CMnt) -> None: """Handle `TO_CVal` node.""" with self._extended_command("TO.CMnt"): self._write(",") self._write(node.mount.value) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_cftp(self, node: TO_CFtp) -> None: """Handle `TO_Cftp` node.""" with self._extended_command("TO.CFtp"): self._write(",") self._write(node.footprint) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_cpgn(self, node: TO_CPgN) -> None: """Handle `TO_CPgN` node.""" with self._extended_command("TO.CPgN"): self._write(",") self._write(node.name) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_cpgd(self, node: TO_CPgD) -> None: """Handle `TO_CPgD` node.""" with self._extended_command("TO.CPgD"): self._write(",") self._write(node.description) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_chgt(self, node: TO_CHgt) -> None: """Handle `TO_CHgt` node.""" with self._extended_command("TO.CHgt"): self._write(",") self._write(self._fmt_double(node.height)) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_clbn(self, node: TO_CLbN) -> None: """Handle `TO_CLbN` node.""" with self._extended_command("TO.CLbn"): self._write(",") self._write(node.name) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_clbd(self, node: TO_CLbD) -> None: """Handle `TO_CLbD` node.""" with self._extended_command("TO.CLbD"): self._write(",") self._write(node.description) - @_insert_base_indent + @_decorator_insert_base_indent def on_to_csup(self, node: TO_CSup) -> None: """Handle `TO_CSup` node.""" with self._extended_command("TO.CSup"): @@ -823,166 +824,171 @@ def on_to_csup(self, node: TO_CSup) -> None: # D codes - @_insert_base_indent - @_insert_extra_indent("d01_indent") def on_d01(self, node: D01) -> None: """Handle `D01` node.""" + if node.is_standalone or not self.keep_non_standalone_codes: + self._insert_base_indent() + self._insert_extra_indent(self.d01_indent) + super().on_d01(node) with self._command("D01"): pass - @_insert_base_indent - @_insert_extra_indent("d02_indent") def on_d02(self, node: D02) -> None: """Handle `D02` node.""" + if node.is_standalone or not self.keep_non_standalone_codes: + self._insert_base_indent() + self._insert_extra_indent(self.d02_indent) + super().on_d02(node) with self._command("D02"): pass - @_insert_base_indent - @_insert_extra_indent("d03_indent") def on_d03(self, node: D03) -> None: """Handle `D03` node.""" + if node.is_standalone or not self.keep_non_standalone_codes: + self._insert_base_indent() + self._insert_extra_indent(self.d03_indent) + super().on_d03(node) with self._command("D03"): pass - @_insert_base_indent def on_dnn(self, node: Dnn) -> None: """Handle `Dnn` node.""" + if node.is_standalone or not self.keep_non_standalone_codes: + self._insert_base_indent() + with self._command(node.value): pass # G codes - @_insert_base_indent - def on_g01(self, node: G01) -> None: # noqa: ARG002 + def _handle_g(self, node: G, cls: Type[G]) -> None: + if node.is_standalone or not self.keep_non_standalone_codes: + with self._command(cls.__qualname__): + pass + return + + self._write(cls.__qualname__) + + @_decorator_insert_base_indent + def on_g01(self, node: G01) -> None: """Handle `G01` node.""" - with self._command("G01"): - pass + self._handle_g(node, G01) - @_insert_base_indent - def on_g02(self, node: G02) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g02(self, node: G02) -> None: """Handle `G02` node.""" - with self._command("G02"): - pass + self._handle_g(node, G02) - @_insert_base_indent - def on_g03(self, node: G03) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g03(self, node: G03) -> None: """Handle `G03` node.""" - with self._command("G03"): - pass + self._handle_g(node, G03) - @_insert_base_indent + @_decorator_insert_base_indent def on_g04(self, node: G04) -> None: """Handle `G04` node.""" with self._command(f"G04{node.string or ''}"): pass - @_insert_base_indent - def on_g36(self, node: G36) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g36(self, node: G36) -> None: """Handle `G36` node.""" - with self._command("G36"): - pass + self._handle_g(node, G36) - @_insert_base_indent - def on_g37(self, node: G37) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g37(self, node: G37) -> None: """Handle `G37` node.""" - with self._command("G37"): - pass + self._handle_g(node, G37) - @_insert_base_indent + @_decorator_insert_base_indent def on_g54(self, node: G54) -> None: """Handle `G54` node.""" - with self._command("G54", asterisk=False, lf=False): - pass - super().on_g54(node) + if self.remove_g54: + return + self._handle_g(node, G54) - @_insert_base_indent + @_decorator_insert_base_indent def on_g55(self, node: G55) -> None: """Handle `G55` node.""" - with self._command("G55", asterisk=False, lf=False): - pass - super().on_g55(node) + if self.remove_g55: + return + self._handle_g(node, G55) - @_insert_base_indent - def on_g70(self, node: G70) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g70(self, node: G70) -> None: """Handle `G70` node.""" - with self._command("G70"): - pass + self._handle_g(node, G70) - @_insert_base_indent - def on_g71(self, node: G71) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g71(self, node: G71) -> None: """Handle `G71` node.""" - with self._command("G71"): - pass + self._handle_g(node, G71) - @_insert_base_indent - def on_g74(self, node: G74) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g74(self, node: G74) -> None: """Handle `G74` node.""" - with self._command("G74"): - pass + self._handle_g(node, G74) - @_insert_base_indent - def on_g75(self, node: G75) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g75(self, node: G75) -> None: """Handle `G75` node.""" - with self._command("G75"): - pass + self._handle_g(node, G75) - @_insert_base_indent - def on_g90(self, node: G90) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g90(self, node: G90) -> None: """Handle `G90` node.""" - with self._command("G90"): - pass + self._handle_g(node, G90) - @_insert_base_indent - def on_g91(self, node: G91) -> None: # noqa: ARG002 + @_decorator_insert_base_indent + def on_g91(self, node: G91) -> None: """Handle `G91` node.""" - with self._command("G91"): - pass + self._handle_g(node, G91) # Load - @_insert_base_indent + @_decorator_insert_base_indent def on_lm(self, node: LM) -> None: """Handle `LM` node.""" super().on_lm(node) - @_insert_base_indent + @_decorator_insert_base_indent def on_ln(self, node: LN) -> None: """Handle `LN` node.""" super().on_ln(node) - @_insert_base_indent + @_decorator_insert_base_indent def on_lp(self, node: LP) -> None: """Handle `LP` node.""" super().on_lp(node) - @_insert_base_indent + @_decorator_insert_base_indent def on_lr(self, node: LR) -> None: """Handle `LR` node.""" super().on_lr(node) - @_insert_base_indent + @_decorator_insert_base_indent def on_ls(self, node: LS) -> None: """Handle `LS` node.""" super().on_ls(node) # M Codes - @_insert_base_indent + @_decorator_insert_base_indent def on_m00(self, node: M00) -> None: # noqa: ARG002 """Handle `M00` node.""" with self._command("M00"): pass - @_insert_base_indent + @_decorator_insert_base_indent def on_m01(self, node: M01) -> None: # noqa: ARG002 """Handle `M01` node.""" with self._command("M01"): pass - @_insert_base_indent + @_decorator_insert_base_indent def on_m02(self, node: M02) -> None: # noqa: ARG002 """Handle `M02` node.""" with self._command("M02"): @@ -1039,7 +1045,7 @@ def on_pos(self, node: Pos) -> None: self._write("+") node.operand.visit(self) - @_insert_base_indent + @_decorator_insert_base_indent def on_assignment(self, node: Assignment) -> None: """Handle `Assignment` node.""" self._write(self._macro_primitive_lf) @@ -1082,12 +1088,12 @@ def on_coordinate_j(self, node: CoordinateJ) -> None: # Primitives - @_insert_base_indent + @_decorator_insert_base_indent def on_code_0(self, node: Code0) -> None: """Handle `Code0` node.""" self._write(f"{self._macro_primitive_lf}0{node.string}*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_1(self, node: Code1) -> None: """Handle `Code1` node.""" self._write(f"{self._macro_primitive_lf}1,{self._macro_param_lf}") @@ -1131,7 +1137,7 @@ def _macro_param_lf(self) -> str: msg = f"Unsupported macro split mode: {self.macro_split_mode}" raise NotImplementedError(msg) - @_insert_base_indent + @_decorator_insert_base_indent def on_code_2(self, node: Code2) -> None: """Handle `Code2` node.""" self._write(f"{self._macro_primitive_lf}2,{self._macro_param_lf}") @@ -1150,7 +1156,7 @@ def on_code_2(self, node: Code2) -> None: node.rotation.visit(self) self._write("*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_4(self, node: Code4) -> None: """Handle `Code4` node.""" self._write(f"{self._macro_primitive_lf}4,{self._macro_param_lf}") @@ -1168,7 +1174,7 @@ def on_code_4(self, node: Code4) -> None: node.rotation.visit(self) self._write("*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_5(self, node: Code5) -> None: """Handle `Code5` node.""" self._write(f"{self._macro_primitive_lf}5,{self._macro_param_lf}") @@ -1185,7 +1191,7 @@ def on_code_5(self, node: Code5) -> None: node.rotation.visit(self) self._write("*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_6(self, node: Code6) -> None: """Handle `Code6` node.""" self._write(f"{self._macro_primitive_lf}6,{self._macro_param_lf}") @@ -1208,7 +1214,7 @@ def on_code_6(self, node: Code6) -> None: node.rotation.visit(self) self._write("*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_7(self, node: Code7) -> None: """Handle `Code7` node.""" self._write(f"{self._macro_primitive_lf}7,{self._macro_param_lf}") @@ -1225,7 +1231,7 @@ def on_code_7(self, node: Code7) -> None: node.rotation.visit(self) self._write("*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_20(self, node: Code20) -> None: """Handle `Code20` node.""" self._write(f"{self._macro_primitive_lf}20,{self._macro_param_lf}") @@ -1244,7 +1250,7 @@ def on_code_20(self, node: Code20) -> None: node.rotation.visit(self) self._write("*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_21(self, node: Code21) -> None: """Handle `Code21` node.""" self._write(f"{self._macro_primitive_lf}21,{self._macro_param_lf}") @@ -1261,7 +1267,7 @@ def on_code_21(self, node: Code21) -> None: node.rotation.visit(self) self._write("*") - @_insert_base_indent + @_decorator_insert_base_indent def on_code_22(self, node: Code22) -> None: """Handle `Code22` node.""" self._write(f"{self._macro_primitive_lf}22,{self._macro_param_lf}") @@ -1280,13 +1286,13 @@ def on_code_22(self, node: Code22) -> None: # Properties - @_insert_base_indent + @_decorator_insert_base_indent def on_as(self, node: AS) -> None: """Handle `AS` node.""" with self._extended_command("AS"): self._write(node.correspondence.value) - @_insert_base_indent + @_decorator_insert_base_indent def on_fs(self, node: FS) -> None: """Handle `FS` node.""" with self._extended_command("FS"): @@ -1295,38 +1301,38 @@ def on_fs(self, node: FS) -> None: self._write(f"X{node.x_integral}{node.x_decimal}") self._write(f"Y{node.y_integral}{node.y_decimal}") - @_insert_base_indent + @_decorator_insert_base_indent def on_in(self, node: IN) -> None: """Handle `IN` node.""" with self._extended_command("IN"): self._write(node.name) - @_insert_base_indent + @_decorator_insert_base_indent def on_ip(self, node: IP) -> None: """Handle `IP` node.""" with self._extended_command("IP"): self._write(node.polarity) - @_insert_base_indent + @_decorator_insert_base_indent def on_ir(self, node: IR) -> None: """Handle `IR` node.""" with self._extended_command("IR"): self._write(self._fmt_double(node.rotation_degrees)) - @_insert_base_indent + @_decorator_insert_base_indent def on_mi(self, node: MI) -> None: """Handle `MI` node.""" with self._extended_command("MI"): self._write(f"A{node.a_mirroring}") self._write(f"B{node.b_mirroring}") - @_insert_base_indent + @_decorator_insert_base_indent def on_mo(self, node: MO) -> None: """Handle `MO` node.""" with self._extended_command("MO"): self._write(node.mode.value) - @_insert_base_indent + @_decorator_insert_base_indent def on_of(self, node: OF) -> None: """Handle `OF` node.""" with self._extended_command("OF"): @@ -1335,7 +1341,7 @@ def on_of(self, node: OF) -> None: if node.b_offset is not None: self._write(f"B{node.b_offset}") - @_insert_base_indent + @_decorator_insert_base_indent def on_sf(self, node: SF) -> None: """Handle `SF` node.""" with self._extended_command("SF"): diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 9eed99f5..fc99c58b 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -1062,25 +1062,54 @@ def g_codes(self) -> pp.ParserElement: else: g04_comment = g04_comment.set_parse_action(self.make_unpack_callback(G04)) - def _(cls: Type[Node]) -> pp.ParserElement: + non_standalone_d_codes = self.d_codes(is_standalone=False) + + def _standalone(cls: Type[Node]) -> pp.ParserElement: code = int(cls.__qualname__.lstrip("G")) return ( self._command(pp.Regex(f"G0*{code}")) .set_name(cls.__qualname__) .set_parse_action(self.make_unpack_callback(cls, is_standalone=True)) - ) | ( # We have to account for legacy cases like `G70D02*`, see - # `G.is_standalone` docstring for more information. - (pp.Regex(f"G0*{code}")) + ) + + def _non_standalone(cls: Type[Node]) -> pp.ParserElement: + # We have to account for legacy cases like `G70D02*`, see + # `G.is_standalone` docstring for more information. + code = int(cls.__qualname__.lstrip("G")) + return ( + ( + pp.Regex(f"G0*{code}") + + pp.FollowedBy(pp.one_of(["D", "X", "Y", "I", "J"])) + ) .set_name(cls.__qualname__) .set_parse_action(self.make_unpack_callback(cls, is_standalone=False)) - + self.d_codes(is_standalone=False) - ) + ) + non_standalone_d_codes return pp.MatchFirst( [ g04_comment, *( - _(cls) + _standalone(cast(Type[Node], cls)) + for cls in reversed( + ( + G01, + G02, + G03, + G36, + G37, + G54, + G55, + G70, + G71, + G74, + G75, + G90, + G91, + ) + ) + ), + *( + _non_standalone(cast(Type[Node], cls)) for cls in reversed( ( G01, diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 2d2a116b..82bc7669 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -2,7 +2,6 @@ from dataclasses import dataclass from io import StringIO -from pathlib import Path from typing import Any import pytest @@ -85,9 +84,11 @@ def test_formatter(asset: Asset, config: Config) -> None: formatted_source = output_buffer.read() formatted_ast = parser.parse(formatted_source) - assert formatted_ast.model_dump_json(serialize_as_any=True) == ast.model_dump_json( + if formatted_ast.model_dump_json(serialize_as_any=True) == ast.model_dump_json( serialize_as_any=True - ) + ): + msg = "Formatted AST is the same as the original AST." + raise AssertionError(msg) DONUT_MACRO_SOURCE = """%AMDonut* @@ -144,7 +145,6 @@ def test_indent_character_tab() -> None: class TestMacroSplitMode: - def test_none(self) -> None: formatted_source = _format( DONUT_MACRO_SOURCE, From e88239e5f2e5c78a7f40fc12b3014c0727623df3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 18 Aug 2024 01:59:02 +0200 Subject: [PATCH 51/91] Fix assert in formatter test --- test/gerberx3/test_formatter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 82bc7669..274536fe 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -84,7 +84,7 @@ def test_formatter(asset: Asset, config: Config) -> None: formatted_source = output_buffer.read() formatted_ast = parser.parse(formatted_source) - if formatted_ast.model_dump_json(serialize_as_any=True) == ast.model_dump_json( + if formatted_ast.model_dump_json(serialize_as_any=True) != ast.model_dump_json( serialize_as_any=True ): msg = "Formatted AST is the same as the original AST." From 9c9401c45c59915e8c6b4198cbdbc437e1eb1377 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 18 Aug 2024 02:26:33 +0200 Subject: [PATCH 52/91] Add load commands formatting --- src/pygerber/gerberx3/formatter.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index d2cba129..00af18e2 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -952,27 +952,32 @@ def on_g91(self, node: G91) -> None: @_decorator_insert_base_indent def on_lm(self, node: LM) -> None: """Handle `LM` node.""" - super().on_lm(node) + with self._extended_command(f"LM{node.mirroring.value}"): + pass @_decorator_insert_base_indent def on_ln(self, node: LN) -> None: """Handle `LN` node.""" - super().on_ln(node) + with self._extended_command(f"LN{node.name}"): + pass @_decorator_insert_base_indent def on_lp(self, node: LP) -> None: """Handle `LP` node.""" - super().on_lp(node) + with self._extended_command(f"LP{node.polarity.value}"): + pass @_decorator_insert_base_indent def on_lr(self, node: LR) -> None: """Handle `LR` node.""" - super().on_lr(node) + with self._extended_command(f"LR{self._fmt_double(node.rotation)}"): + pass @_decorator_insert_base_indent def on_ls(self, node: LS) -> None: """Handle `LS` node.""" - super().on_ls(node) + with self._extended_command(f"LS{self._fmt_double(node.scale)}"): + pass # M Codes From 55936074ddb6734a8c45038165f40a54cc70d4af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 18 Aug 2024 21:42:28 +0200 Subject: [PATCH 53/91] Fix parsing and formatting of SR block --- src/pygerber/gerberx3/formatter.py | 7 +++- .../gerberx3/parser/pyparsing/grammar.py | 7 ++-- .../tokens/aperture/step_repeat/3.grb | 40 +++++++++++++++++++ test/assets/gerberx3/ucamco/2.11.2/source.grb | 5 ++- test/assets/gerberx3/ucamco/4.11.4/source.grb | 2 +- 5 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 test/assets/gerberx3/tokens/aperture/step_repeat/3.grb diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 00af18e2..97c77c34 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -159,7 +159,7 @@ def __init__( # noqa: PLR0913 macro_end_in_new_line: bool = False, block_aperture_body_indent: str | int = 0, step_and_repeat_body_indent: str | int = 0, - float_decimal_places: int = 8, + float_decimal_places: int = -1, float_trim_trailing_zeros: bool = True, d01_indent: int | str = 0, d02_indent: int | str = 0, @@ -223,7 +223,8 @@ def __init__( # noqa: PLR0913 Indentation of step and repeat definition body, by default 0 This indentations stacks for nested step and repeat blocks. float_decimal_places : int, optional - Limit number of decimal places shown for float values, by default 8 + Limit number of decimal places shown for float values, by default -1 + Negative values are interpreted as no limit. float_trim_trailing_zeros : bool, optional Remove trailing zeros from floats, by default True When this is enabled, after floating point number is formatted with respect @@ -340,6 +341,8 @@ def output(self) -> StringIO: return self._output def _fmt_double(self, value: Double) -> str: + if self.float_decimal_places < 0: + return str(value) double = f"{value:.{self.float_decimal_places}f}" if self.float_trim_trailing_zeros: return double.rstrip("0").rstrip(".") diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index fc99c58b..82b5365e 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -18,6 +18,7 @@ from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen +from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen from pygerber.gerberx3.ast.nodes.attribute.TA import ( AperFunction, @@ -278,8 +279,8 @@ def aperture_block(self) -> pp.ParserElement: """Create a parser element capable of parsing aperture blocks.""" return pp.MatchFirst( [ - self.ab_open, self.ab_close, + self.ab_open, ] ) @@ -325,8 +326,8 @@ def step_repeat(self) -> pp.ParserElement: """Create a parser element capable of parsing step repeats.""" return pp.MatchFirst( [ - self.sr_open, self.sr_close, + self.sr_open, ] ) @@ -351,7 +352,7 @@ def sr_close(self) -> pp.ParserElement: return ( self._extended_command(pp.Literal("SR")) .set_name("SRclose") - .set_parse_action(self.make_unpack_callback(AMclose)) + .set_parse_action(self.make_unpack_callback(SRclose)) ) def add_aperture(self) -> pp.ParserElement: diff --git a/test/assets/gerberx3/tokens/aperture/step_repeat/3.grb b/test/assets/gerberx3/tokens/aperture/step_repeat/3.grb new file mode 100644 index 00000000..a1af8f4d --- /dev/null +++ b/test/assets/gerberx3/tokens/aperture/step_repeat/3.grb @@ -0,0 +1,40 @@ +%SRX2Y2I35J15*% +G01* +X0Y0D01* +X2500000Y0D01* +X1000000Y1000000D02* +X6000000D01* +X11000000Y6000000D01* +X16000000D02* +Y1000000D01* +D11* +X1000000Y1000000D03* +X11000000D03* +X16000000D03* +Y6000000D03* +X11000000D03* +D12* +X1000000Y6000000D03* +D13* +X21000000Y6000000D03* +D14* +Y3500000D03* +D15* +Y1000000D03* +D10* +X28500000Y4000000D02* +G75* +G03* +X28500000Y4000000I2500000J0D01* +D16* +X25000000Y4000000D03* +X26000000Y3000000D03* +G01* +%LPD*% +D10* +X6000000Y10750000D02* +X11000000D01* +D11* +X6000000Y10750000D03* +X11000000D03* +%SR*% diff --git a/test/assets/gerberx3/ucamco/2.11.2/source.grb b/test/assets/gerberx3/ucamco/2.11.2/source.grb index 586d9853..453a6b7b 100644 --- a/test/assets/gerberx3/ucamco/2.11.2/source.grb +++ b/test/assets/gerberx3/ucamco/2.11.2/source.grb @@ -8,8 +8,9 @@ G04 Ucamco ex. 2: Shapes* G04 Comment %TF.FileFunction,Other,Sample*% G04 Attribute: the is not a PCB layer, it is just an * G04 example * G04 Define Apertures* G04 Comment * -%AMTHERMAL80* G04 Define the aperture macro 'THERMAL80' * -7,0,0,0.800,0.550,0.125,45*% G04 Use thermal primitive in the macro * +%AMTHERMAL80* +7,0,0,0.800,0.550,0.125,45*% G04 Define the aperture macro 'THERMAL80' * + G04 Use thermal primitive in the macro * %ADD10C,0.1*% G04 Define aperture 10 as a circle with diameter 0.1 mm * %ADD11C,0.6*% G04 Define aperture 11 as a circle with diameter 0.6 mm * %ADD12R,0.6X0.6*% G04 Define aperture 12 as a rectangle with size 0.6 x 0.6 mm * diff --git a/test/assets/gerberx3/ucamco/4.11.4/source.grb b/test/assets/gerberx3/ucamco/4.11.4/source.grb index a0f920c7..a0daf38b 100644 --- a/test/assets/gerberx3/ucamco/4.11.4/source.grb +++ b/test/assets/gerberx3/ucamco/4.11.4/source.grb @@ -1,6 +1,6 @@ G04 Ucamco copyright* %TF.GenerationSoftware,Ucamco,UcamX,2016.04-160425*% -%TF.CreationDate,2016-04-25T00:00;00+01:00*% +%TF.CreationDate,2016-04-25T00:00:00+01:00*% %TF.Part,Other,Testfile*% %FSLAX46Y46*% %MOMM*% From c278f58732fc49697968ed61e73d8016e378093c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 02:31:32 +0200 Subject: [PATCH 54/91] Add empty_line_before_polarity_switch implementation --- src/pygerber/gerberx3/formatter.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 97c77c34..4f7bbfe1 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -367,6 +367,28 @@ def _insert_base_indent(self) -> None: def _insert_extra_indent(self, value: str) -> None: self._write(value) + @staticmethod + def _insert_var( + variable_name_or_getter: str | Callable[[Formatter], str], + ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: + def _decorator( + function: Callable[ParamT, ReturnT], + ) -> Callable[ParamT, ReturnT]: + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + if isinstance(variable_name_or_getter, str): + self._write(getattr(self, variable_name_or_getter)) + else: + self._write(variable_name_or_getter(self)) + + return function(*args, **kwargs) + + return _ + + return _decorator + @staticmethod def _increase_base_indent( variable_name: str, @@ -964,6 +986,7 @@ def on_ln(self, node: LN) -> None: with self._extended_command(f"LN{node.name}"): pass + @_insert_var("empty_line_before_polarity_switch") @_decorator_insert_base_indent def on_lp(self, node: LP) -> None: """Handle `LP` node.""" From 24f86a11865c251255010f9a1b51acb7117d7d9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 02:33:29 +0200 Subject: [PATCH 55/91] Add test_step_and_repeat_body_indent() --- test/gerberx3/test_formatter.py | 35 +++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 274536fe..99f43229 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -82,6 +82,7 @@ def test_formatter(asset: Asset, config: Config) -> None: output_buffer.seek(0) formatted_source = output_buffer.read() + formatted_ast = parser.parse(formatted_source) if formatted_ast.model_dump_json(serialize_as_any=True) != ast.model_dump_json( @@ -304,7 +305,7 @@ def test_parameters_macro_end_in_new_line(self) -> None: """ -def test_block_aperture_indent() -> None: +def test_block_aperture_body_indent() -> None: formatted_source = _format( APERTURE_BLOCK_SOURCE, indent_character=" ", @@ -331,7 +332,7 @@ def test_block_aperture_indent() -> None: ) -def test_nested_block_aperture_indent() -> None: +def test_nested_block_aperture_body_indent() -> None: formatted_source = _format( APERTURE_BLOCK_NESTED_SOURCE, indent_character=" ", @@ -360,3 +361,33 @@ def test_nested_block_aperture_indent() -> None: %AB*% """ ) + + +STEP_AND_REPEAT_SOURCE = """%SRX3Y2I5.0J4.0*% +D13* +X123456Y789012D03* +D14* +X456789Y012345D03* +%SR*% +""" + + +def test_step_and_repeat_body_indent() -> None: + formatted_source = _format( + STEP_AND_REPEAT_SOURCE, + indent_character=" ", + step_and_repeat_body_indent=4, + ) + from pathlib import Path + + Path("formatted.gbr").write_text(formatted_source) + assert ( + formatted_source + == """%SRX3Y2I5.0J4.0*% + D13* + X123456Y789012D03* + D14* + X456789Y012345D03* +%SR*% +""" + ) From 8c0bef61c88c2c43376ef64d596b4ac67519c632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 02:34:03 +0200 Subject: [PATCH 56/91] Add formatter presets --- src/pygerber/gerberx3/formatter_presets.py | 68 ++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 src/pygerber/gerberx3/formatter_presets.py diff --git a/src/pygerber/gerberx3/formatter_presets.py b/src/pygerber/gerberx3/formatter_presets.py new file mode 100644 index 00000000..a8fe8555 --- /dev/null +++ b/src/pygerber/gerberx3/formatter_presets.py @@ -0,0 +1,68 @@ +"""`pygerber.gerberx3.formatter_presets` module contains named predefined formatter +configurations. +""" + +from __future__ import annotations + +from pygerber.gerberx3.formatter import Formatter + +extra_indent = { + "indent_character": " ", + "macro_body_indent": 4, + "macro_param_indent": 0, + "macro_split_mode": Formatter.MacroSplitMode.PRIMITIVES, + "macro_end_in_new_line": True, + "block_aperture_body_indent": 4, + "step_and_repeat_body_indent": 4, + "float_decimal_places": 6, + "float_trim_trailing_zeros": True, + "d01_indent": 2, + "d02_indent": 2, + "d03_indent": 2, + "line_end": "\n", + "empty_line_before_polarity_switch": True, + "keep_non_standalone_codes": False, + "remove_g54": True, + "remove_g55": True, + "strip_whitespace": False, +} +balanced = { + "indent_character": " ", + "macro_body_indent": 4, + "macro_param_indent": 0, + "macro_split_mode": Formatter.MacroSplitMode.PRIMITIVES, + "macro_end_in_new_line": True, + "block_aperture_body_indent": 4, + "step_and_repeat_body_indent": 4, + "float_decimal_places": 6, + "float_trim_trailing_zeros": True, + "d01_indent": 0, + "d02_indent": 0, + "d03_indent": 0, + "line_end": "\n", + "empty_line_before_polarity_switch": True, + "keep_non_standalone_codes": False, + "remove_g54": True, + "remove_g55": True, + "strip_whitespace": False, +} +small_indent = { + "indent_character": " ", + "macro_body_indent": 2, + "macro_param_indent": 0, + "macro_split_mode": Formatter.MacroSplitMode.PRIMITIVES, + "macro_end_in_new_line": True, + "block_aperture_body_indent": 2, + "step_and_repeat_body_indent": 2, + "float_decimal_places": 6, + "float_trim_trailing_zeros": True, + "d01_indent": 0, + "d02_indent": 0, + "d03_indent": 0, + "line_end": "\n", + "empty_line_before_polarity_switch": True, + "keep_non_standalone_codes": False, + "remove_g54": True, + "remove_g55": True, + "strip_whitespace": False, +} From bbe7be92d0c4b83d559fddc5836f74de673ebb9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 17:54:46 +0200 Subject: [PATCH 57/91] Improve Shape docstring --- src/pygerber/vm/commands/shape.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/pygerber/vm/commands/shape.py b/src/pygerber/vm/commands/shape.py index b1d7087a..40f3a107 100644 --- a/src/pygerber/vm/commands/shape.py +++ b/src/pygerber/vm/commands/shape.py @@ -22,7 +22,13 @@ class ShapeSegment(BaseModel): class Shape(Command): - """Draw a line from the current position to the given position.""" + """`Shape` command instructs VM to render a shape described by series of + lines and arcs into currently active layer. + + Last point of first segment (line or arc) is always connected to the first point + first segment, so shapes are implicitly closed. If those points are not overlapping, + they are connected by a straight line. + """ commands: List[ShapeSegment] negative: bool = False From 7c311759f06f12c87d3cc91b97ee0f1a43a1ac5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 18:23:07 +0200 Subject: [PATCH 58/91] Extract enums used by nodes into separate module --- .../gerberx3/ast/nodes/attribute/TA.py | 44 +---- .../gerberx3/ast/nodes/attribute/TF.py | 48 +---- .../gerberx3/ast/nodes/attribute/TO.py | 11 +- .../gerberx3/ast/nodes/d_codes/Dnn.py | 3 +- src/pygerber/gerberx3/ast/nodes/enums.py | 177 ++++++++++++++++++ src/pygerber/gerberx3/ast/nodes/load/LM.py | 11 +- src/pygerber/gerberx3/ast/nodes/load/LP.py | 9 +- .../gerberx3/ast/nodes/other/coordinate.py | 9 +- .../gerberx3/ast/nodes/properties/AS.py | 9 +- .../gerberx3/ast/nodes/properties/FS.py | 7 +- .../gerberx3/ast/nodes/properties/IP.py | 3 +- .../gerberx3/ast/nodes/properties/MO.py | 11 +- src/pygerber/gerberx3/ast/nodes/types.py | 29 ++- src/pygerber/gerberx3/ast/visitor.py | 2 +- src/pygerber/gerberx3/formatter.py | 8 +- .../gerberx3/parser/pyparsing/grammar.py | 2 +- test/gerberx3/test_ast/test_ast_visitor.py | 26 +-- 17 files changed, 245 insertions(+), 164 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/enums.py diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py index ee7b8253..c7705e44 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py @@ -2,12 +2,12 @@ from __future__ import annotations -from enum import Enum from typing import TYPE_CHECKING, Callable, List, Literal, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.enums import AperFunction if TYPE_CHECKING: from typing_extensions import Self @@ -36,48 +36,6 @@ def get_visitor_callback_function( return visitor.on_ta_user_name -class AperFunction(Enum): - """Enum representing possible AperFunction values.""" - - ViaDrill = "ViaDrill" - BackDrill = "BackDrill" - ComponentDrill = "ComponentDrill" - MechanicalDrill = "MechanicalDrill" - CastellatedDrill = "CastellatedDrill" - OtherDrill = "OtherDrill" - ComponentPad = "ComponentPad" - SMDPad = "SMDPad" - BGAPad = "BGAPad" - ConnectorPad = "ConnectorPad" - HeatsinkPad = "HeatsinkPad" - ViaPad = "ViaPad" - TestPad = "TestPad" - CastellatedPad = "CastellatedPad" - FiducialPad = "FiducialPad" - ThermalReliefPad = "ThermalReliefPad" - WasherPad = "WasherPad" - AntiPad = "AntiPad" - OtherPad = "OtherPad" - Conductor = "Conductor" - EtchedComponent = "EtchedComponent" - NonConductor = "NonConductor" - CopperBalancing = "CopperBalancing" - Border = "Border" - OtherCopper = "OtherCopper" - ComponentMain = "ComponentMain" - ComponentOutline = "ComponentOutline" - ComponentPin = "ComponentPin" - Profile = "Profile" - Material = "Material" - NonMaterial = "NonMaterial" - Other = "Other" - - def __repr__(self) -> str: - return f"{self.__class__.__name__}.{self.name}" - - __str__ = __repr__ - - class TA_AperFunction(TA): # noqa: N801 """Represents TA .AperFunction Gerber attribute.""" diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py index ee020a8e..c8dd47e3 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -4,12 +4,12 @@ import datetime # noqa: TCH003 import hashlib -from enum import Enum from typing import TYPE_CHECKING, Callable, List, Literal, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.enums import FileFunction, Part if TYPE_CHECKING: from typing_extensions import Self @@ -38,16 +38,6 @@ def get_visitor_callback_function( return visitor.on_tf_user_name -class Part(Enum): - """Enumerate supported part types.""" - - Single = "Single" - Array = "Array" - FabricationPanel = "FabricationPanel" - Coupon = "Coupon" - Other = "Other" - - class TF_Part(TF): # noqa: N801 """Represents TF Gerber extended command with part attribute.""" @@ -65,42 +55,6 @@ def get_visitor_callback_function( return visitor.on_tf_part -class FileFunction(Enum): - """Enumerate supported file function types.""" - - Copper = "Copper" - Plated = "Plated" - NonPlated = "NonPlated" - Profile = "Profile" - Soldermask = "Soldermask" - Legend = "Legend" - Component = "Component" - Paste = "Paste" - Glue = "Glue" - Carbonmask = "Carbonmask" - Goldmask = "Goldmask" - Heatsinkmask = "Heatsinkmask" - Peelablemask = "Peelablemask" - Silvermask = "Silvermask" - Tinmask = "Tinmask" - Depthrout = "Depthrout" - Vcut = "Vcut" - Viafill = "Viafill" - Pads = "Pads" - Other = "Other" - Drillmap = "Drillmap" - FabricationDrawing = "FabricationDrawing" - Vcutmap = "Vcutmap" - AssemblyDrawing = "AssemblyDrawing" - ArrayDrawing = "ArrayDrawing" - OtherDrawing = "OtherDrawing" - - def __repr__(self) -> str: - return f"{self.__class__.__name__}.{self.name}" - - __str__ = __repr__ - - class TF_FileFunction(TF): # noqa: N801 """Represents TF Gerber extended command with file function attribute.""" diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py index fdb25887..c99180fc 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py @@ -2,12 +2,12 @@ from __future__ import annotations -from enum import Enum from typing import TYPE_CHECKING, Callable, List, Optional from pydantic import Field from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.enums import Mount from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: @@ -151,15 +151,6 @@ def get_visitor_callback_function( return visitor.on_to_cval -class Mount(Enum): - """Mount type enumeration.""" - - SMD = "SMD" - TH = "TH" - Pressfit = "Pressfit" - Other = "Other" - - class TO_CMnt(TO): # noqa: N801 """Represents TO Gerber extended command with .CMnt attribute.""" diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py index 8dbedb79..85fd7472 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.d_codes.D import D +from pygerber.gerberx3.ast.nodes.types import ApertureIdStr if TYPE_CHECKING: from typing_extensions import Self @@ -15,7 +16,7 @@ class Dnn(D): """Represents DNN Gerber command.""" - value: str + aperture_id: ApertureIdStr def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/enums.py b/src/pygerber/gerberx3/ast/nodes/enums.py new file mode 100644 index 00000000..16611458 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/enums.py @@ -0,0 +1,177 @@ +"""`pygerber.gerberx3.ast.nodes.enums` module contains definition of enums used in +GerberX3 AST nodes. +""" + +from __future__ import annotations + +from enum import Enum + + +class Zeros(Enum): + """Zeros enumeration.""" + + SKIP_LEADING = "L" + """Skip leading zeros mode.""" + + SKIP_TRAILING = "T" + """Skip trailing zeros mode.""" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + __str__ = __repr__ + + +class CoordinateMode(Enum): + """Coordinate mode enumeration.""" + + ABSOLUTE = "A" + """Absolute coordinate mode.""" + + INCREMENTAL = "I" + """Incremental coordinate mode.""" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + __str__ = __repr__ + + +class AperFunction(Enum): + """Enum representing possible AperFunction values.""" + + ViaDrill = "ViaDrill" + BackDrill = "BackDrill" + ComponentDrill = "ComponentDrill" + MechanicalDrill = "MechanicalDrill" + CastellatedDrill = "CastellatedDrill" + OtherDrill = "OtherDrill" + ComponentPad = "ComponentPad" + SMDPad = "SMDPad" + BGAPad = "BGAPad" + ConnectorPad = "ConnectorPad" + HeatsinkPad = "HeatsinkPad" + ViaPad = "ViaPad" + TestPad = "TestPad" + CastellatedPad = "CastellatedPad" + FiducialPad = "FiducialPad" + ThermalReliefPad = "ThermalReliefPad" + WasherPad = "WasherPad" + AntiPad = "AntiPad" + OtherPad = "OtherPad" + Conductor = "Conductor" + EtchedComponent = "EtchedComponent" + NonConductor = "NonConductor" + CopperBalancing = "CopperBalancing" + Border = "Border" + OtherCopper = "OtherCopper" + ComponentMain = "ComponentMain" + ComponentOutline = "ComponentOutline" + ComponentPin = "ComponentPin" + Profile = "Profile" + Material = "Material" + NonMaterial = "NonMaterial" + Other = "Other" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + __str__ = __repr__ + + +class Part(Enum): + """Enumerate supported part types.""" + + Single = "Single" + Array = "Array" + FabricationPanel = "FabricationPanel" + Coupon = "Coupon" + Other = "Other" + + +class FileFunction(Enum): + """Enumerate supported file function types.""" + + Copper = "Copper" + Plated = "Plated" + NonPlated = "NonPlated" + Profile = "Profile" + Soldermask = "Soldermask" + Legend = "Legend" + Component = "Component" + Paste = "Paste" + Glue = "Glue" + Carbonmask = "Carbonmask" + Goldmask = "Goldmask" + Heatsinkmask = "Heatsinkmask" + Peelablemask = "Peelablemask" + Silvermask = "Silvermask" + Tinmask = "Tinmask" + Depthrout = "Depthrout" + Vcut = "Vcut" + Viafill = "Viafill" + Pads = "Pads" + Other = "Other" + Drillmap = "Drillmap" + FabricationDrawing = "FabricationDrawing" + Vcutmap = "Vcutmap" + AssemblyDrawing = "AssemblyDrawing" + ArrayDrawing = "ArrayDrawing" + OtherDrawing = "OtherDrawing" + + def __repr__(self) -> str: + return f"{self.__class__.__name__}.{self.name}" + + __str__ = __repr__ + + +class Mount(Enum): + """Mount type enumeration.""" + + SMD = "SMD" + TH = "TH" + Pressfit = "Pressfit" + Fiducial = "Fiducial" + Other = "Other" + + +class Mirroring(Enum): + """Mirroring enum.""" + + None_ = "N" + X = "X" + Y = "Y" + XY = "XY" + + +class Polarity(Enum): + """Polarity enum.""" + + Clear = "C" + Dark = "D" + + +class AxisCorrespondence(Enum): + """Represents axis correspondence.""" + + AX_BY = "AXBY" + AY_BX = "AYBX" + + +class UnitMode(Enum): + """Unit mode enumeration.""" + + IMPERIAL = "IN" + """Imperial unit mode. In this mode inches are used to express lengths.""" + METRIC = "MM" + """Metric unit mode. In this mode millimeters are used to express lengths.""" + + +class ImagePolarity(Enum): + """Image polarity enumeration.""" + + POSITIVE = "POS" + """Positive image polarity.""" + + NEGATIVE = "NEG" + """Negative image polarity.""" diff --git a/src/pygerber/gerberx3/ast/nodes/load/LM.py b/src/pygerber/gerberx3/ast/nodes/load/LM.py index 759cce76..de4cfefb 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LM.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LM.py @@ -2,10 +2,10 @@ from __future__ import annotations -from enum import Enum from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.state_enums import Mirroring if TYPE_CHECKING: from typing_extensions import Self @@ -13,15 +13,6 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class Mirroring(Enum): - """Mirroring enum.""" - - None_ = "N" - X = "X" - Y = "Y" - XY = "XY" - - class LM(Node): """Represents LM Gerber extended command.""" diff --git a/src/pygerber/gerberx3/ast/nodes/load/LP.py b/src/pygerber/gerberx3/ast/nodes/load/LP.py index f9f048e2..b47369ef 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LP.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LP.py @@ -2,10 +2,10 @@ from __future__ import annotations -from enum import Enum from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.state_enums import Polarity if TYPE_CHECKING: from typing_extensions import Self @@ -13,13 +13,6 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class Polarity(Enum): - """Polarity enum.""" - - Clear = "C" - Dark = "D" - - class LP(Node): """Represents LP Gerber extended command.""" diff --git a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py index 57e88ede..6e98d7c2 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py +++ b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py @@ -7,6 +7,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import PackedCoordinateStr if TYPE_CHECKING: from typing_extensions import Self @@ -21,7 +22,7 @@ class Coordinate(Node): class CoordinateX(Coordinate): """Represents X Coordinate node.""" - value: str + value: PackedCoordinateStr def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" @@ -37,7 +38,7 @@ def get_visitor_callback_function( class CoordinateY(Coordinate): """Represents Y Coordinate node.""" - value: str + value: PackedCoordinateStr def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" @@ -53,7 +54,7 @@ def get_visitor_callback_function( class CoordinateI(Coordinate): """Represents I Coordinate node.""" - value: str + value: PackedCoordinateStr def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" @@ -69,7 +70,7 @@ def get_visitor_callback_function( class CoordinateJ(Coordinate): """Represents J Coordinate node.""" - value: str + value: PackedCoordinateStr def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/properties/AS.py b/src/pygerber/gerberx3/ast/nodes/properties/AS.py index 04375bea..79320442 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/AS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/AS.py @@ -2,10 +2,10 @@ from __future__ import annotations -from enum import Enum from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.enums import AxisCorrespondence if TYPE_CHECKING: from typing_extensions import Self @@ -13,13 +13,6 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class AxisCorrespondence(Enum): - """Represents axis correspondence.""" - - AX_BY = "AXBY" - AY_BX = "AYBX" - - class AS(Node): """Represents AS Gerber extended command.""" diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py index 7bb3a09c..7a30be58 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/FS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -2,9 +2,10 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable, Literal +from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.enums import CoordinateMode, Zeros if TYPE_CHECKING: from typing_extensions import Self @@ -15,8 +16,8 @@ class FS(Node): """Represents FS Gerber extended command.""" - zeros: Literal["L", "T"] - coordinate_mode: Literal["A", "I"] + zeros: Zeros + coordinate_mode: CoordinateMode x_integral: int x_decimal: int diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IP.py b/src/pygerber/gerberx3/ast/nodes/properties/IP.py index 80a22f7d..33289036 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IP.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IP.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.enums import ImagePolarity if TYPE_CHECKING: from typing_extensions import Self @@ -15,7 +16,7 @@ class IP(Node): """Represents IP Gerber extended command.""" - polarity: str + polarity: ImagePolarity def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MO.py b/src/pygerber/gerberx3/ast/nodes/properties/MO.py index 7fad7d87..20218a3b 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/MO.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/MO.py @@ -2,10 +2,10 @@ from __future__ import annotations -from enum import Enum from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.enums import UnitMode if TYPE_CHECKING: from typing_extensions import Self @@ -13,15 +13,6 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class UnitMode(Enum): - """Unit mode enumeration.""" - - IMPERIAL = "IN" - """Imperial unit mode. In this mode inches are used to express lengths.""" - METRIC = "MM" - """Metric unit mode. In this mode millimeters are used to express lengths.""" - - class MO(Node): """Represents MO Gerber extended command.""" diff --git a/src/pygerber/gerberx3/ast/nodes/types.py b/src/pygerber/gerberx3/ast/nodes/types.py index eb796fe6..fe3e5cfe 100644 --- a/src/pygerber/gerberx3/ast/nodes/types.py +++ b/src/pygerber/gerberx3/ast/nodes/types.py @@ -2,10 +2,37 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any + +from pydantic_core import CoreSchema, core_schema if TYPE_CHECKING: + from pydantic import GetCoreSchemaHandler from typing_extensions import TypeAlias Double: TypeAlias = float Integer: TypeAlias = int + + +class ApertureIdStr(str): + """String subclass representing aperture ID.""" + + __slots__ = () + + @classmethod + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: + return core_schema.no_info_after_validator_function(cls, handler(str)) + + +class PackedCoordinateStr(str): + """String subclass representing packed coordinates.""" + + __slots__ = () + + @classmethod + def __get_pydantic_core_schema__( + cls, source_type: Any, handler: GetCoreSchemaHandler + ) -> CoreSchema: + return core_schema.no_info_after_validator_function(cls, handler(str)) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index c5c42de2..40e8232a 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -1,4 +1,4 @@ -"""`pygerber.gerberx3.node_visitor` contains definition of `NodeVisitor` class.""" +"""`pygerber.gerberx3.visitor` contains definition of `AstVisitor` class.""" from __future__ import annotations diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 4f7bbfe1..e3d0aa0a 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -884,7 +884,7 @@ def on_dnn(self, node: Dnn) -> None: if node.is_standalone or not self.keep_non_standalone_codes: self._insert_base_indent() - with self._command(node.value): + with self._command(node.aperture_id): pass # G codes @@ -1327,8 +1327,8 @@ def on_as(self, node: AS) -> None: def on_fs(self, node: FS) -> None: """Handle `FS` node.""" with self._extended_command("FS"): - self._write(node.zeros) - self._write(node.coordinate_mode) + self._write(node.zeros.value) + self._write(node.coordinate_mode.value) self._write(f"X{node.x_integral}{node.x_decimal}") self._write(f"Y{node.y_integral}{node.y_decimal}") @@ -1342,7 +1342,7 @@ def on_in(self, node: IN) -> None: def on_ip(self, node: IP) -> None: """Handle `IP` node.""" with self._extended_command("IP"): - self._write(node.polarity) + self._write(node.polarity.value) @_decorator_insert_base_indent def on_ir(self, node: IR) -> None: diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 82b5365e..ebeaaedc 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -998,7 +998,7 @@ def d_codes(self, *, is_standalone: bool) -> pp.ParserElement: def _dnn(self, *, is_standalone: bool) -> pp.ParserElement: return ( - self._command(self.aperture_identifier.set_results_name("value")) + self._command(self.aperture_identifier.set_results_name("aperture_id")) .set_parse_action( self.make_unpack_callback(Dnn, is_standalone=is_standalone) ) diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index c1100020..8d78cc39 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -63,6 +63,7 @@ from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn +from pygerber.gerberx3.ast.nodes.enums import CoordinateMode, ImagePolarity, Zeros from pygerber.gerberx3.ast.nodes.file import File from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 @@ -121,6 +122,7 @@ from pygerber.gerberx3.ast.nodes.properties.MO import MO, UnitMode from pygerber.gerberx3.ast.nodes.properties.OF import OF from pygerber.gerberx3.ast.nodes.properties.SF import SF +from pygerber.gerberx3.ast.nodes.types import ApertureIdStr, PackedCoordinateStr from pygerber.gerberx3.ast.visitor import AstVisitor NODE_SAMPLES: Dict[Type[Node], Node] = { @@ -238,16 +240,16 @@ D02: D02( source="", location=0, - x=CoordinateX(source="", location=0, value="1"), - y=CoordinateY(source="", location=0, value="2"), + x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(source="", location=0, value=PackedCoordinateStr("2")), ), D03: D03( source="", location=0, - x=CoordinateX(source="", location=0, value="1"), - y=CoordinateY(source="", location=0, value="2"), + x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(source="", location=0, value=PackedCoordinateStr("2")), ), - Dnn: Dnn(source="", location=0, value="D11"), + Dnn: Dnn(source="", location=0, aperture_id=ApertureIdStr("D11")), G01: G01(source="", location=0), G02: G02(source="", location=0), G03: G03(source="", location=0), @@ -326,10 +328,10 @@ y=Constant(source="", location=0, constant=2), ), Variable: Variable(source="", location=0, variable="$1"), - CoordinateX: CoordinateX(source="", location=0, value="1"), - CoordinateY: CoordinateY(source="", location=0, value="1"), - CoordinateI: CoordinateI(source="", location=0, value="1"), - CoordinateJ: CoordinateJ(source="", location=0, value="1"), + CoordinateX: CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + CoordinateY: CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), + CoordinateI: CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), + CoordinateJ: CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), Code0: Code0(source="", location=0, string="string"), Code1: Code1( source="", @@ -435,15 +437,15 @@ FS: FS( source="", location=0, - zeros="L", - coordinate_mode="A", + zeros=Zeros.SKIP_LEADING, + coordinate_mode=CoordinateMode.ABSOLUTE, x_integral=2, x_decimal=3, y_integral=4, y_decimal=5, ), IN: IN(source="", location=0, name="name"), - IP: IP(source="", location=0, polarity="D"), + IP: IP(source="", location=0, polarity=ImagePolarity.POSITIVE), IR: IR(source="", location=0, rotation_degrees=90), MI: MI(source="", location=0, a_mirroring=0, b_mirroring=1), MO: MO(source="", location=0, mode=UnitMode.METRIC), From 7ce8da48d6b94eb188850ef50115980fb0b9889e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 19:29:15 +0200 Subject: [PATCH 59/91] Add common base class for all AD command variants --- src/pygerber/gerberx3/ast/nodes/aperture/AD.py | 11 +++++++++++ src/pygerber/gerberx3/ast/nodes/aperture/ADC.py | 5 ++--- src/pygerber/gerberx3/ast/nodes/aperture/ADO.py | 5 ++--- src/pygerber/gerberx3/ast/nodes/aperture/ADP.py | 5 ++--- src/pygerber/gerberx3/ast/nodes/aperture/ADR.py | 5 ++--- src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py | 5 ++--- src/pygerber/gerberx3/ast/nodes/enums.py | 2 +- 7 files changed, 22 insertions(+), 16 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AD.py diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py new file mode 100644 index 00000000..6b1519ed --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py @@ -0,0 +1,11 @@ +"""`pygerber.nodes.aperture.ADC` module contains definition of `AD` class.""" + +from __future__ import annotations + +from pygerber.gerberx3.ast.nodes.base import Node + + +class AD(Node): + """Common base class for all commands adding new apertures.""" + + aperture_identifier: str diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py index ca920474..57d20278 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.aperture.AD import AD from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: @@ -15,10 +15,9 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class ADC(Node): +class ADC(AD): """Represents AD Gerber extended command.""" - aperture_identifier: str diameter: Double hole_diameter: Optional[Double] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py index 46b251fa..510c9255 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.aperture.AD import AD from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: @@ -15,10 +15,9 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class ADO(Node): +class ADO(AD): """Represents AD obround Gerber extended command.""" - aperture_identifier: str width: Double height: Double hole_diameter: Optional[Double] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py index 2d6b04b0..d516a267 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.aperture.AD import AD from pygerber.gerberx3.ast.nodes.types import Double, Integer if TYPE_CHECKING: @@ -15,10 +15,9 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class ADP(Node): +class ADP(AD): """Represents AD polygon Gerber extended command.""" - aperture_identifier: str outer_diameter: Double vertices: Integer rotation: Optional[Double] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py index eb18b47a..f14e79d1 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.aperture.AD import AD from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: @@ -15,10 +15,9 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class ADR(Node): +class ADR(AD): """Represents AD rectangle Gerber extended command.""" - aperture_identifier: str width: Double height: Double hole_diameter: Optional[Double] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py index 85705340..e88f7cea 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py @@ -6,7 +6,7 @@ from pydantic import Field -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.aperture.AD import AD from pygerber.gerberx3.ast.nodes.types import Double if TYPE_CHECKING: @@ -15,10 +15,9 @@ from pygerber.gerberx3.ast.visitor import AstVisitor -class ADmacro(Node): +class ADmacro(AD): """Represents AD macro Gerber extended command.""" - aperture_identifier: str name: str params: Optional[List[Double]] = Field(default=None) diff --git a/src/pygerber/gerberx3/ast/nodes/enums.py b/src/pygerber/gerberx3/ast/nodes/enums.py index 16611458..8f9d93f4 100644 --- a/src/pygerber/gerberx3/ast/nodes/enums.py +++ b/src/pygerber/gerberx3/ast/nodes/enums.py @@ -138,7 +138,7 @@ class Mount(Enum): class Mirroring(Enum): """Mirroring enum.""" - None_ = "N" + NONE = "N" X = "X" Y = "Y" XY = "XY" From 99b3c2aeecde8f8a29e40f7bb0079bc0f4105f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 22:13:32 +0200 Subject: [PATCH 60/91] Add AB class for wrapping whole AB blocks --- src/pygerber/gerberx3/ast/nodes/__init__.py | 28 ++ .../gerberx3/ast/nodes/aperture/AB.py | 32 +++ .../gerberx3/ast/nodes/aperture/AM.py | 28 ++ src/pygerber/gerberx3/ast/visitor.py | 193 +++++++------- src/pygerber/gerberx3/formatter.py | 2 +- .../gerberx3/parser/pyparsing/grammar.py | 246 ++++++++++-------- 6 files changed, 334 insertions(+), 195 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AB.py create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/AM.py diff --git a/src/pygerber/gerberx3/ast/nodes/__init__.py b/src/pygerber/gerberx3/ast/nodes/__init__.py index 8492268c..829f2b2b 100644 --- a/src/pygerber/gerberx3/ast/nodes/__init__.py +++ b/src/pygerber/gerberx3/ast/nodes/__init__.py @@ -4,18 +4,22 @@ from __future__ import annotations +from pygerber.gerberx3.ast.nodes.aperture.AB import AB from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen +from pygerber.gerberx3.ast.nodes.aperture.AD import AD from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR +from pygerber.gerberx3.ast.nodes.aperture.AM import AM from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen from pygerber.gerberx3.ast.nodes.attribute.TA import ( + TA, TA_AperFunction, TA_DrillTolerance, TA_FlashText, @@ -23,6 +27,7 @@ ) from pygerber.gerberx3.ast.nodes.attribute.TD import TD from pygerber.gerberx3.ast.nodes.attribute.TF import ( + TF, TF_MD5, TF_CreationDate, TF_FileFunction, @@ -34,6 +39,7 @@ TF_UserName, ) from pygerber.gerberx3.ast.nodes.attribute.TO import ( + TO, TO_C, TO_CMNP, TO_N, @@ -51,6 +57,7 @@ TO_CVal, TO_UserName, ) +from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 @@ -81,6 +88,7 @@ from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 from pygerber.gerberx3.ast.nodes.math.assignment import Assignment from pygerber.gerberx3.ast.nodes.math.constant import Constant +from pygerber.gerberx3.ast.nodes.math.expression import Expression from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul @@ -90,6 +98,7 @@ from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable from pygerber.gerberx3.ast.nodes.other.coordinate import ( + Coordinate, CoordinateI, CoordinateJ, CoordinateX, @@ -114,12 +123,19 @@ from pygerber.gerberx3.ast.nodes.properties.MO import MO from pygerber.gerberx3.ast.nodes.properties.OF import OF from pygerber.gerberx3.ast.nodes.properties.SF import SF +from pygerber.gerberx3.ast.nodes.types import ( + ApertureIdStr, + Double, + Integer, + PackedCoordinateStr, +) __all__ = [ "ABclose", "ABopen", "ADC", "ADmacro", + "AD", "ADO", "ADP", "ADR", @@ -218,4 +234,16 @@ "MO", "OF", "SF", + "Double", + "Integer", + "ApertureIdStr", + "PackedCoordinateStr", + "Node", + "TA", + "TO", + "TF", + "Coordinate", + "Expression", + "AB", + "AM", ] diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB.py new file mode 100644 index 00000000..ac0c1c0a --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB.py @@ -0,0 +1,32 @@ +"""`pygerber.nodes.aperture.AB` module contains definition of `AB` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose +from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from typing_extensions import Self + + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class AB(Node): + """Represents AB Gerber extended command.""" + + open: ABopen + nodes: list[Node] + close: ABclose + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_ab(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_ab diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py new file mode 100644 index 00000000..441b8999 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py @@ -0,0 +1,28 @@ +"""`pygerber.nodes.aperture.AM` module contains definition of `AM` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from typing_extensions import Self + + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class AM(Node): + """Represents AM Gerber extended command.""" + + nodes: list[Node] + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_am(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_am diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 40e8232a..50ee1168 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -5,28 +5,96 @@ from typing import TYPE_CHECKING, List, Optional if TYPE_CHECKING: - from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose - from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen - from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC - from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro - from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO - from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP - from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR - from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose - from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen - from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose - from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen - from pygerber.gerberx3.ast.nodes.attribute.TA import ( + from pygerber.gerberx3.ast.nodes import ( + AB, + AD, + ADC, + ADO, + ADP, + ADR, + AM, + AS, + D01, + D02, + D03, + FS, + G01, + G02, + G03, + G04, + G36, + G37, + G54, + G55, + G70, + G71, + G74, + G75, + G90, + G91, + IN, + IP, + IR, + LM, + LN, + LP, + LR, + LS, + M00, + M01, + M02, + MI, + MO, + OF, + SF, TA, + TD, + TF, + TF_MD5, + TO, + TO_C, + TO_CMNP, + TO_N, + TO_P, + ABclose, + ABopen, + Add, + ADmacro, + AMclose, + AMopen, + Assignment, + Code0, + Code1, + Code2, + Code4, + Code5, + Code6, + Code7, + Code20, + Code21, + Code22, + Constant, + Coordinate, + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, + Div, + Dnn, + Expression, + File, + Mul, + Neg, + Node, + Point, + Pos, + SRclose, + SRopen, + Sub, TA_AperFunction, TA_DrillTolerance, TA_FlashText, TA_UserName, - ) - from pygerber.gerberx3.ast.nodes.attribute.TD import TD - from pygerber.gerberx3.ast.nodes.attribute.TF import ( - TF, - TF_MD5, TF_CreationDate, TF_FileFunction, TF_FilePolarity, @@ -35,13 +103,6 @@ TF_ProjectId, TF_SameCoordinates, TF_UserName, - ) - from pygerber.gerberx3.ast.nodes.attribute.TO import ( - TO, - TO_C, - TO_CMNP, - TO_N, - TO_P, TO_CFtp, TO_CHgt, TO_CLbD, @@ -54,72 +115,8 @@ TO_CSup, TO_CVal, TO_UserName, + Variable, ) - from pygerber.gerberx3.ast.nodes.base import Node - from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 - from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 - from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 - from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn - from pygerber.gerberx3.ast.nodes.file import File - from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 - from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 - from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 - from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 - from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 - from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 - from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 - from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 - from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 - from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 - from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 - from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 - from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 - from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 - from pygerber.gerberx3.ast.nodes.load.LM import LM - from pygerber.gerberx3.ast.nodes.load.LN import LN - from pygerber.gerberx3.ast.nodes.load.LP import LP - from pygerber.gerberx3.ast.nodes.load.LR import LR - from pygerber.gerberx3.ast.nodes.load.LS import LS - from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 - from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 - from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 - from pygerber.gerberx3.ast.nodes.math.assignment import Assignment - from pygerber.gerberx3.ast.nodes.math.constant import Constant - from pygerber.gerberx3.ast.nodes.math.expression import Expression - from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add - from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div - from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul - from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub - from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg - from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos - from pygerber.gerberx3.ast.nodes.math.point import Point - from pygerber.gerberx3.ast.nodes.math.variable import Variable - from pygerber.gerberx3.ast.nodes.other.coordinate import ( - Coordinate, - CoordinateI, - CoordinateJ, - CoordinateX, - CoordinateY, - ) - from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 - from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 - from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 - from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 - from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 - from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 - from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 - from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 - from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 - from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 - from pygerber.gerberx3.ast.nodes.properties.AS import AS - from pygerber.gerberx3.ast.nodes.properties.FS import FS - from pygerber.gerberx3.ast.nodes.properties.IN import IN - from pygerber.gerberx3.ast.nodes.properties.IP import IP - from pygerber.gerberx3.ast.nodes.properties.IR import IR - from pygerber.gerberx3.ast.nodes.properties.MI import MI - from pygerber.gerberx3.ast.nodes.properties.MO import MO - from pygerber.gerberx3.ast.nodes.properties.OF import OF - from pygerber.gerberx3.ast.nodes.properties.SF import SF class AstVisitor: @@ -136,26 +133,44 @@ def __init__(self) -> None: # Aperture + def on_ab(self, node: AB) -> None: + """Handle `AB` root node.""" + node.open.visit(self) + for inner_node in node.nodes: + inner_node.visit(self) + node.close.visit(self) + def on_ab_close(self, node: ABclose) -> None: """Handle `ABclose` node.""" def on_ab_open(self, node: ABopen) -> None: """Handle `ABopen` node.""" + def on_ad(self, node: AD) -> None: + """Handle `AD` node.""" + def on_adc(self, node: ADC) -> None: """Handle `AD` circle node.""" + self.on_ad(node) def on_adr(self, node: ADR) -> None: """Handle `AD` rectangle node.""" + self.on_ad(node) def on_ado(self, node: ADO) -> None: """Handle `AD` obround node.""" + self.on_ad(node) def on_adp(self, node: ADP) -> None: """Handle `AD` polygon node.""" + self.on_ad(node) def on_ad_macro(self, node: ADmacro) -> None: """Handle `AD` macro node.""" + self.on_ad(node) + + def on_am(self, node: AM) -> None: + """Handle `AM` root node.""" def on_am_close(self, node: AMclose) -> None: """Handle `AMclose` node.""" diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index e3d0aa0a..31c87f79 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -89,6 +89,7 @@ CoordinateY, Div, Dnn, + Double, File, G, Mul, @@ -124,7 +125,6 @@ TO_UserName, Variable, ) -from pygerber.gerberx3.ast.nodes.types import Double from pygerber.gerberx3.ast.visitor import AstVisitor if TYPE_CHECKING: diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index ebeaaedc..b1cda47c 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -9,27 +9,90 @@ import pyparsing as pp -from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose -from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen -from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC -from pygerber.gerberx3.ast.nodes.aperture.ADmacro import ADmacro -from pygerber.gerberx3.ast.nodes.aperture.ADO import ADO -from pygerber.gerberx3.ast.nodes.aperture.ADP import ADP -from pygerber.gerberx3.ast.nodes.aperture.ADR import ADR -from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose -from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen -from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose -from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen -from pygerber.gerberx3.ast.nodes.attribute.TA import ( - AperFunction, +from pygerber.gerberx3.ast.nodes import ( + AB, + ADC, + ADO, + ADP, + ADR, + AS, + D01, + D02, + D03, + FS, + G01, + G02, + G03, + G04, + G36, + G37, + G54, + G55, + G70, + G71, + G74, + G75, + G90, + G91, + IN, + IP, + IR, + LM, + LN, + LP, + LR, + LS, + M00, + M01, + M02, + MI, + MO, + OF, + SF, + TD, + TF_MD5, + TO_C, + TO_CMNP, + TO_N, + TO_P, + ABclose, + ABopen, + Add, + ADmacro, + AMclose, + AMopen, + Assignment, + Code0, + Code1, + Code2, + Code4, + Code5, + Code6, + Code7, + Code20, + Code21, + Code22, + Constant, + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, + Div, + Dnn, + Expression, + File, + Mul, + Neg, + Node, + Point, + Pos, + SRclose, + SRopen, + Sub, TA_AperFunction, TA_DrillTolerance, TA_FlashText, TA_UserName, -) -from pygerber.gerberx3.ast.nodes.attribute.TD import TD -from pygerber.gerberx3.ast.nodes.attribute.TF import ( - TF_MD5, TF_CreationDate, TF_FileFunction, TF_FilePolarity, @@ -38,12 +101,6 @@ TF_ProjectId, TF_SameCoordinates, TF_UserName, -) -from pygerber.gerberx3.ast.nodes.attribute.TO import ( - TO_C, - TO_CMNP, - TO_N, - TO_P, TO_CFtp, TO_CHgt, TO_CLbD, @@ -56,70 +113,9 @@ TO_CSup, TO_CVal, TO_UserName, + Variable, ) -from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 -from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 -from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 -from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn -from pygerber.gerberx3.ast.nodes.file import File -from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 -from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 -from pygerber.gerberx3.ast.nodes.g_codes.G03 import G03 -from pygerber.gerberx3.ast.nodes.g_codes.G04 import G04 -from pygerber.gerberx3.ast.nodes.g_codes.G36 import G36 -from pygerber.gerberx3.ast.nodes.g_codes.G37 import G37 -from pygerber.gerberx3.ast.nodes.g_codes.G54 import G54 -from pygerber.gerberx3.ast.nodes.g_codes.G55 import G55 -from pygerber.gerberx3.ast.nodes.g_codes.G70 import G70 -from pygerber.gerberx3.ast.nodes.g_codes.G71 import G71 -from pygerber.gerberx3.ast.nodes.g_codes.G74 import G74 -from pygerber.gerberx3.ast.nodes.g_codes.G75 import G75 -from pygerber.gerberx3.ast.nodes.g_codes.G90 import G90 -from pygerber.gerberx3.ast.nodes.g_codes.G91 import G91 -from pygerber.gerberx3.ast.nodes.load.LM import LM -from pygerber.gerberx3.ast.nodes.load.LN import LN -from pygerber.gerberx3.ast.nodes.load.LP import LP -from pygerber.gerberx3.ast.nodes.load.LR import LR -from pygerber.gerberx3.ast.nodes.load.LS import LS -from pygerber.gerberx3.ast.nodes.m_codes.M00 import M00 -from pygerber.gerberx3.ast.nodes.m_codes.M01 import M01 -from pygerber.gerberx3.ast.nodes.m_codes.M02 import M02 -from pygerber.gerberx3.ast.nodes.math.assignment import Assignment -from pygerber.gerberx3.ast.nodes.math.constant import Constant -from pygerber.gerberx3.ast.nodes.math.expression import Expression -from pygerber.gerberx3.ast.nodes.math.operators.binary.add import Add -from pygerber.gerberx3.ast.nodes.math.operators.binary.div import Div -from pygerber.gerberx3.ast.nodes.math.operators.binary.mul import Mul -from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub -from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg -from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos -from pygerber.gerberx3.ast.nodes.math.point import Point -from pygerber.gerberx3.ast.nodes.math.variable import Variable -from pygerber.gerberx3.ast.nodes.other.coordinate import ( - CoordinateI, - CoordinateJ, - CoordinateX, - CoordinateY, -) -from pygerber.gerberx3.ast.nodes.primitives.code_0 import Code0 -from pygerber.gerberx3.ast.nodes.primitives.code_1 import Code1 -from pygerber.gerberx3.ast.nodes.primitives.code_2 import Code2 -from pygerber.gerberx3.ast.nodes.primitives.code_4 import Code4 -from pygerber.gerberx3.ast.nodes.primitives.code_5 import Code5 -from pygerber.gerberx3.ast.nodes.primitives.code_6 import Code6 -from pygerber.gerberx3.ast.nodes.primitives.code_7 import Code7 -from pygerber.gerberx3.ast.nodes.primitives.code_20 import Code20 -from pygerber.gerberx3.ast.nodes.primitives.code_21 import Code21 -from pygerber.gerberx3.ast.nodes.primitives.code_22 import Code22 -from pygerber.gerberx3.ast.nodes.properties.FS import FS -from pygerber.gerberx3.ast.nodes.properties.IN import IN -from pygerber.gerberx3.ast.nodes.properties.IP import IP -from pygerber.gerberx3.ast.nodes.properties.IR import IR -from pygerber.gerberx3.ast.nodes.properties.MI import MI -from pygerber.gerberx3.ast.nodes.properties.MO import MO -from pygerber.gerberx3.ast.nodes.properties.OF import OF -from pygerber.gerberx3.ast.nodes.properties.SF import SF +from pygerber.gerberx3.ast.nodes.enums import AperFunction T = TypeVar("T", bound=Node) @@ -158,12 +154,12 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: pp.MatchFirst( [ self.aperture(), - self.attribute(), - self.g_codes(), - self.load_commands(), - self.m_codes(), - self.d_codes(is_standalone=True), - self.properties(), + self.attribute, + self.g_codes, + self.load_commands, + self.m_codes, + self.properties, + self.d_codes_standalone, ] ) ) @@ -269,21 +265,45 @@ def aperture(self) -> pp.ParserElement: return pp.MatchFirst( [ self.aperture_block(), - self.macro(), - self.step_repeat(), - self.add_aperture(), + self.macro, + self.step_repeat, + self.add_aperture, ] ) def aperture_block(self) -> pp.ParserElement: """Create a parser element capable of parsing aperture blocks.""" - return pp.MatchFirst( - [ - self.ab_close, - self.ab_open, - ] + block = pp.Forward() + + aperture_block = ( + ( + self.ab_open.set_results_name("open") + + block + + self.ab_close.set_results_name("close") + ) + .set_name("ApertureBlock") + .set_parse_action(self.make_unpack_callback(AB)) ) + block <<= pp.ZeroOrMore( + pp.MatchFirst( + [ + self.attribute, + self.g_codes, + self.load_commands, + self.m_codes, # Technically not valid according to standard. + self.properties, + self.d_codes_standalone, + # Other aperture altering commands. + self.macro, + self.step_repeat, + self.add_aperture, + ] + ) + ).set_results_name("nodes") + + return aperture_block + @pp.cached_property def ab_open(self) -> pp.ParserElement: """Create a parser element capable of parsing AB-open.""" @@ -302,6 +322,7 @@ def ab_close(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(ABclose)) ) + @pp.cached_property def macro(self) -> pp.ParserElement: """Create a parser element capable of parsing macros.""" return self.am_open + self.primitives + self.am_close @@ -322,6 +343,7 @@ def am_close(self) -> pp.ParserElement: self.make_unpack_callback(AMclose) ) + @pp.cached_property def step_repeat(self) -> pp.ParserElement: """Create a parser element capable of parsing step repeats.""" return pp.MatchFirst( @@ -355,6 +377,7 @@ def sr_close(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(SRclose)) ) + @pp.cached_property def add_aperture(self) -> pp.ParserElement: """Create a parser element capable of parsing add-aperture commands.""" return pp.MatchFirst( @@ -447,6 +470,7 @@ def _x(self) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██ ██ ██ ██ ██████ ██████ ██ ███████ + @pp.cached_property def attribute(self) -> pp.ParserElement: """Create a parser element capable of parsing attributes.""" return pp.MatchFirst( @@ -980,7 +1004,17 @@ def _to(self) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██████ █████ ███████ ██████ ███████ ███████ - def d_codes(self, *, is_standalone: bool) -> pp.ParserElement: + @pp.cached_property + def d_codes_standalone(self) -> pp.ParserElement: + """Create a parser element capable of parsing standalone D-codes.""" + return self._d_codes(is_standalone=True) + + @pp.cached_property + def d_codes_non_standalone(self) -> pp.ParserElement: + """Create a parser element capable of parsing standalone D-codes.""" + return self._d_codes(is_standalone=False) + + def _d_codes(self, *, is_standalone: bool) -> pp.ParserElement: """Create a parser element capable of parsing D-codes. `is_standalone` parameter is used to determine if the D-code is standalone, ie. @@ -1052,6 +1086,7 @@ def _d03(self, *, is_standalone: bool) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██████ █████ ███████ ██████ ███████ ███████ + @pp.cached_property def g_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing G-codes.""" g04_comment = self._command( @@ -1063,8 +1098,6 @@ def g_codes(self) -> pp.ParserElement: else: g04_comment = g04_comment.set_parse_action(self.make_unpack_callback(G04)) - non_standalone_d_codes = self.d_codes(is_standalone=False) - def _standalone(cls: Type[Node]) -> pp.ParserElement: code = int(cls.__qualname__.lstrip("G")) return ( @@ -1084,7 +1117,7 @@ def _non_standalone(cls: Type[Node]) -> pp.ParserElement: ) .set_name(cls.__qualname__) .set_parse_action(self.make_unpack_callback(cls, is_standalone=False)) - ) + non_standalone_d_codes + ) + self.d_codes_non_standalone return pp.MatchFirst( [ @@ -1138,6 +1171,7 @@ def _non_standalone(cls: Type[Node]) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # noqa: E501 # ██████ ███████ ██ ██ ██████ █████ ███████ ██ ██ ██ ██ ██ ██ ██ ████ ██████ ███████ # noqa: E501 + @pp.cached_property def load_commands(self) -> pp.ParserElement: """Create a parser element capable of parsing Load-commands.""" return pp.MatchFirst([self.ln(), self.lp(), self.lr(), self.ls(), self.lm()]) @@ -1199,6 +1233,7 @@ def lm(self) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ █████ ███████ ██████ ███████ ███████ + @pp.cached_property def m_codes(self) -> pp.ParserElement: """Create a parser element capable of parsing M-codes.""" return pp.MatchFirst( @@ -1576,6 +1611,7 @@ def primitive( # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██████ ██ ███████ ██ ██ ██ ██ ███████ ███████ + @pp.cached_property def properties(self) -> pp.ParserElement: """Create a parser element capable of parsing Properties-commands.""" return pp.MatchFirst( @@ -1660,7 +1696,7 @@ def as_(self) -> pp.ParserElement: pp.Literal("AS") + pp.one_of(["AXBY", "AYBX"]).set_results_name("correspondence") ) - .set_parse_action(self.make_unpack_callback(OF)) + .set_parse_action(self.make_unpack_callback(AS)) .set_name("OF") ) From 4f63df0f9a34c9fe2d9351fa2882d83a44cde8e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Mon, 19 Aug 2024 22:20:38 +0200 Subject: [PATCH 61/91] Add AM class for aperture macro wrapping --- src/pygerber/gerberx3/ast/nodes/aperture/AM.py | 6 +++++- src/pygerber/gerberx3/ast/visitor.py | 4 ++++ src/pygerber/gerberx3/parser/pyparsing/grammar.py | 11 ++++++++++- 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py index 441b8999..ee02f567 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py @@ -4,6 +4,8 @@ from typing import TYPE_CHECKING, Callable +from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose +from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen from pygerber.gerberx3.ast.nodes.base import Node if TYPE_CHECKING: @@ -15,7 +17,9 @@ class AM(Node): """Represents AM Gerber extended command.""" - nodes: list[Node] + open: AMopen + primitives: list[Node] + close: AMclose def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 50ee1168..a7f9a699 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -171,6 +171,10 @@ def on_ad_macro(self, node: ADmacro) -> None: def on_am(self, node: AM) -> None: """Handle `AM` root node.""" + node.open.visit(self) + for primitive in node.primitives: + primitive.visit(self) + node.close.visit(self) def on_am_close(self, node: AMclose) -> None: """Handle `AMclose` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index b1cda47c..40753820 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -15,6 +15,7 @@ ADO, ADP, ADR, + AM, AS, D01, D02, @@ -325,7 +326,15 @@ def ab_close(self) -> pp.ParserElement: @pp.cached_property def macro(self) -> pp.ParserElement: """Create a parser element capable of parsing macros.""" - return self.am_open + self.primitives + self.am_close + return ( + ( + self.am_open.set_results_name("open") + + self.primitives.set_results_name("primitives") + + self.am_close.set_results_name("close") + ) + .set_name("MacroDefinition") + .set_parse_action(self.make_unpack_callback(AM)) + ) @pp.cached_property def am_open(self) -> pp.ParserElement: From 3939c6fb18a81fe0578e23391aa85d219350e621 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 20 Aug 2024 00:01:42 +0200 Subject: [PATCH 62/91] Fix nested AB parsing --- .../gerberx3/ast/state_tracking_visitor.py | 203 ++++++++++++++++++ .../gerberx3/parser/pyparsing/grammar.py | 40 ++-- test/assets/gerberx3/ucamco/4.11.4/source.grb | 6 +- 3 files changed, 225 insertions(+), 24 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/state_tracking_visitor.py diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py new file mode 100644 index 00000000..653831a8 --- /dev/null +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -0,0 +1,203 @@ +"""`pygerber.gerberx3.node_visitor` contains definition of `StateTrackingVisitor` +class. +""" + +from __future__ import annotations + +from enum import Enum +from typing import Any, Callable, Optional + +from pydantic import BaseModel, Field + +from pygerber.gerberx3.ast.nodes import AB, AD, AM +from pygerber.gerberx3.ast.nodes.enums import ( + AxisCorrespondence, + CoordinateMode, + ImagePolarity, + Mirroring, + Zeros, +) +from pygerber.gerberx3.ast.nodes.properties.MO import UnitMode +from pygerber.gerberx3.ast.nodes.types import ApertureIdStr, Double, PackedCoordinateStr +from pygerber.gerberx3.ast.visitor import AstVisitor + + +class _StateModel(BaseModel): + """Base class for all models representing parts of Gerber state.""" + + +class CoordinateFormat(_StateModel): + """Coordinate format information.""" + + zeros: Zeros + coordinate_mode: CoordinateMode + + x_integral: int + x_decimal: int + + y_integral: int + y_decimal: int + + def __init__(self, **kwargs: Any) -> None: + super().__init__(**kwargs) + + if self.zeros == Zeros.SKIP_LEADING: + 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] + elif self.zeros == Zeros.SKIP_TRAILING: + self.unpack_x = self._unpack_skip_trailing(self.x_integral, self.x_decimal) # type: ignore[method-assign] + self.unpack_y = self._unpack_skip_trailing(self.y_integral, self.y_decimal) # type: ignore[method-assign] + else: + msg = f"Unknown zeros mode: {self.zeros}" + raise ValueError(msg) + + def unpack_x(self, coordinate: PackedCoordinateStr, /) -> Double: # noqa: ARG002 + """Unpack X coordinate using the current coordinate format.""" + msg = "Coordinate format was not properly set." + raise NotImplementedError(msg) + + def unpack_y(self, coordinate: PackedCoordinateStr, /) -> Double: # noqa: ARG002 + """Unpack X coordinate using the current coordinate format.""" + msg = "Coordinate format was not properly set." + raise NotImplementedError(msg) + + def _unpack_skip_trailing( + self, integer: int, decimal: int + ) -> Callable[[PackedCoordinateStr], Double]: + def _(coordinate: PackedCoordinateStr) -> Double: + padded_coordinate = coordinate.ljust((integer + decimal), "0") + integer_value_str = padded_coordinate[:integer] + decimal_value_str = padded_coordinate[integer:] + + return float(f"{integer_value_str}.{decimal_value_str}") + + return _ + + def _unpack_skip_leading( + self, integer: int, decimal: int + ) -> Callable[[PackedCoordinateStr], Double]: + def _(coordinate: PackedCoordinateStr) -> Double: + padded_coordinate = coordinate.rjust((integer + decimal), "0") + integer_value_str = padded_coordinate[:integer] + decimal_value_str = padded_coordinate[integer:] + + return float(f"{integer_value_str}.{decimal_value_str}") + + return _ + + +class Attributes(_StateModel): + """Attributes of the Gerber file.""" + + aperture_attributes: dict[str, Any] = Field(default_factory=dict) + + file_attributes: dict[str, Any] = Field(default_factory=dict) + + object_attributes: dict[str, Any] = Field(default_factory=dict) + + image_polarity: ImagePolarity = Field(default=None) + """The name of the image. (Spec reference: 8.1.3)""" + + file_name: Optional[str] = Field(default=None) + """The name of the file. (Spec reference: 8.1.6)""" + + axis_correspondence: AxisCorrespondence = Field(default=AxisCorrespondence.AX_BY) + """The axis correspondence. (Spec reference: 8.1.2)""" + + +class Transform(_StateModel): + """Aperture transformations.""" + + mirroring: Mirroring = Field(default=Mirroring.NONE) + """Aperture mirroring set with LM command. (Spec reference: 4.9.3)""" + + rotation: Double = Field(default=0.0) + """Aperture rotation set with LR command. (Spec reference: 4.9.4)""" + + scaling: Double = Field(default=1.0) + """Aperture scaling set with LS command. (Spec reference: 4.9.5)""" + + +class PlotMode(Enum): + """Plot mode of the Gerber file.""" + + LINEAR = "LINEAR" + """Linear interpolation mode.""" + + ARC = "ARC" + """Clockwise circular interpolation mode.""" + + CCW_ARC = "CCW_ARC" + """Counter-clockwise circular interpolation mode.""" + + +class ApertureStorage(_StateModel): + """Storage for apertures.""" + + apertures: dict[ApertureIdStr, AD] = Field(default_factory=dict) + """Aperture storage.""" + + blocks: dict[str, AB] = Field(default_factory=dict) + """Block aperture storage.""" + + macros: dict[str, AM] = Field(default_factory=dict) + """Macro definition storage.""" + + +class MacroContext(_StateModel): + """Macro evaluation context.""" + + +class State(_StateModel): + """Internal state of the compiler.""" + + unit_mode: UnitMode = Field(default=UnitMode.METRIC) + """The draw units used for the Gerber file. (Spec reference: 4.2.1)""" + + coordinate_format: Optional[CoordinateFormat] = Field(default=None) + """The coordinate format specification, including the number of decimals. + (Spec reference: 4.2.2)""" + + plot_mode: PlotMode = Field(default=PlotMode.LINEAR) + """The plot mode. (Spec reference 4.7)""" + + current_aperture_id: Optional[ApertureIdStr] = Field(default=None) + """The ID of currently selected aperture. (Spec reference: 8.6)""" + + current_x: Double = Field(default=0.0) + """Current X coordinate value.""" + + current_y: Double = Field(default=0.0) + """Current Y coordinate value.""" + + coordinate_x: Double = Field(default=0.0) + """Last X coordinate value set by CoordinateX node.""" + + coordinate_y: Double = Field(default=0.0) + """Last Y coordinate value set by CoordinateY node.""" + + coordinate_i: Double = Field(default=0.0) + """Last I coordinate value set by CoordinateI node.""" + + coordinate_j: Double = Field(default=0.0) + """Last J coordinate value set by CoordinateJ node.""" + + transform: Transform = Field(default_factory=lambda: Transform) + """Current aperture transformation parameters.""" + + apertures: ApertureStorage + + macro_context: MacroContext = Field(default_factory=lambda: MacroContext) + + +class StateTrackingVisitor(AstVisitor): + """`StateTrackingVisitor` is a visitor class that tracks the internal state + defined in the GerberX3 specification and modifies it according to Gerber + commands. + + Additionally, it defines a set of higher level callback methods that extend + interface of `AstVisitor` class. + """ + + def __init__(self) -> None: + super().__init__() diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 40753820..d580ccad 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -274,35 +274,35 @@ def aperture(self) -> pp.ParserElement: def aperture_block(self) -> pp.ParserElement: """Create a parser element capable of parsing aperture blocks.""" - block = pp.Forward() + aperture_block = pp.Forward() - aperture_block = ( + aperture_block <<= ( ( self.ab_open.set_results_name("open") - + block + + pp.ZeroOrMore( + pp.MatchFirst( + [ + self.attribute, + self.g_codes, + self.load_commands, + # Technically not valid according to standard. + self.m_codes, + self.properties, + self.d_codes_standalone, + # Other aperture altering commands. + self.macro, + self.step_repeat, + self.add_aperture, + aperture_block, + ] + ) + ).set_results_name("nodes") + self.ab_close.set_results_name("close") ) .set_name("ApertureBlock") .set_parse_action(self.make_unpack_callback(AB)) ) - block <<= pp.ZeroOrMore( - pp.MatchFirst( - [ - self.attribute, - self.g_codes, - self.load_commands, - self.m_codes, # Technically not valid according to standard. - self.properties, - self.d_codes_standalone, - # Other aperture altering commands. - self.macro, - self.step_repeat, - self.add_aperture, - ] - ) - ).set_results_name("nodes") - return aperture_block @pp.cached_property diff --git a/test/assets/gerberx3/ucamco/4.11.4/source.grb b/test/assets/gerberx3/ucamco/4.11.4/source.grb index a0daf38b..d2a450d0 100644 --- a/test/assets/gerberx3/ucamco/4.11.4/source.grb +++ b/test/assets/gerberx3/ucamco/4.11.4/source.grb @@ -18,11 +18,9 @@ G04 Define block aperture 100, consisting of two draws and a round dot* D11* X-3556000Y17605375D03* %AB*% -G04 Define block aperture 102, consisting of 2x3 flashes of aperture 101 -and 1 flash of D12* +G04 Define block aperture 102, consisting of 2x3 flashes of aperture 101 and 1 flash of D12* %ABD102*% - G04 Define nested block aperture 101, consisting of 2x2 flashes of - aperture 100* + G04 Define nested block aperture 101, consisting of 2x2 flashes of aperture 100* %ABD101*% D100* X0Y0D03* From e8168fc7a5864a04f0cd3f00479434eb75e4f7a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 20 Aug 2024 00:56:06 +0200 Subject: [PATCH 63/91] Add SR class for wrapping whole SR blocks --- src/pygerber/gerberx3/ast/nodes/__init__.py | 2 + .../gerberx3/ast/nodes/aperture/AB_open.py | 3 +- .../gerberx3/ast/nodes/aperture/AD.py | 3 +- .../gerberx3/ast/nodes/aperture/SR.py | 32 +++++++++ src/pygerber/gerberx3/ast/visitor.py | 8 +++ .../gerberx3/parser/pyparsing/grammar.py | 69 +++++++++++++------ test/gerberx3/test_ast/test_ast_visitor.py | 12 ++-- 7 files changed, 100 insertions(+), 29 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/aperture/SR.py diff --git a/src/pygerber/gerberx3/ast/nodes/__init__.py b/src/pygerber/gerberx3/ast/nodes/__init__.py index 829f2b2b..f3e56375 100644 --- a/src/pygerber/gerberx3/ast/nodes/__init__.py +++ b/src/pygerber/gerberx3/ast/nodes/__init__.py @@ -16,6 +16,7 @@ from pygerber.gerberx3.ast.nodes.aperture.AM import AM from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen +from pygerber.gerberx3.ast.nodes.aperture.SR import SR from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen from pygerber.gerberx3.ast.nodes.attribute.TA import ( @@ -141,6 +142,7 @@ "ADR", "AMclose", "AMopen", + "SR", "SRclose", "SRopen", "TA_AperFunction", diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py index e1b0cf89..bdac0b81 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import ApertureIdStr if TYPE_CHECKING: from typing_extensions import Self @@ -15,7 +16,7 @@ class ABopen(Node): """Represents AB Gerber extended command.""" - aperture_identifier: str + aperture_identifier: ApertureIdStr def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py index 6b1519ed..a25cb091 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py @@ -3,9 +3,10 @@ from __future__ import annotations from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.types import ApertureIdStr class AD(Node): """Common base class for all commands adding new apertures.""" - aperture_identifier: str + aperture_identifier: ApertureIdStr diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR.py new file mode 100644 index 00000000..a3adbd34 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR.py @@ -0,0 +1,32 @@ +"""`pygerber.nodes.aperture.SR` module contains definition of `SR` class.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose +from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen +from pygerber.gerberx3.ast.nodes.base import Node + +if TYPE_CHECKING: + from typing_extensions import Self + + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class SR(Node): + """Represents SR Gerber extended command.""" + + open: SRopen + nodes: list[Node] + close: SRclose + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_sr(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_sr diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index a7f9a699..0d1eeedf 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -47,6 +47,7 @@ MO, OF, SF, + SR, TA, TD, TF, @@ -182,6 +183,13 @@ def on_am_close(self, node: AMclose) -> None: def on_am_open(self, node: AMopen) -> None: """Handle `AMopen` node.""" + def on_sr(self, node: SR) -> None: + """Handle `SR` root node.""" + node.open.visit(self) + for inner_node in node.nodes: + inner_node.visit(self) + node.close.visit(self) + def on_sr_close(self, node: SRclose) -> None: """Handle `SRclose` node.""" diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index d580ccad..4da18200 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -50,6 +50,7 @@ MO, OF, SF, + SR, TD, TF_MD5, TO_C, @@ -144,6 +145,9 @@ def __init__( self.enable_debug = enable_debug self.optimization = optimization + self.step_repeat_forward = pp.Forward() + self.aperture_block_forward = pp.Forward() + def build(self) -> pp.ParserElement: """Build the grammar.""" @@ -154,13 +158,13 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: pp.OneOrMore( pp.MatchFirst( [ - self.aperture(), - self.attribute, + self.d_codes_standalone, self.g_codes, self.load_commands, - self.m_codes, + self.aperture(), + self.attribute, self.properties, - self.d_codes_standalone, + self.m_codes, ] ) ) @@ -265,35 +269,35 @@ def aperture(self) -> pp.ParserElement: """Create a parser element capable of parsing apertures.""" return pp.MatchFirst( [ - self.aperture_block(), + self.aperture_block, self.macro, self.step_repeat, self.add_aperture, ] ) + @pp.cached_property def aperture_block(self) -> pp.ParserElement: """Create a parser element capable of parsing aperture blocks.""" - aperture_block = pp.Forward() + self.aperture_block_forward = pp.Forward() - aperture_block <<= ( + self.aperture_block_forward <<= ( ( self.ab_open.set_results_name("open") + pp.ZeroOrMore( pp.MatchFirst( [ - self.attribute, + self.d_codes_standalone, self.g_codes, self.load_commands, - # Technically not valid according to standard. - self.m_codes, self.properties, - self.d_codes_standalone, - # Other aperture altering commands. - self.macro, - self.step_repeat, + self.attribute, self.add_aperture, - aperture_block, + self.step_repeat_forward, + self.aperture_block_forward, + self.macro, + # Technically not valid according to standard. + self.m_codes, ] ) ).set_results_name("nodes") @@ -303,7 +307,7 @@ def aperture_block(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(AB)) ) - return aperture_block + return self.aperture_block_forward @pp.cached_property def ab_open(self) -> pp.ParserElement: @@ -355,13 +359,36 @@ def am_close(self) -> pp.ParserElement: @pp.cached_property def step_repeat(self) -> pp.ParserElement: """Create a parser element capable of parsing step repeats.""" - return pp.MatchFirst( - [ - self.sr_close, - self.sr_open, - ] + self.step_and_repeat_block_forward = pp.Forward() + + self.step_and_repeat_block_forward <<= ( + ( + self.sr_open.set_results_name("open") + + pp.ZeroOrMore( + pp.MatchFirst( + [ + self.d_codes_standalone, + self.g_codes, + self.load_commands, + self.properties, + self.attribute, + self.add_aperture, + self.step_repeat_forward, + self.aperture_block_forward, + self.macro, + # Technically not valid according to standard. + self.m_codes, + ] + ) + ).set_results_name("nodes") + + self.sr_close.set_results_name("close") + ) + .set_name("StepAndRepeatBlock") + .set_parse_action(self.make_unpack_callback(SR)) ) + return self.step_and_repeat_block_forward + @pp.cached_property def sr_open(self) -> pp.ParserElement: """Create a parser element capable of parsing SR-open.""" diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 8d78cc39..dfa3a581 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -127,25 +127,25 @@ NODE_SAMPLES: Dict[Type[Node], Node] = { ABclose: ABclose(source="", location=0), - ABopen: ABopen(source="", location=0, aperture_identifier="D11"), + ABopen: ABopen(source="", location=0, aperture_identifier=ApertureIdStr("D11")), ADC: ADC( source="", location=0, - aperture_identifier="D11", + aperture_identifier=ApertureIdStr("D11"), diameter=0.1, hole_diameter=0.05, ), ADmacro: ADmacro( source="", location=0, - aperture_identifier="D11", + aperture_identifier=ApertureIdStr("D11"), name="macro", params=[1, 2], ), ADO: ADO( source="", location=0, - aperture_identifier="D11", + aperture_identifier=ApertureIdStr("D11"), width=0.1, height=0.05, hole_diameter=0.05, @@ -153,7 +153,7 @@ ADR: ADR( source="", location=0, - aperture_identifier="D11", + aperture_identifier=ApertureIdStr("D11"), width=0.1, height=0.05, hole_diameter=0.05, @@ -161,7 +161,7 @@ ADP: ADP( source="", location=0, - aperture_identifier="D11", + aperture_identifier=ApertureIdStr("D11"), outer_diameter=0.1, vertices=4, rotation=0.1, From 7866d07d2ca736a38d7e8d1118d895a3477cd3d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 20 Aug 2024 00:58:25 +0200 Subject: [PATCH 64/91] Add aperture creating commands to StateTrackingVisitor --- .../gerberx3/ast/state_tracking_visitor.py | 21 +++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 653831a8..34148711 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -137,7 +137,7 @@ class ApertureStorage(_StateModel): apertures: dict[ApertureIdStr, AD] = Field(default_factory=dict) """Aperture storage.""" - blocks: dict[str, AB] = Field(default_factory=dict) + blocks: dict[ApertureIdStr, AB] = Field(default_factory=dict) """Block aperture storage.""" macros: dict[str, AM] = Field(default_factory=dict) @@ -185,9 +185,11 @@ class State(_StateModel): transform: Transform = Field(default_factory=lambda: Transform) """Current aperture transformation parameters.""" - apertures: ApertureStorage + apertures: ApertureStorage = Field(default_factory=lambda: ApertureStorage) + """Container for different types of apertures.""" macro_context: MacroContext = Field(default_factory=lambda: MacroContext) + """Context used for macro evaluation.""" class StateTrackingVisitor(AstVisitor): @@ -201,3 +203,18 @@ class StateTrackingVisitor(AstVisitor): def __init__(self) -> None: super().__init__() + self.state = State() + + # Aperture + + def on_ab(self, node: AB) -> None: + """Handle `ABclose` node.""" + self.state.apertures.blocks[node.open.aperture_identifier] = node + + def on_ad(self, node: AD) -> None: + """Handle `AD` node.""" + self.state.apertures.apertures[node.aperture_identifier] = node + + def on_am(self, node: AM) -> None: + """Handle `AM` root node.""" + self.state.apertures.macros[node.open.name] = node From 18cde8dff60aa2d3c835cb96aaebb1aa55b12b5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Tue, 20 Aug 2024 01:51:43 +0200 Subject: [PATCH 65/91] Add attribute tracking logic to StateTrackingVisitor --- .../gerberx3/ast/nodes/attribute/TA.py | 25 ++++++ .../gerberx3/ast/nodes/attribute/TF.py | 50 +++++++++++ .../gerberx3/ast/nodes/attribute/TO.py | 85 +++++++++++++++++++ .../gerberx3/ast/state_tracking_visitor.py | 44 ++++++++-- .../test_nodes/test_state_tracking_visitor.py | 84 ++++++++++++++++++ 5 files changed, 281 insertions(+), 7 deletions(-) create mode 100644 test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py index c7705e44..027358d7 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py @@ -18,6 +18,11 @@ class TA(Node): """Represents TA Gerber extended command.""" + @property + def attribute_name(self) -> str: + """Get attribute name.""" + raise NotImplementedError + class TA_UserName(TA): # noqa: N801 """Represents TA Gerber extended command with user name.""" @@ -25,6 +30,11 @@ class TA_UserName(TA): # noqa: N801 user_name: str fields: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return self.user_name + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_user_name(self) @@ -42,6 +52,11 @@ class TA_AperFunction(TA): # noqa: N801 function: Optional[AperFunction] = Field(default=None) fields: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".AperFunction" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_aper_function(self) @@ -59,6 +74,11 @@ class TA_DrillTolerance(TA): # noqa: N801 plus_tolerance: Optional[float] = Field(default=None) minus_tolerance: Optional[float] = Field(default=None) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".DrillTolerance" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_drill_tolerance(self) @@ -80,6 +100,11 @@ class TA_FlashText(TA): # noqa: N801 size: Optional[str] = Field(default=None) comments: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".FlashText" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_ta_flash_text(self) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py index c8dd47e3..e32042b5 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -20,6 +20,11 @@ class TF(Node): """Represents TF Gerber extended command.""" + @property + def attribute_name(self) -> str: + """Get attribute name.""" + raise NotImplementedError + class TF_UserName(TF): # noqa: N801 """Represents TF Gerber extended command with user name.""" @@ -27,6 +32,11 @@ class TF_UserName(TF): # noqa: N801 user_name: str fields: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return self.user_name + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_user_name(self) @@ -44,6 +54,11 @@ class TF_Part(TF): # noqa: N801 part: Part fields: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".Part" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_part(self) @@ -61,6 +76,11 @@ class TF_FileFunction(TF): # noqa: N801 file_function: FileFunction fields: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".FileFunction" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_file_function(self) @@ -77,6 +97,11 @@ class TF_FilePolarity(TF): # noqa: N801 polarity: Literal["Positive", "Negative"] + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".FilePolarity" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_file_polarity(self) @@ -93,6 +118,11 @@ class TF_SameCoordinates(TF): # noqa: N801 identifier: Optional[str] = Field(default=None) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".SameCoordinates" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_same_coordinates(self) @@ -109,6 +139,11 @@ class TF_CreationDate(TF): # noqa: N801 creation_date: datetime.datetime + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CreationDate" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_creation_date(self) @@ -127,6 +162,11 @@ class TF_GenerationSoftware(TF): # noqa: N801 application: Optional[str] = Field(default=None) version: Optional[str] = Field(default=None) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".GenerationSoftware" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_generation_software(self) @@ -145,6 +185,11 @@ class TF_ProjectId(TF): # noqa: N801 guid: Optional[str] = Field(default=None) revision: Optional[str] = Field(default=None) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".ProjectId" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_project_id(self) @@ -164,6 +209,11 @@ class TF_MD5(TF): # noqa: N801 md5: str = Field(min_length=MD5_LENGTH_HEX, max_length=MD5_LENGTH_HEX) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".MD5" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_tf_md5(self) diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py index c99180fc..33114421 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py @@ -19,6 +19,11 @@ class TO(Node): """Represents TO Gerber extended command.""" + @property + def attribute_name(self) -> str: + """Get attribute name.""" + raise NotImplementedError + class TO_UserName(TO): # noqa: N801 """Represents TO Gerber extended command with user name.""" @@ -26,6 +31,11 @@ class TO_UserName(TO): # noqa: N801 user_name: str fields: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return self.user_name + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_user_name(self) @@ -42,6 +52,11 @@ class TO_N(TO): # noqa: N801 net_names: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".N" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_n(self) @@ -60,6 +75,11 @@ class TO_P(TO): # noqa: N801 number: str function: Optional[str] = Field(default=None) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".P" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_p(self) @@ -76,6 +96,11 @@ class TO_C(TO): # noqa: N801 refdes: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".C" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_c(self) @@ -92,6 +117,11 @@ class TO_CRot(TO): # noqa: N801 angle: Double + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CRot" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_crot(self) @@ -108,6 +138,11 @@ class TO_CMfr(TO): # noqa: N801 manufacturer: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CMfr" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cmfr(self) @@ -124,6 +159,11 @@ class TO_CMNP(TO): # noqa: N801 part_number: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CMNP" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cmnp(self) @@ -140,6 +180,11 @@ class TO_CVal(TO): # noqa: N801 value: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CVal" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cval(self) @@ -156,6 +201,11 @@ class TO_CMnt(TO): # noqa: N801 mount: Mount + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CMnt" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cmnt(self) @@ -172,6 +222,11 @@ class TO_CFtp(TO): # noqa: N801 footprint: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CFtp" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cftp(self) @@ -188,6 +243,11 @@ class TO_CPgN(TO): # noqa: N801 name: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CPgN" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cpgn(self) @@ -204,6 +264,11 @@ class TO_CPgD(TO): # noqa: N801 description: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CPgD" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_cpgd(self) @@ -220,6 +285,11 @@ class TO_CHgt(TO): # noqa: N801 height: Double + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CHgt" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_chgt(self) @@ -236,6 +306,11 @@ class TO_CLbN(TO): # noqa: N801 name: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CLbN" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_clbn(self) @@ -252,6 +327,11 @@ class TO_CLbD(TO): # noqa: N801 description: str + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CLbD" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_clbd(self) @@ -271,6 +351,11 @@ class TO_CSup(TO): # noqa: N801 other_suppliers: List[str] = Field(default_factory=list) + @property + def attribute_name(self) -> str: + """Get attribute name.""" + return ".CSup" + def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" visitor.on_to_csup(self) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 34148711..74b01c99 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -9,7 +9,7 @@ from pydantic import BaseModel, Field -from pygerber.gerberx3.ast.nodes import AB, AD, AM +from pygerber.gerberx3.ast.nodes import AB, AD, AM, TA, TD, TF, TO from pygerber.gerberx3.ast.nodes.enums import ( AxisCorrespondence, CoordinateMode, @@ -89,11 +89,14 @@ def _(coordinate: PackedCoordinateStr) -> Double: class Attributes(_StateModel): """Attributes of the Gerber file.""" - aperture_attributes: dict[str, Any] = Field(default_factory=dict) + aperture_attributes: dict[str, TA] = Field(default_factory=dict) + """Object attributes created with TA extended command.""" - file_attributes: dict[str, Any] = Field(default_factory=dict) + file_attributes: dict[str, TF] = Field(default_factory=dict) + """Object attributes created with TF extended command.""" - object_attributes: dict[str, Any] = Field(default_factory=dict) + object_attributes: dict[str, TO] = Field(default_factory=dict) + """Object attributes created with TO extended command.""" image_polarity: ImagePolarity = Field(default=None) """The name of the image. (Spec reference: 8.1.3)""" @@ -182,15 +185,18 @@ class State(_StateModel): coordinate_j: Double = Field(default=0.0) """Last J coordinate value set by CoordinateJ node.""" - transform: Transform = Field(default_factory=lambda: Transform) + transform: Transform = Field(default_factory=Transform) """Current aperture transformation parameters.""" - apertures: ApertureStorage = Field(default_factory=lambda: ApertureStorage) + apertures: ApertureStorage = Field(default_factory=ApertureStorage) """Container for different types of apertures.""" - macro_context: MacroContext = Field(default_factory=lambda: MacroContext) + macro_context: MacroContext = Field(default_factory=MacroContext) """Context used for macro evaluation.""" + attributes: Attributes = Field(default_factory=Attributes) + """Container for holding currently active attributes.""" + class StateTrackingVisitor(AstVisitor): """`StateTrackingVisitor` is a visitor class that tracks the internal state @@ -218,3 +224,27 @@ def on_ad(self, node: AD) -> None: def on_am(self, node: AM) -> None: """Handle `AM` root node.""" self.state.apertures.macros[node.open.name] = node + + # Attribute + + def on_ta(self, node: TA) -> None: + """Handle `TA_UserName` node.""" + self.state.attributes.aperture_attributes[node.attribute_name] = node + + def on_tf(self, node: TF) -> None: + """Handle `TF` node.""" + self.state.attributes.file_attributes[node.attribute_name] = node + + def on_to(self, node: TO) -> None: + """Handle `TO` node.""" + self.state.attributes.object_attributes[node.attribute_name] = node + + def on_td(self, node: TD) -> None: + """Handle `TD` node.""" + if node.name is None: + self.state.attributes.aperture_attributes.clear() + self.state.attributes.object_attributes.clear() + return + + self.state.attributes.aperture_attributes.pop(node.name, None) + self.state.attributes.object_attributes.pop(node.name, None) diff --git a/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py new file mode 100644 index 00000000..83b27225 --- /dev/null +++ b/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from pygerber.gerberx3.ast.nodes.attribute.TA import TA_AperFunction, TA_DrillTolerance +from pygerber.gerberx3.ast.nodes.attribute.TF import TF_FileFunction +from pygerber.gerberx3.ast.nodes.enums import AperFunction, FileFunction +from pygerber.gerberx3.ast.state_tracking_visitor import StateTrackingVisitor + + +def test_set_file_attribute() -> None: + """Test if file attribute assigned is performed correctly.""" + visitor = StateTrackingVisitor() + node = TF_FileFunction( + source="", + location=0, + file_function=FileFunction.Copper, + fields=["FileFunction", "External"], + ) + visitor.on_tf_file_function(node) + + +def test_set_aperture_attribute() -> None: + """Test if aperture attribute assigned is performed correctly.""" + visitor = StateTrackingVisitor() + + # Test plain assignment of TA_AperFunction + node = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaPad, + fields=[], + ) + visitor.on_ta_aper_function(node) + assert visitor.state.attributes.aperture_attributes[".AperFunction"] == node + + +def test_set_two_aperture_attribute() -> None: + """Test if aperture attribute assigned is performed correctly.""" + visitor = StateTrackingVisitor() + + # Test plain assignment of TA_AperFunction + node0 = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaPad, + fields=[], + ) + visitor.on_ta_aper_function(node0) + node1 = TA_DrillTolerance( + source="", + location=0, + plus_tolerance=0.1, + minus_tolerance=0.1, + ) + visitor.on_ta_drill_tolerance(node1) + assert visitor.state.attributes.aperture_attributes[".AperFunction"] == node0 + assert visitor.state.attributes.aperture_attributes[".DrillTolerance"] == node1 + + +def test_override_aperture_attribute() -> None: + """Test if overriding aperture attribute is performed correctly.""" + visitor = StateTrackingVisitor() + + # Set initial aperture attribute + initial_node = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaDrill, + fields=[], + ) + visitor.on_ta_aper_function(initial_node) + + # Override the aperture attribute + override_node = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaPad, + fields=[], + ) + visitor.on_ta_aper_function(override_node) + + # Verify that the attribute has been overridden + assert ( + visitor.state.attributes.aperture_attributes[".AperFunction"] == override_node + ) From c4de2f94b399732055e6170f97ee840273e9ea9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Wed, 21 Aug 2024 02:36:22 +0200 Subject: [PATCH 66/91] Add D01 handling to StateTrackingVisitor --- src/pygerber/gerberx3/ast/errors.py | 41 +++++ .../gerberx3/ast/state_tracking_visitor.py | 144 +++++++++++++++++- .../test_nodes/test_state_tracking_visitor.py | 97 +++++++++++- 3 files changed, 273 insertions(+), 9 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/errors.py diff --git a/src/pygerber/gerberx3/ast/errors.py b/src/pygerber/gerberx3/ast/errors.py new file mode 100644 index 00000000..d446f0a8 --- /dev/null +++ b/src/pygerber/gerberx3/ast/errors.py @@ -0,0 +1,41 @@ +"""`pygerber.gerberx3.ast.errors` module gathers errors raised by visitors.""" + +from __future__ import annotations + +from pygerber.gerberx3.ast.nodes.types import ApertureIdStr + + +class VisitorError(Exception): + """Base class for all errors raised by visitors.""" + + +class StateTrackingVisitorError(VisitorError): + """Base class for all errors raised by state tracking visitors.""" + + +class DirectADHandlerDispatchNotSupportedError(StateTrackingVisitorError): + """Raised when generic AD class is used to select aperture handler.""" + + def __init__(self) -> None: + super().__init__( + "AD class can not be used to select handler. Use subclass instead." + ) + + +class ApertureNotSelectedError(StateTrackingVisitorError): + """Raised when an aperture is not selected in the state tracking visitor.""" + + def __init__(self) -> None: + super().__init__( + "Aperture was not selected before attempt was made to use it to draw." + ) + + +class ApertureNotFoundError(VisitorError): + """Raised when an aperture is not found in the aperture dictionary.""" + + def __init__(self, aperture_number: ApertureIdStr) -> None: + self.aperture_number = aperture_number + super().__init__( + f"Aperture {aperture_number} not found in the aperture dictionary." + ) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 74b01c99..d6e8b880 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -9,16 +9,44 @@ from pydantic import BaseModel, Field -from pygerber.gerberx3.ast.nodes import AB, AD, AM, TA, TD, TF, TO +from pygerber.common.error import throw +from pygerber.gerberx3.ast.errors import ( + ApertureNotFoundError, + ApertureNotSelectedError, + DirectADHandlerDispatchNotSupportedError, +) +from pygerber.gerberx3.ast.nodes import ( + AB, + AD, + ADC, + ADO, + ADP, + ADR, + AM, + D01, + D02, + D03, + G01, + G02, + G03, + TA, + TD, + TF, + TO, + ADmacro, + ApertureIdStr, + Dnn, + Double, + PackedCoordinateStr, +) from pygerber.gerberx3.ast.nodes.enums import ( AxisCorrespondence, CoordinateMode, ImagePolarity, Mirroring, + UnitMode, Zeros, ) -from pygerber.gerberx3.ast.nodes.properties.MO import UnitMode -from pygerber.gerberx3.ast.nodes.types import ApertureIdStr, Double, PackedCoordinateStr from pygerber.gerberx3.ast.visitor import AstVisitor @@ -173,16 +201,16 @@ class State(_StateModel): current_y: Double = Field(default=0.0) """Current Y coordinate value.""" - coordinate_x: Double = Field(default=0.0) + coordinate_x: Optional[Double] = Field(default=None) """Last X coordinate value set by CoordinateX node.""" - coordinate_y: Double = Field(default=0.0) + coordinate_y: Optional[Double] = Field(default=None) """Last Y coordinate value set by CoordinateY node.""" - coordinate_i: Double = Field(default=0.0) + coordinate_i: Optional[Double] = Field(default=None) """Last I coordinate value set by CoordinateI node.""" - coordinate_j: Double = Field(default=0.0) + coordinate_j: Optional[Double] = Field(default=None) """Last J coordinate value set by CoordinateJ node.""" transform: Transform = Field(default_factory=Transform) @@ -197,6 +225,20 @@ class State(_StateModel): attributes: Attributes = Field(default_factory=Attributes) """Container for holding currently active attributes.""" + @property + def current_aperture(self) -> AD | AB: + """Get currently selected aperture.""" + if self.current_aperture_id is None: + raise ApertureNotSelectedError + + if self.current_aperture_id in self.apertures.apertures: + return self.apertures.apertures[self.current_aperture_id] + + if self.current_aperture_id in self.apertures.blocks: + return self.apertures.blocks[self.current_aperture_id] + + raise ApertureNotFoundError(self.current_aperture_id) + class StateTrackingVisitor(AstVisitor): """`StateTrackingVisitor` is a visitor class that tracks the internal state @@ -210,6 +252,19 @@ class StateTrackingVisitor(AstVisitor): def __init__(self) -> None: super().__init__() self.state = State() + self._on_d01_handler = self.on_draw_line + self._on_d03_handler_dispatch_table = { + AD: lambda *_: throw(DirectADHandlerDispatchNotSupportedError()), + ADC: self.on_flash_circle, + ADR: self.on_flash_rectangle, + ADO: self.on_flash_obround, + ADP: self.on_flash_polygon, + ADmacro: self.on_flash_macro, + AB: self.on_flash_block, + } + self._on_d03_handler: Callable[[D03, AD | AB], None] = lambda *_: throw( # type: ignore[unreachable] + ApertureNotSelectedError() + ) # Aperture @@ -248,3 +303,78 @@ def on_td(self, node: TD) -> None: self.state.attributes.aperture_attributes.pop(node.name, None) self.state.attributes.object_attributes.pop(node.name, None) + + def on_d01(self, node: D01) -> None: + """Handle `D01` node.""" + super().on_d01(node) + self._on_d01_handler(node) + self._update_coordinates() + + def on_draw_line(self, node: D01) -> None: + """Handle `D01` node in linear interpolation mode.""" + + def on_draw_cw_arc(self, node: D01) -> None: + """Handle `D01` node in clockwise circular interpolation mode.""" + + def on_draw_ccw_arc(self, node: D01) -> None: + """Handle `D01` node in counter-clockwise circular interpolation.""" + + def _update_coordinates(self) -> None: + if self.state.coordinate_x is not None: + self.state.current_x = self.state.coordinate_x + if self.state.coordinate_y is not None: + self.state.current_y = self.state.coordinate_y + + def on_d02(self, node: D02) -> None: + """Handle `D02` node.""" + super().on_d02(node) + self._update_coordinates() + + def on_d03(self, node: D03) -> None: + """Handle `D03` node.""" + super().on_d03(node) + self._on_d03_handler(node, self._current_aperture) + self._update_coordinates() + + def on_flash_circle(self, node: D03, aperture: ADC) -> None: + """Handle `D03` node with `ADC` aperture.""" + + def on_flash_rectangle(self, node: D03, aperture: ADR) -> None: + """Handle `D03` node with `ADR` aperture.""" + + def on_flash_obround(self, node: D03, aperture: ADO) -> None: + """Handle `D03` node with `ADO` aperture.""" + + def on_flash_polygon(self, node: D03, aperture: ADP) -> None: + """Handle `D03` node with `ADP` aperture.""" + + def on_flash_macro(self, node: D03, aperture: ADmacro) -> None: + """Handle `D03` node with `ADM` aperture.""" + + def on_flash_block(self, node: D03, aperture: AB) -> None: + """Handle `D03` node with `AB` aperture.""" + + def on_dnn(self, node: Dnn) -> None: + """Handle `Dnn` node.""" + self.state.current_aperture_id = node.aperture_id + self._current_aperture = self.state.current_aperture + self._on_d03_handler = self._on_d03_handler_dispatch_table[ # type: ignore[assignment] + type(self._current_aperture) + ] + + # G codes + + def on_g01(self, node: G01) -> None: + """Handle `G01` node.""" + super().on_g01(node) + self._on_d01_handler = self.on_draw_line + + def on_g02(self, node: G02) -> None: + """Handle `G02` node.""" + super().on_g02(node) + self._on_d01_handler = self.on_draw_cw_arc + + def on_g03(self, node: G03) -> None: + """Handle `G03` node.""" + super().on_g03(node) + self._on_d01_handler = self.on_draw_ccw_arc diff --git a/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py index 83b27225..37fe3e1d 100644 --- a/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py @@ -1,7 +1,21 @@ from __future__ import annotations -from pygerber.gerberx3.ast.nodes.attribute.TA import TA_AperFunction, TA_DrillTolerance -from pygerber.gerberx3.ast.nodes.attribute.TF import TF_FileFunction +from unittest.mock import MagicMock + +from pygerber.gerberx3.ast.nodes import ( + D01, + G01, + G02, + G03, + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, + PackedCoordinateStr, + TA_AperFunction, + TA_DrillTolerance, + TF_FileFunction, +) from pygerber.gerberx3.ast.nodes.enums import AperFunction, FileFunction from pygerber.gerberx3.ast.state_tracking_visitor import StateTrackingVisitor @@ -82,3 +96,82 @@ def test_override_aperture_attribute() -> None: assert ( visitor.state.attributes.aperture_attributes[".AperFunction"] == override_node ) + + +def test_d02_draw_linear() -> None: + """Test if D02 command is handled correctly.""" + visitor = StateTrackingVisitor() + visitor.on_draw_line = MagicMock() # type: ignore[method-assign] + visitor.on_draw_cw_arc = MagicMock() # type: ignore[method-assign] + visitor.on_draw_ccw_arc = MagicMock() # type: ignore[method-assign] + + d_code = D01( + source="", + location=0, + x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), + ) + g_code = G01( + source="", + location=0, + ) + visitor.on_g01(g_code) + visitor.on_d01(d_code) + + assert visitor.on_draw_line.called + assert not visitor.on_draw_cw_arc.called + assert not visitor.on_draw_ccw_arc.called + + +def test_d02_draw_cw_arc() -> None: + """Test if D02 command is handled correctly.""" + visitor = StateTrackingVisitor() + visitor.on_draw_line = MagicMock() # type: ignore[method-assign] + visitor.on_draw_cw_arc = MagicMock() # type: ignore[method-assign] + visitor.on_draw_ccw_arc = MagicMock() # type: ignore[method-assign] + + d_code = D01( + source="", + location=0, + x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), + i=CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), + j=CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), + ) + g_code = G02( + source="", + location=0, + ) + visitor.on_g02(g_code) + visitor.on_d01(d_code) + + assert not visitor.on_draw_line.called + assert visitor.on_draw_cw_arc.called + assert not visitor.on_draw_ccw_arc.called + + +def test_d02_draw_ccw_arc() -> None: + """Test if D02 command is handled correctly.""" + visitor = StateTrackingVisitor() + visitor.on_draw_line = MagicMock() # type: ignore[method-assign] + visitor.on_draw_cw_arc = MagicMock() # type: ignore[method-assign] + visitor.on_draw_ccw_arc = MagicMock() # type: ignore[method-assign] + + d_code = D01( + source="", + location=0, + x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), + i=CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), + j=CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), + ) + g_code = G03( + source="", + location=0, + ) + visitor.on_g03(g_code) + visitor.on_d01(d_code) + + assert not visitor.on_draw_line.called + assert not visitor.on_draw_cw_arc.called + assert visitor.on_draw_ccw_arc.called From 2b77f5e2c69339859d98bd7f1e7440462aff7c77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 22 Aug 2024 02:47:58 +0200 Subject: [PATCH 67/91] Fix poe calls + add pytest_mock --- poetry.lock | 19 ++++++++++++++++++- pyproject.toml | 8 ++++---- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/poetry.lock b/poetry.lock index 91089b3a..3aa04333 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2319,6 +2319,23 @@ pygls = ">=1.1.0" pytest = "*" pytest-asyncio = ">=0.23" +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + [[package]] name = "pytest-xdist" version = "3.6.1" @@ -3137,4 +3154,4 @@ svg = ["drawsvg"] [metadata] lock-version = "2.0" python-versions = "^3.8,<3.13" -content-hash = "b626484dbfb7283f1967797f070661aa8b38e697a54f6a18933b78defb999273" +content-hash = "af0cc411e6b0dc0e002c5cea763bef80a823313f471c0311fca6c206327a8802" diff --git a/pyproject.toml b/pyproject.toml index 80da9301..6c715c55 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ pytest-cov = ">=4.1,<6.0" pytest-lsp = "^0.4.2" pytest-asyncio = "^0.23.7" +pytest-mock = "^3.14.0" [tool.poetry.group.docs] optional = true @@ -113,23 +114,22 @@ pygerber_language_server = "pygerber.gerberx3.language_server.__main__:main" # ------------------------------------------------------------------------------------- # git hooks install-hooks = [ - { cmd = "poetry install --sync --with=docs --extras=language-server --extras=svg --no-ansi" }, + { cmd = "poetry install --sync --with=docs --extras=all --no-ansi" }, { cmd = "poetry run python -m scripts.install_hooks" }, { cmd = "poetry run pre-commit install --install-hooks --overwrite" }, ] # ------------- # hook triggers run-code-quality-checks = [ - { cmd = "poetry install --sync --with=docs --extras=language-server --extras=svg --no-ansi" }, + { cmd = "poetry install --sync --with=docs --extras=all --no-ansi" }, { cmd = "poetry run pre-commit run --all-files -v" }, ] # ------------------------------------------------------------------------------------- run-unit-tests = [ - { cmd = "poetry install --sync --with=docs --extras=language-server --extras=svg --no-ansi" }, + { cmd = "poetry install --sync --with=docs --extras=all --no-ansi" }, { cmd = "poetry run pytest --log-level=DEBUG -s -n logical --cov=pygerber --cov-report=term-missing:skip-covered" }, ] run-type-checks = [ - { cmd = "poetry install --sync --with=docs --extras=language-server --extras=svg --no-ansi" }, { cmd = "poetry run mypy --config-file=pyproject.toml src/pygerber/ test/" }, ] From 362048eb71d7308aa2f0234b8a2b2d91accab6c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 22 Aug 2024 02:50:25 +0200 Subject: [PATCH 68/91] Add tests for aperture callbacks --- .../test_nodes/test_state_tracking_visitor.py | 177 -------- .../test_ast/test_state_tracking_visitor.py | 380 ++++++++++++++++++ 2 files changed, 380 insertions(+), 177 deletions(-) delete mode 100644 test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py create mode 100644 test/gerberx3/test_ast/test_state_tracking_visitor.py diff --git a/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py deleted file mode 100644 index 37fe3e1d..00000000 --- a/test/gerberx3/test_ast/test_nodes/test_state_tracking_visitor.py +++ /dev/null @@ -1,177 +0,0 @@ -from __future__ import annotations - -from unittest.mock import MagicMock - -from pygerber.gerberx3.ast.nodes import ( - D01, - G01, - G02, - G03, - CoordinateI, - CoordinateJ, - CoordinateX, - CoordinateY, - PackedCoordinateStr, - TA_AperFunction, - TA_DrillTolerance, - TF_FileFunction, -) -from pygerber.gerberx3.ast.nodes.enums import AperFunction, FileFunction -from pygerber.gerberx3.ast.state_tracking_visitor import StateTrackingVisitor - - -def test_set_file_attribute() -> None: - """Test if file attribute assigned is performed correctly.""" - visitor = StateTrackingVisitor() - node = TF_FileFunction( - source="", - location=0, - file_function=FileFunction.Copper, - fields=["FileFunction", "External"], - ) - visitor.on_tf_file_function(node) - - -def test_set_aperture_attribute() -> None: - """Test if aperture attribute assigned is performed correctly.""" - visitor = StateTrackingVisitor() - - # Test plain assignment of TA_AperFunction - node = TA_AperFunction( - source="", - location=0, - function=AperFunction.ViaPad, - fields=[], - ) - visitor.on_ta_aper_function(node) - assert visitor.state.attributes.aperture_attributes[".AperFunction"] == node - - -def test_set_two_aperture_attribute() -> None: - """Test if aperture attribute assigned is performed correctly.""" - visitor = StateTrackingVisitor() - - # Test plain assignment of TA_AperFunction - node0 = TA_AperFunction( - source="", - location=0, - function=AperFunction.ViaPad, - fields=[], - ) - visitor.on_ta_aper_function(node0) - node1 = TA_DrillTolerance( - source="", - location=0, - plus_tolerance=0.1, - minus_tolerance=0.1, - ) - visitor.on_ta_drill_tolerance(node1) - assert visitor.state.attributes.aperture_attributes[".AperFunction"] == node0 - assert visitor.state.attributes.aperture_attributes[".DrillTolerance"] == node1 - - -def test_override_aperture_attribute() -> None: - """Test if overriding aperture attribute is performed correctly.""" - visitor = StateTrackingVisitor() - - # Set initial aperture attribute - initial_node = TA_AperFunction( - source="", - location=0, - function=AperFunction.ViaDrill, - fields=[], - ) - visitor.on_ta_aper_function(initial_node) - - # Override the aperture attribute - override_node = TA_AperFunction( - source="", - location=0, - function=AperFunction.ViaPad, - fields=[], - ) - visitor.on_ta_aper_function(override_node) - - # Verify that the attribute has been overridden - assert ( - visitor.state.attributes.aperture_attributes[".AperFunction"] == override_node - ) - - -def test_d02_draw_linear() -> None: - """Test if D02 command is handled correctly.""" - visitor = StateTrackingVisitor() - visitor.on_draw_line = MagicMock() # type: ignore[method-assign] - visitor.on_draw_cw_arc = MagicMock() # type: ignore[method-assign] - visitor.on_draw_ccw_arc = MagicMock() # type: ignore[method-assign] - - d_code = D01( - source="", - location=0, - x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), - ) - g_code = G01( - source="", - location=0, - ) - visitor.on_g01(g_code) - visitor.on_d01(d_code) - - assert visitor.on_draw_line.called - assert not visitor.on_draw_cw_arc.called - assert not visitor.on_draw_ccw_arc.called - - -def test_d02_draw_cw_arc() -> None: - """Test if D02 command is handled correctly.""" - visitor = StateTrackingVisitor() - visitor.on_draw_line = MagicMock() # type: ignore[method-assign] - visitor.on_draw_cw_arc = MagicMock() # type: ignore[method-assign] - visitor.on_draw_ccw_arc = MagicMock() # type: ignore[method-assign] - - d_code = D01( - source="", - location=0, - x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), - i=CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), - j=CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), - ) - g_code = G02( - source="", - location=0, - ) - visitor.on_g02(g_code) - visitor.on_d01(d_code) - - assert not visitor.on_draw_line.called - assert visitor.on_draw_cw_arc.called - assert not visitor.on_draw_ccw_arc.called - - -def test_d02_draw_ccw_arc() -> None: - """Test if D02 command is handled correctly.""" - visitor = StateTrackingVisitor() - visitor.on_draw_line = MagicMock() # type: ignore[method-assign] - visitor.on_draw_cw_arc = MagicMock() # type: ignore[method-assign] - visitor.on_draw_ccw_arc = MagicMock() # type: ignore[method-assign] - - d_code = D01( - source="", - location=0, - x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), - i=CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), - j=CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), - ) - g_code = G03( - source="", - location=0, - ) - visitor.on_g03(g_code) - visitor.on_d01(d_code) - - assert not visitor.on_draw_line.called - assert not visitor.on_draw_cw_arc.called - assert visitor.on_draw_ccw_arc.called diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py new file mode 100644 index 00000000..e9d91136 --- /dev/null +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -0,0 +1,380 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING + +import pytest + +from pygerber.gerberx3.ast.nodes import ( + AB, + ADC, + ADO, + ADP, + ADR, + AM, + D01, + D03, + G01, + G02, + G03, + ABclose, + ABopen, + ADmacro, + AMclose, + AMopen, + CoordinateI, + CoordinateJ, + CoordinateX, + CoordinateY, + Dnn, + PackedCoordinateStr, + TA_AperFunction, + TA_DrillTolerance, + TF_FileFunction, +) +from pygerber.gerberx3.ast.nodes.enums import AperFunction, FileFunction +from pygerber.gerberx3.ast.nodes.types import ApertureIdStr +from pygerber.gerberx3.ast.state_tracking_visitor import StateTrackingVisitor + +if TYPE_CHECKING: + from pytest_mock import MockerFixture + + +def test_set_file_attribute() -> None: + """Test if file attribute assigned is performed correctly.""" + visitor = StateTrackingVisitor() + node = TF_FileFunction( + source="", + location=0, + file_function=FileFunction.Copper, + fields=["FileFunction", "External"], + ) + visitor.on_tf_file_function(node) + + +def test_set_aperture_attribute() -> None: + """Test if aperture attribute assigned is performed correctly.""" + visitor = StateTrackingVisitor() + + # Test plain assignment of TA_AperFunction + node = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaPad, + fields=[], + ) + visitor.on_ta_aper_function(node) + assert visitor.state.attributes.aperture_attributes[".AperFunction"] == node + + +def test_set_two_aperture_attribute() -> None: + """Test if aperture attribute assigned is performed correctly.""" + visitor = StateTrackingVisitor() + + # Test plain assignment of TA_AperFunction + node0 = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaPad, + fields=[], + ) + visitor.on_ta_aper_function(node0) + node1 = TA_DrillTolerance( + source="", + location=0, + plus_tolerance=0.1, + minus_tolerance=0.1, + ) + visitor.on_ta_drill_tolerance(node1) + assert visitor.state.attributes.aperture_attributes[".AperFunction"] == node0 + assert visitor.state.attributes.aperture_attributes[".DrillTolerance"] == node1 + + +def test_override_aperture_attribute() -> None: + """Test if overriding aperture attribute is performed correctly.""" + visitor = StateTrackingVisitor() + + # Set initial aperture attribute + initial_node = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaDrill, + fields=[], + ) + visitor.on_ta_aper_function(initial_node) + + # Override the aperture attribute + override_node = TA_AperFunction( + source="", + location=0, + function=AperFunction.ViaPad, + fields=[], + ) + visitor.on_ta_aper_function(override_node) + + # Verify that the attribute has been overridden + assert ( + visitor.state.attributes.aperture_attributes[".AperFunction"] == override_node + ) + + +def test_d02_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: + """Test if D02 command is handled correctly.""" + on_draw_line = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ + ) + on_draw_cw_arc = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + ) + on_draw_ccw_arc = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + ) + visitor = StateTrackingVisitor() + + g_code = G01( + source="", + location=0, + ) + visitor.on_g01(g_code) + visitor.on_d01(default_d01) + + on_draw_line.assert_called() + on_draw_cw_arc.assert_not_called() + on_draw_ccw_arc.assert_not_called() + + +@pytest.fixture() +def default_d01() -> D01: + return D01( + source="", + location=0, + x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), + i=CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), + j=CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), + ) + + +def test_d02_draw_cw_arc(default_d01: D01, mocker: MockerFixture) -> None: + """Test if D02 command is handled correctly.""" + on_draw_line = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ + ) + on_draw_cw_arc = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + ) + on_draw_ccw_arc = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + ) + visitor = StateTrackingVisitor() + + g_code = G02( + source="", + location=0, + ) + visitor.on_g02(g_code) + visitor.on_d01(default_d01) + + on_draw_line.assert_not_called() + on_draw_cw_arc.assert_called() + on_draw_ccw_arc.assert_not_called() + + +def test_d02_draw_ccw_arc(default_d01: D01, mocker: MockerFixture) -> None: + """Test if D02 command is handled correctly.""" + on_draw_line = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ + ) + on_draw_cw_arc = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + ) + on_draw_ccw_arc = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + ) + visitor = StateTrackingVisitor() + + g_code = G03( + source="", + location=0, + ) + visitor.on_g03(g_code) + visitor.on_d01(default_d01) + + on_draw_line.assert_not_called() + on_draw_cw_arc.assert_not_called() + on_draw_ccw_arc.assert_called() + + +def test_d03_flash_circle(default_d03: D03, mocker: MockerFixture) -> None: + """Test if D03 command callbacks are correctly called.""" + spy = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_flash_circle.__name__ + ) + visitor = StateTrackingVisitor() + + ad = ADC( + source="", + location=0, + aperture_identifier=ApertureIdStr("D10"), + diameter=0.1, + ) + dnn = Dnn( + source="", + location=0, + aperture_id=ApertureIdStr("D10"), + ) + + visitor.on_adc(ad) + visitor.on_dnn(dnn) + visitor.on_d03(default_d03) + + spy.assert_called_once() + + +@pytest.fixture() +def default_d03() -> D03: + return D03( + source="", + location=0, + x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), + ) + + +def test_d03_flash_rectangle(default_d03: D03, mocker: MockerFixture) -> None: + """Test if D03 command callbacks are correctly called.""" + spy = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_flash_rectangle.__name__ + ) + visitor = StateTrackingVisitor() + + ad = ADR( + source="", + location=0, + aperture_identifier=ApertureIdStr("D10"), + width=0.1, + height=0.1, + ) + dnn = Dnn( + source="", + location=0, + aperture_id=ApertureIdStr("D10"), + ) + + visitor.on_adr(ad) + visitor.on_dnn(dnn) + visitor.on_d03(default_d03) + + spy.assert_called_once() + + +def test_d03_flash_obround(default_d03: D03, mocker: MockerFixture) -> None: + """Test if D03 command callbacks are correctly called.""" + spy = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_flash_obround.__name__ + ) + visitor = StateTrackingVisitor() + + ad = ADO( + source="", + location=0, + aperture_identifier=ApertureIdStr("D10"), + width=0.1, + height=0.1, + ) + dnn = Dnn( + source="", + location=0, + aperture_id=ApertureIdStr("D10"), + ) + + visitor.on_ado(ad) + visitor.on_dnn(dnn) + visitor.on_d03(default_d03) + + spy.assert_called_once() + + +def test_d03_flash_polygon(default_d03: D03, mocker: MockerFixture) -> None: + """Test if D03 command callbacks are correctly called.""" + spy = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_flash_polygon.__name__ + ) + visitor = StateTrackingVisitor() + + ad = ADP( + source="", + location=0, + aperture_identifier=ApertureIdStr("D10"), + vertices=6, + outer_diameter=0.1, + rotation=0, + hole_diameter=0.05, + ) + dnn = Dnn( + source="", + location=0, + aperture_id=ApertureIdStr("D10"), + ) + + visitor.on_adp(ad) + visitor.on_dnn(dnn) + visitor.on_d03(default_d03) + + spy.assert_called_once() + + +def test_d03_flash_macro(default_d03: D03, mocker: MockerFixture) -> None: + """Test if D03 command callbacks are correctly called.""" + spy = mocker.spy(StateTrackingVisitor, StateTrackingVisitor.on_flash_macro.__name__) + visitor = StateTrackingVisitor() + + am = AM( + source="", + location=0, + open=AMopen(source="", location=0, name="MACRO0"), + primitives=[], + close=AMclose(source="", location=0), + ) + ad = ADmacro( + source="", + location=0, + aperture_identifier=ApertureIdStr("D10"), + name="MACRO0", + ) + dnn = Dnn( + source="", + location=0, + aperture_id=ApertureIdStr("D10"), + ) + + visitor.on_am(am) + visitor.on_ad_macro(ad) + visitor.on_dnn(dnn) + visitor.on_d03(default_d03) + + spy.assert_called_once() + + +def test_d03_flash_block(default_d03: D03, mocker: MockerFixture) -> None: + """Test if D03 command callbacks are correctly called.""" + spy = mocker.spy(StateTrackingVisitor, StateTrackingVisitor.on_flash_block.__name__) + visitor = StateTrackingVisitor() + + ab = AB( + source="", + location=0, + open=ABopen(source="", location=0, aperture_identifier=ApertureIdStr("D10")), + nodes=[], + close=ABclose(source="", location=0), + ) + dnn = Dnn( + source="", + location=0, + aperture_id=ApertureIdStr("D10"), + ) + + visitor.on_ab(ab) + visitor.on_dnn(dnn) + visitor.on_d03(default_d03) + + spy.assert_called_once() From eb9f067e564200026c676871220110889d07cc2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 22 Aug 2024 02:52:00 +0200 Subject: [PATCH 69/91] Replace aperture_identifier with aperture_id for consistency --- .../gerberx3/ast/nodes/aperture/AB_open.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/AD.py | 2 +- .../gerberx3/ast/state_tracking_visitor.py | 4 ++-- src/pygerber/gerberx3/formatter.py | 12 ++++++------ .../gerberx3/parser/pyparsing/grammar.py | 16 ++++++++-------- test/gerberx3/test_ast/test_ast_visitor.py | 12 ++++++------ .../test_ast/test_state_tracking_visitor.py | 12 ++++++------ 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py index bdac0b81..b0c6bdfb 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py @@ -16,7 +16,7 @@ class ABopen(Node): """Represents AB Gerber extended command.""" - aperture_identifier: ApertureIdStr + aperture_id: ApertureIdStr def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py index a25cb091..5c77e3fa 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AD.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AD.py @@ -9,4 +9,4 @@ class AD(Node): """Common base class for all commands adding new apertures.""" - aperture_identifier: ApertureIdStr + aperture_id: ApertureIdStr diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index d6e8b880..38bb1535 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -270,11 +270,11 @@ def __init__(self) -> None: def on_ab(self, node: AB) -> None: """Handle `ABclose` node.""" - self.state.apertures.blocks[node.open.aperture_identifier] = node + self.state.apertures.blocks[node.open.aperture_id] = node def on_ad(self, node: AD) -> None: """Handle `AD` node.""" - self.state.apertures.apertures[node.aperture_identifier] = node + self.state.apertures.apertures[node.aperture_id] = node def on_am(self, node: AM) -> None: """Handle `AM` root node.""" diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 31c87f79..ff644929 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -463,12 +463,12 @@ def on_ab_close(self, node: ABclose) -> None: # noqa: ARG002 def on_ab_open(self, node: ABopen) -> None: """Handle `ABopen` node.""" with self._extended_command("AB"): - self._write(node.aperture_identifier) + self._write(node.aperture_id) @_decorator_insert_base_indent def on_adc(self, node: ADC) -> None: """Handle `AD` circle node.""" - with self._extended_command(f"AD{node.aperture_identifier}C,"): + with self._extended_command(f"AD{node.aperture_id}C,"): self._write(self._fmt_double(node.diameter)) if node.hole_diameter is not None: @@ -477,7 +477,7 @@ def on_adc(self, node: ADC) -> None: @_decorator_insert_base_indent def on_adr(self, node: ADR) -> None: """Handle `AD` rectangle node.""" - with self._extended_command(f"AD{node.aperture_identifier}R,"): + with self._extended_command(f"AD{node.aperture_id}R,"): self._write(self._fmt_double(node.width)) self._write(f"X{self._fmt_double(node.height)}") @@ -487,7 +487,7 @@ def on_adr(self, node: ADR) -> None: @_decorator_insert_base_indent def on_ado(self, node: ADO) -> None: """Handle `AD` obround node.""" - with self._extended_command(f"AD{node.aperture_identifier}O,"): + with self._extended_command(f"AD{node.aperture_id}O,"): self._write(self._fmt_double(node.width)) self._write(f"X{self._fmt_double(node.height)}") @@ -497,7 +497,7 @@ def on_ado(self, node: ADO) -> None: @_decorator_insert_base_indent def on_adp(self, node: ADP) -> None: """Handle `AD` polygon node.""" - with self._extended_command(f"AD{node.aperture_identifier}P,"): + with self._extended_command(f"AD{node.aperture_id}P,"): self._write(self._fmt_double(node.outer_diameter)) self._write(f"X{node.vertices}") @@ -510,7 +510,7 @@ def on_adp(self, node: ADP) -> None: @_decorator_insert_base_indent def on_ad_macro(self, node: ADmacro) -> None: """Handle `AD` macro node.""" - with self._extended_command(f"AD{node.aperture_identifier}{node.name}"): + with self._extended_command(f"AD{node.aperture_id}{node.name}"): if node.params is not None: first, *rest = node.params self._write(f",{first}") diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 4da18200..09436b60 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -241,9 +241,9 @@ def boolean(self) -> pp.ParserElement: return pp.one_of(("0", "1")).set_results_name("boolean") @pp.cached_property - def aperture_identifier(self) -> pp.ParserElement: + def aperture_id(self) -> pp.ParserElement: """Create a parser element capable of parsing aperture identifiers.""" - return pp.Regex(r"D[0]*[1-9][0-9]+").set_results_name("aperture_identifier") + return pp.Regex(r"D[0]*[1-9][0-9]+").set_results_name("aperture_id") def make_unpack_callback( self, @@ -313,7 +313,7 @@ def aperture_block(self) -> pp.ParserElement: def ab_open(self) -> pp.ParserElement: """Create a parser element capable of parsing AB-open.""" return ( - self._extended_command(pp.Literal("AB") + self.aperture_identifier) + self._extended_command(pp.Literal("AB") + self.aperture_id) .set_name("ABopen") .set_parse_action(self.make_unpack_callback(ABopen)) ) @@ -431,7 +431,7 @@ def add_aperture_circle(self) -> pp.ParserElement: return ( self._extended_command( pp.Literal("AD") - + self.aperture_identifier + + self.aperture_id + pp.Literal("C,") + self.double.set_results_name("diameter") + pp.Opt(self._x + self.double.set_results_name("hole_diameter")) @@ -449,7 +449,7 @@ def add_aperture_rectangle( return ( self._extended_command( pp.Literal("AD") - + self.aperture_identifier + + self.aperture_id + pp.Literal(f"{symbol},") + self.double.set_results_name("width") + self._x @@ -467,7 +467,7 @@ def add_aperture_polygon(self) -> pp.ParserElement: return ( self._extended_command( pp.Literal("AD") - + self.aperture_identifier + + self.aperture_id + pp.Literal("P,") + self.double.set_results_name("outer_diameter") + self._x @@ -488,7 +488,7 @@ def add_aperture_macro(self) -> pp.ParserElement: return ( self._extended_command( pp.Literal("AD") - + self.aperture_identifier + + self.aperture_id + self.name.set_results_name("name") + pp.Opt(self.comma + param + pp.ZeroOrMore(self._x + param)) ) @@ -1068,7 +1068,7 @@ def _d_codes(self, *, is_standalone: bool) -> pp.ParserElement: def _dnn(self, *, is_standalone: bool) -> pp.ParserElement: return ( - self._command(self.aperture_identifier.set_results_name("aperture_id")) + self._command(self.aperture_id.set_results_name("aperture_id")) .set_parse_action( self.make_unpack_callback(Dnn, is_standalone=is_standalone) ) diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index dfa3a581..36c7c67a 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -127,25 +127,25 @@ NODE_SAMPLES: Dict[Type[Node], Node] = { ABclose: ABclose(source="", location=0), - ABopen: ABopen(source="", location=0, aperture_identifier=ApertureIdStr("D11")), + ABopen: ABopen(source="", location=0, aperture_id=ApertureIdStr("D11")), ADC: ADC( source="", location=0, - aperture_identifier=ApertureIdStr("D11"), + aperture_id=ApertureIdStr("D11"), diameter=0.1, hole_diameter=0.05, ), ADmacro: ADmacro( source="", location=0, - aperture_identifier=ApertureIdStr("D11"), + aperture_id=ApertureIdStr("D11"), name="macro", params=[1, 2], ), ADO: ADO( source="", location=0, - aperture_identifier=ApertureIdStr("D11"), + aperture_id=ApertureIdStr("D11"), width=0.1, height=0.05, hole_diameter=0.05, @@ -153,7 +153,7 @@ ADR: ADR( source="", location=0, - aperture_identifier=ApertureIdStr("D11"), + aperture_id=ApertureIdStr("D11"), width=0.1, height=0.05, hole_diameter=0.05, @@ -161,7 +161,7 @@ ADP: ADP( source="", location=0, - aperture_identifier=ApertureIdStr("D11"), + aperture_id=ApertureIdStr("D11"), outer_diameter=0.1, vertices=4, rotation=0.1, diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py index e9d91136..2428578e 100644 --- a/test/gerberx3/test_ast/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -214,7 +214,7 @@ def test_d03_flash_circle(default_d03: D03, mocker: MockerFixture) -> None: ad = ADC( source="", location=0, - aperture_identifier=ApertureIdStr("D10"), + aperture_id=ApertureIdStr("D10"), diameter=0.1, ) dnn = Dnn( @@ -250,7 +250,7 @@ def test_d03_flash_rectangle(default_d03: D03, mocker: MockerFixture) -> None: ad = ADR( source="", location=0, - aperture_identifier=ApertureIdStr("D10"), + aperture_id=ApertureIdStr("D10"), width=0.1, height=0.1, ) @@ -277,7 +277,7 @@ def test_d03_flash_obround(default_d03: D03, mocker: MockerFixture) -> None: ad = ADO( source="", location=0, - aperture_identifier=ApertureIdStr("D10"), + aperture_id=ApertureIdStr("D10"), width=0.1, height=0.1, ) @@ -304,7 +304,7 @@ def test_d03_flash_polygon(default_d03: D03, mocker: MockerFixture) -> None: ad = ADP( source="", location=0, - aperture_identifier=ApertureIdStr("D10"), + aperture_id=ApertureIdStr("D10"), vertices=6, outer_diameter=0.1, rotation=0, @@ -338,7 +338,7 @@ def test_d03_flash_macro(default_d03: D03, mocker: MockerFixture) -> None: ad = ADmacro( source="", location=0, - aperture_identifier=ApertureIdStr("D10"), + aperture_id=ApertureIdStr("D10"), name="MACRO0", ) dnn = Dnn( @@ -363,7 +363,7 @@ def test_d03_flash_block(default_d03: D03, mocker: MockerFixture) -> None: ab = AB( source="", location=0, - open=ABopen(source="", location=0, aperture_identifier=ApertureIdStr("D10")), + open=ABopen(source="", location=0, aperture_id=ApertureIdStr("D10")), nodes=[], close=ABclose(source="", location=0), ) From e513fe3b49b1e6ecbf2f67c60c6fb5204dec675c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Thu, 22 Aug 2024 13:27:29 +0200 Subject: [PATCH 70/91] Improve abstraction in expression definition in grammar --- src/pygerber/gerberx3/ast/nodes/__init__.py | 3 +- src/pygerber/gerberx3/ast/nodes/base.py | 36 +++++++- .../gerberx3/parser/pyparsing/grammar.py | 89 +++++++++---------- .../gerberx3/tokens/math/order/add_add.grb | 3 + .../gerberx3/tokens/math/order/div_div.grb | 3 + .../gerberx3/tokens/math/order/mul_mul.grb | 3 + 6 files changed, 86 insertions(+), 51 deletions(-) create mode 100644 test/assets/gerberx3/tokens/math/order/add_add.grb create mode 100644 test/assets/gerberx3/tokens/math/order/div_div.grb create mode 100644 test/assets/gerberx3/tokens/math/order/mul_mul.grb diff --git a/src/pygerber/gerberx3/ast/nodes/__init__.py b/src/pygerber/gerberx3/ast/nodes/__init__.py index f3e56375..a7b21bf5 100644 --- a/src/pygerber/gerberx3/ast/nodes/__init__.py +++ b/src/pygerber/gerberx3/ast/nodes/__init__.py @@ -58,7 +58,7 @@ TO_CVal, TO_UserName, ) -from pygerber.gerberx3.ast.nodes.base import Node +from pygerber.gerberx3.ast.nodes.base import Node, SourceInfo from pygerber.gerberx3.ast.nodes.d_codes.D01 import D01 from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 @@ -248,4 +248,5 @@ "Expression", "AB", "AM", + "SourceInfo", ] diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py index f59138a6..2ce7f25c 100644 --- a/src/pygerber/gerberx3/ast/nodes/base.py +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -3,8 +3,9 @@ from __future__ import annotations from abc import abstractmethod -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, Optional +import pyparsing as pp from pydantic import Field from pygerber.gerberx3.ast.nodes.model import ModelType @@ -15,11 +16,34 @@ from pygerber.gerberx3.ast.visitor import AstVisitor +class SourceInfo(ModelType): + """Source information for the node.""" + + source: str + location: int + length: int + + @pp.cached_property + def line(self) -> int: + """Get the line number of the start location within the string; the first line + is line 1, newlines start new rows. + """ + return pp.lineno(self.location, self.source) + + @pp.cached_property + def column(self) -> int: + """Get the column number of the start location within the string; the first + column is column 1, newlines reset the column number to 1. + """ + return pp.col(self.location, self.source) + + class Node(ModelType): """Base class for all nodes.""" - source: str = Field(repr=False, exclude=True) - location: int = Field(repr=False, exclude=True) + source_info: Optional[SourceInfo] = Field(default=None, repr=False, exclude=True) + source: str = Field(default="", repr=False, exclude=True) + location: int = Field(default=0, repr=False, exclude=True) @abstractmethod def visit(self, visitor: AstVisitor) -> None: @@ -30,3 +54,9 @@ def get_visitor_callback_function( self, visitor: AstVisitor ) -> Callable[[Self], None]: """Get callback function for the node.""" + + def __len__(self) -> int: + """Get the length of token in source code.""" + if self.source_info is None: + return 0 + return self.source_info.length diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 09436b60..e5d15d4a 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -5,7 +5,7 @@ from __future__ import annotations from enum import IntFlag -from typing import Any, Callable, List, Literal, Type, TypeVar, cast +from typing import Any, Callable, Literal, Type, TypeVar, cast import pyparsing as pp @@ -88,6 +88,7 @@ Node, Point, Pos, + SourceInfo, SRclose, SRopen, Sub, @@ -206,7 +207,7 @@ def string(self) -> pp.ParserElement: @pp.cached_property def comma(self) -> pp.ParserElement: """Create a parser element capable of parsing commas.""" - return pp.Suppress(pp.Literal(",").set_name(",")) + return pp.Literal(",").set_name(",") @pp.cached_property def name(self) -> pp.ParserElement: @@ -254,7 +255,11 @@ def make_unpack_callback( def _(s: str, loc: int, tokens: pp.ParseResults) -> Node: return self.get_cls(node_type)( - source=s, location=loc, **tokens.as_dict(), **kwargs + source_info=SourceInfo( + source=s, location=loc, length=sum(len(t) for t in tokens.as_list()) + ), + **tokens.as_dict(), + **kwargs, ) return _ @@ -352,8 +357,10 @@ def am_open(self) -> pp.ParserElement: @pp.cached_property def am_close(self) -> pp.ParserElement: """Create a parser element capable of parsing AM-close.""" - return self._extended.set_name("AM_close").set_parse_action( - self.make_unpack_callback(AMclose) + return ( + self._extended.copy() + .set_name("AM_close") + .set_parse_action(self.make_unpack_callback(AMclose)) ) @pp.cached_property @@ -498,7 +505,7 @@ def add_aperture_macro(self) -> pp.ParserElement: @pp.cached_property def _x(self) -> pp.ParserElement: - return pp.Suppress(pp.Literal("X")).set_name("X") + return pp.Literal("X").set_name("X") # █████ ████████ ████████ ██████ ██ ██████ ██ ██ ████████ ███████ # ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ @@ -1306,81 +1313,71 @@ def expression(self) -> pp.ParserElement: factor = self.constant | self.variable def _neg(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - token_list = cast(List[List[Expression]], tokens.as_list())[0] + nested_token_list = tokens.as_list() + assert len(nested_token_list) == 1 + token_list = [t for t in nested_token_list[0] if isinstance(t, Expression)] assert len(token_list) == 1 return self.get_cls(Neg)(source=s, location=loc, operand=token_list[0]) def _pos(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - token_list = cast(List[List[Expression]], tokens.as_list())[0] + nested_token_list = tokens.as_list() + assert len(nested_token_list) == 1 + token_list = [t for t in nested_token_list[0] if isinstance(t, Expression)] assert len(token_list) == 1 return self.get_cls(Pos)(source=s, location=loc, operand=token_list[0]) - def _sub(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - nested_token_list = cast(List[List[Expression]], tokens.as_list()) - assert len(nested_token_list) == 1 - token_list = nested_token_list[0] - assert len(token_list) > 1 - return self.get_cls(Sub)(source=s, location=loc, operands=token_list) - - def _add(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - nested_token_list = cast(List[List[Expression]], tokens.as_list()) - assert len(nested_token_list) == 1 - token_list = nested_token_list[0] - assert len(token_list) > 1 - return self.get_cls(Add)(source=s, location=loc, operands=token_list) - - def _div(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - nested_token_list = cast(List[List[Expression]], tokens.as_list()) - assert len(nested_token_list) == 1 - token_list = nested_token_list[0] - assert len(token_list) > 1 - return self.get_cls(Div)(source=s, location=loc, operands=token_list) + def _binary( + cls: Type[Expression], + ) -> Callable[[str, int, pp.ParseResults], Expression]: + def _(s: str, loc: int, tokens: pp.ParseResults) -> Expression: + nested_token_list = tokens.as_list() + assert len(nested_token_list) == 1 + token_list = [ + t for t in nested_token_list[0] if isinstance(t, Expression) + ] + assert len(token_list) > 1 + return self.get_cls(cls)(source=s, location=loc, operands=token_list) # type: ignore[call-arg, return-value, type-var] - def _mul(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - nested_token_list = cast(List[List[Expression]], tokens.as_list()) - assert len(nested_token_list) == 1 - token_list = nested_token_list[0] - assert len(token_list) > 1 - return self.get_cls(Mul)(source=s, location=loc, operands=token_list) + return _ return pp.infix_notation( factor, [ ( - pp.Suppress("-"), + "-", 1, pp.OpAssoc.RIGHT, _neg, ), ( - pp.Suppress("+"), + "+", 1, pp.OpAssoc.RIGHT, _pos, ), ( - pp.Suppress("/"), + "/", 2, pp.OpAssoc.LEFT, - _div, + _binary(Div), ), ( - pp.Suppress(pp.one_of("x X")), + pp.one_of("x X"), 2, pp.OpAssoc.LEFT, - _mul, + _binary(Mul), ), ( - pp.Suppress("-"), + "-", 2, pp.OpAssoc.LEFT, - _sub, + _binary(Sub), ), ( - pp.Suppress("+"), + "+", 2, pp.OpAssoc.LEFT, - _add, + _binary(Add), ), ], ).set_name("expression") @@ -1406,9 +1403,7 @@ def assignment(self) -> pp.ParserElement: """Create a parser element capable of parsing assignments.""" return ( self._command( - self.variable - + pp.Suppress("=") - + self.expression.set_results_name("expression") + self.variable + "=" + self.expression.set_results_name("expression") ) .set_results_name("assignment") .set_parse_action(self.make_unpack_callback(Assignment)) diff --git a/test/assets/gerberx3/tokens/math/order/add_add.grb b/test/assets/gerberx3/tokens/math/order/add_add.grb new file mode 100644 index 00000000..7f5ee6b5 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/add_add.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100-$1+1.75* +% diff --git a/test/assets/gerberx3/tokens/math/order/div_div.grb b/test/assets/gerberx3/tokens/math/order/div_div.grb new file mode 100644 index 00000000..62a5f818 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/div_div.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75/100/$1* +% diff --git a/test/assets/gerberx3/tokens/math/order/mul_mul.grb b/test/assets/gerberx3/tokens/math/order/mul_mul.grb new file mode 100644 index 00000000..100ffa7a --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/mul_mul.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=1.75x100X$1* +% From 4401024c696e42cbc65e421069a5e60b3d4463ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 23 Aug 2024 21:32:40 +0200 Subject: [PATCH 71/91] Improve abstraction in expression definition in grammar --- src/pygerber/gerberx3/ast/nodes/__init__.py | 2 + .../ast/nodes/math/operators/binary/add.py | 3 +- .../ast/nodes/math/operators/binary/div.py | 3 +- .../ast/nodes/math/operators/binary/mul.py | 3 +- .../ast/nodes/math/operators/binary/sub.py | 3 +- .../gerberx3/ast/nodes/math/parenthesis.py | 30 ++++ src/pygerber/gerberx3/ast/visitor.py | 17 +- src/pygerber/gerberx3/formatter.py | 77 ++++++--- .../gerberx3/parser/pyparsing/grammar.py | 161 ++++++++++-------- .../gerberx3/tokens/math/order/add_add.grb | 2 +- .../tokens/math/order/add_div_add.grb | 3 + test/gerberx3/test_ast/test_ast_visitor.py | 16 +- test/gerberx3/test_formatter.py | 4 + 13 files changed, 215 insertions(+), 109 deletions(-) create mode 100644 src/pygerber/gerberx3/ast/nodes/math/parenthesis.py create mode 100644 test/assets/gerberx3/tokens/math/order/add_div_add.grb diff --git a/src/pygerber/gerberx3/ast/nodes/__init__.py b/src/pygerber/gerberx3/ast/nodes/__init__.py index a7b21bf5..edff53b7 100644 --- a/src/pygerber/gerberx3/ast/nodes/__init__.py +++ b/src/pygerber/gerberx3/ast/nodes/__init__.py @@ -96,6 +96,7 @@ from pygerber.gerberx3.ast.nodes.math.operators.binary.sub import Sub from pygerber.gerberx3.ast.nodes.math.operators.unary.neg import Neg from pygerber.gerberx3.ast.nodes.math.operators.unary.pos import Pos +from pygerber.gerberx3.ast.nodes.math.parenthesis import Parenthesis from pygerber.gerberx3.ast.nodes.math.point import Point from pygerber.gerberx3.ast.nodes.math.variable import Variable from pygerber.gerberx3.ast.nodes.other.coordinate import ( @@ -246,6 +247,7 @@ "TF", "Coordinate", "Expression", + "Parenthesis", "AB", "AM", "SourceInfo", diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py index 3c0e6220..61d096e1 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py @@ -19,7 +19,8 @@ class Add(Expression): """Represents math expression addition operator.""" - operands: List[Expression] = Field(min_length=2) + head: Expression + tail: List[Expression] = Field(min_length=1) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py index 151f8267..01e0c095 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py @@ -19,7 +19,8 @@ class Div(Expression): """Represents math expression division operator.""" - operands: List[Expression] = Field(min_length=2) + head: Expression + tail: List[Expression] = Field(min_length=1) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py index fbfabc38..ffc56d2a 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py @@ -19,7 +19,8 @@ class Mul(Expression): """Represents math expression multiplication operator.""" - operands: List[Expression] = Field(min_length=2) + head: Expression + tail: List[Expression] = Field(min_length=1) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py index 05a334bb..2547331e 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py @@ -19,7 +19,8 @@ class Sub(Expression): """Represents math expression subtraction operator.""" - operands: List[Expression] = Field(min_length=2) + head: Expression + tail: List[Expression] = Field(min_length=1) def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/src/pygerber/gerberx3/ast/nodes/math/parenthesis.py b/src/pygerber/gerberx3/ast/nodes/math/parenthesis.py new file mode 100644 index 00000000..3d94de67 --- /dev/null +++ b/src/pygerber/gerberx3/ast/nodes/math/parenthesis.py @@ -0,0 +1,30 @@ +"""`pygerber.nodes.math.parenthesis` module contains definition of `Parenthesis` +class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +from pygerber.gerberx3.ast.nodes.math.expression import Expression + +if TYPE_CHECKING: + from typing_extensions import Self + + from pygerber.gerberx3.ast.visitor import AstVisitor + + +class Parenthesis(Expression): + """Represents math expression expression.""" + + inner: Expression + + def visit(self, visitor: AstVisitor) -> None: + """Handle visitor call.""" + visitor.on_parenthesis(self) + + def get_visitor_callback_function( + self, visitor: AstVisitor + ) -> Callable[[Self], None]: + """Get callback function for the node.""" + return visitor.on_parenthesis diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index 0d1eeedf..afc3d810 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -87,6 +87,7 @@ Mul, Neg, Node, + Parenthesis, Point, Pos, SRclose, @@ -440,25 +441,29 @@ def on_m02(self, node: M02) -> None: def on_add(self, node: Add) -> None: """Handle `Add` node.""" self.on_expression(node) - for operand in node.operands: + node.head.visit(self) + for operand in node.tail: operand.visit(self) def on_div(self, node: Div) -> None: """Handle `Div` node.""" self.on_expression(node) - for operand in node.operands: + node.head.visit(self) + for operand in node.tail: operand.visit(self) def on_mul(self, node: Mul) -> None: """Handle `Mul` node.""" self.on_expression(node) - for operand in node.operands: + node.head.visit(self) + for operand in node.tail: operand.visit(self) def on_sub(self, node: Sub) -> None: """Handle `Sub` node.""" self.on_expression(node) - for operand in node.operands: + node.head.visit(self) + for operand in node.tail: operand.visit(self) # Math :: Operators :: Unary @@ -485,6 +490,10 @@ def on_constant(self, node: Constant) -> None: def on_expression(self, node: Expression) -> None: """Handle `Expression` node.""" + def on_parenthesis(self, node: Parenthesis) -> None: + """Handle `Parenthesis` node.""" + node.inner.visit(self) + def on_point(self, node: Point) -> None: """Handle `Point` node.""" node.x.visit(self) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index ff644929..3856e625 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -94,6 +94,7 @@ G, Mul, Neg, + Parenthesis, Point, Pos, SRclose, @@ -169,6 +170,7 @@ def __init__( # noqa: PLR0913 keep_non_standalone_codes: bool = True, remove_g54: bool = False, remove_g55: bool = False, + explicit_parenthesis: bool = False, strip_whitespace: bool = False, ) -> None: r"""Initialize Formatter instance. @@ -256,6 +258,10 @@ def __init__( # noqa: PLR0913 Remove G55 code from output, by default False G55 code has no effect on the output, it was used in legacy files to prefix flash command. + explicit_parenthesis: bool, optional + Add explicit parenthesis around all mathematical + expressions within macro, by default False + When false, original parenthesis are kept. strip_whitespace : bool, optional Remove all semantically insignificant whitespace, by default False @@ -305,6 +311,7 @@ def __init__( # noqa: PLR0913 self.keep_non_standalone_codes = keep_non_standalone_codes self.remove_g54 = remove_g54 self.remove_g55 = remove_g55 + self.explicit_parenthesis = explicit_parenthesis self.strip_whitespace = strip_whitespace if self.strip_whitespace: @@ -1030,39 +1037,59 @@ def on_m02(self, node: M02) -> None: # noqa: ARG002 # Math :: Operators :: Binary def on_add(self, node: Add) -> None: """Handle `Add` node.""" - self._write("(") - for i, operand in enumerate(node.operands): + if self.explicit_parenthesis: + self._write("(") + + node.head.visit(self) + + for operand in node.tail: + self._write("+") operand.visit(self) - if i < len(node.operands) - 1: - self._write("+") - self._write(")") + + if self.explicit_parenthesis: + self._write(")") def on_div(self, node: Div) -> None: """Handle `Div` node.""" - self._write("(") - for i, operand in enumerate(node.operands): + if self.explicit_parenthesis: + self._write("(") + + node.head.visit(self) + + for operand in node.tail: + self._write("/") operand.visit(self) - if i < len(node.operands) - 1: - self._write("/") - self._write(")") + + if self.explicit_parenthesis: + self._write(")") def on_mul(self, node: Mul) -> None: """Handle `Mul` node.""" - self._write("(") - for i, operand in enumerate(node.operands): + if self.explicit_parenthesis: + self._write("(") + + node.head.visit(self) + + for operand in node.tail: + self._write("x") operand.visit(self) - if i < len(node.operands) - 1: - self._write("x") - self._write(")") + + if self.explicit_parenthesis: + self._write(")") def on_sub(self, node: Sub) -> None: """Handle `Sub` node.""" - self._write("(") - for i, operand in enumerate(node.operands): + if self.explicit_parenthesis: + self._write("(") + + node.head.visit(self) + + for operand in node.tail: + self._write("-") operand.visit(self) - if i < len(node.operands) - 1: - self._write("-") - self._write(")") + + if self.explicit_parenthesis: + self._write(")") # Math :: Operators :: Unary @@ -1089,6 +1116,16 @@ def on_constant(self, node: Constant) -> None: """Handle `Constant` node.""" self._write(self._fmt_double(node.constant)) + def on_parenthesis(self, node: Parenthesis) -> None: + """Handle `Parenthesis` node.""" + if not self.explicit_parenthesis: + self._write("(") + + node.inner.visit(self) + + if not self.explicit_parenthesis: + self._write(")") + def on_point(self, node: Point) -> None: """Handle `Point` node.""" node.x.visit(self) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index e5d15d4a..41b3c00a 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -81,11 +81,11 @@ CoordinateY, Div, Dnn, - Expression, File, Mul, Neg, Node, + Parenthesis, Point, Pos, SourceInfo, @@ -137,12 +137,14 @@ def __init__( self, ast_node_class_overrides: dict[str, Type[Node]], *, - enable_packrat: bool = True, + enable_packrat: bool = False, + packrat_cache_size: int = 128, enable_debug: bool = False, optimization: int = 0, ) -> None: self.ast_node_class_overrides = ast_node_class_overrides self.enable_packrat = enable_packrat + self.packrat_cache_size = packrat_cache_size self.enable_debug = enable_debug self.optimization = optimization @@ -174,7 +176,7 @@ def _(s: str, loc: int, tokens: pp.ParseResults) -> File: ) if self.enable_packrat: - root.enable_packrat() + root.enable_packrat(cache_size_limit=self.packrat_cache_size) if self.enable_debug: root.set_debug() @@ -1307,80 +1309,95 @@ def m(self, value: int, cls: Type[Node]) -> pp.ParserElement: # ██ ██ ██ ██ ██ ██ ██ ██ # ██ ██ ██ ██ ██ ██ ██ + def _cb(self, element: pp.ParserElement, cls: Type[Node]) -> pp.ParserElement: + return element.set_parse_action(self.make_unpack_callback(cls)) + @pp.cached_property def expression(self) -> pp.ParserElement: """Create a parser element capable of parsing expressions.""" - factor = self.constant | self.variable - - def _neg(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - nested_token_list = tokens.as_list() - assert len(nested_token_list) == 1 - token_list = [t for t in nested_token_list[0] if isinstance(t, Expression)] - assert len(token_list) == 1 - return self.get_cls(Neg)(source=s, location=loc, operand=token_list[0]) - - def _pos(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - nested_token_list = tokens.as_list() - assert len(nested_token_list) == 1 - token_list = [t for t in nested_token_list[0] if isinstance(t, Expression)] - assert len(token_list) == 1 - return self.get_cls(Pos)(source=s, location=loc, operand=token_list[0]) - - def _binary( - cls: Type[Expression], - ) -> Callable[[str, int, pp.ParseResults], Expression]: - def _(s: str, loc: int, tokens: pp.ParseResults) -> Expression: - nested_token_list = tokens.as_list() - assert len(nested_token_list) == 1 - token_list = [ - t for t in nested_token_list[0] if isinstance(t, Expression) - ] - assert len(token_list) > 1 - return self.get_cls(cls)(source=s, location=loc, operands=token_list) # type: ignore[call-arg, return-value, type-var] + expr = pp.Forward() + factor = ( + self.variable + | self.constant + | self._cb(pp.Literal("(") + expr("inner") + pp.Literal(")"), Parenthesis) + )("factor") + + last_expr = factor + # Unary - operator + op_expr = pp.Literal("-") + + this_expr = pp.Forward() + match_expr = pp.FollowedBy(op_expr + this_expr) + ( + op_expr + this_expr("operand") + ).set_parse_action(self.make_unpack_callback(Neg)) + this_expr <<= match_expr | last_expr + last_expr = this_expr + + # Unary + operator + op_expr = pp.Literal("+") + + this_expr = pp.Forward() + match_expr = pp.FollowedBy(op_expr + this_expr) + ( + op_expr + this_expr("operand") + ).set_parse_action(self.make_unpack_callback(Pos)) + this_expr <<= match_expr | last_expr + last_expr = this_expr + + # Binary / operator + op_expr = pp.Literal("/") + + this_expr = pp.Forward() + match_expr = pp.FollowedBy(last_expr + op_expr + last_expr) + ( + last_expr("head") + + (op_expr + last_expr.set_results_name("tail", list_all_matches=True))[ + 1, ... + ] + ).set_parse_action(self.make_unpack_callback(Div)) + this_expr <<= match_expr | last_expr + last_expr = this_expr + + # Binary x|X operator + op_expr = pp.one_of(["x", "X"]) + + this_expr = pp.Forward() + match_expr = pp.FollowedBy(last_expr + op_expr + last_expr) + ( + last_expr("head") + + (op_expr + last_expr.set_results_name("tail", list_all_matches=True))[ + 1, ... + ] + ).set_parse_action(self.make_unpack_callback(Mul)) + this_expr <<= match_expr | last_expr + last_expr = this_expr + + # Binary - operator + op_expr = pp.Literal("-") + + this_expr = pp.Forward() + match_expr = pp.FollowedBy(last_expr + op_expr + last_expr) + ( + last_expr("head") + + (op_expr + last_expr.set_results_name("tail", list_all_matches=True))[ + 1, ... + ] + ).set_parse_action(self.make_unpack_callback(Sub)) + this_expr <<= match_expr | last_expr + last_expr = this_expr + + # Binary - operator + op_expr = pp.Literal("+") + + this_expr = pp.Forward() + match_expr = pp.FollowedBy(last_expr + op_expr + last_expr) + ( + last_expr("head") + + (op_expr + last_expr.set_results_name("tail", list_all_matches=True))[ + 1, ... + ] + ).set_parse_action(self.make_unpack_callback(Add)) + this_expr <<= match_expr | last_expr + last_expr = this_expr - return _ + expr <<= last_expr - return pp.infix_notation( - factor, - [ - ( - "-", - 1, - pp.OpAssoc.RIGHT, - _neg, - ), - ( - "+", - 1, - pp.OpAssoc.RIGHT, - _pos, - ), - ( - "/", - 2, - pp.OpAssoc.LEFT, - _binary(Div), - ), - ( - pp.one_of("x X"), - 2, - pp.OpAssoc.LEFT, - _binary(Mul), - ), - ( - "-", - 2, - pp.OpAssoc.LEFT, - _binary(Sub), - ), - ( - "+", - 2, - pp.OpAssoc.LEFT, - _binary(Add), - ), - ], - ).set_name("expression") + return expr("expr") @pp.cached_property def constant(self) -> pp.ParserElement: diff --git a/test/assets/gerberx3/tokens/math/order/add_add.grb b/test/assets/gerberx3/tokens/math/order/add_add.grb index 7f5ee6b5..0cfe110a 100644 --- a/test/assets/gerberx3/tokens/math/order/add_add.grb +++ b/test/assets/gerberx3/tokens/math/order/add_add.grb @@ -1,3 +1,3 @@ %AMTEST1* -$2=100-$1+1.75* +$2=100+$1+1.75* % diff --git a/test/assets/gerberx3/tokens/math/order/add_div_add.grb b/test/assets/gerberx3/tokens/math/order/add_div_add.grb new file mode 100644 index 00000000..743c9e51 --- /dev/null +++ b/test/assets/gerberx3/tokens/math/order/add_div_add.grb @@ -0,0 +1,3 @@ +%AMTEST1* +$2=100-$1/1.75+$2* +% diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 36c7c67a..5bc82e28 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -275,32 +275,32 @@ Add: Add( source="", location=0, - operands=[ - Constant(source="", location=0, constant=1), + head=Constant(source="", location=0, constant=1), + tail=[ Constant(source="", location=0, constant=2), ], ), Div: Div( source="", location=0, - operands=[ - Constant(source="", location=0, constant=1), + head=Constant(source="", location=0, constant=1), + tail=[ Constant(source="", location=0, constant=2), ], ), Mul: Mul( source="", location=0, - operands=[ - Constant(source="", location=0, constant=1), + head=Constant(source="", location=0, constant=1), + tail=[ Constant(source="", location=0, constant=2), ], ), Sub: Sub( source="", location=0, - operands=[ - Constant(source="", location=0, constant=1), + head=Constant(source="", location=0, constant=1), + tail=[ Constant(source="", location=0, constant=2), ], ), diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 99f43229..9805214e 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -83,6 +83,10 @@ def test_formatter(asset: Asset, config: Config) -> None: output_buffer.seek(0) formatted_source = output_buffer.read() + from pathlib import Path + + Path("formatted.gbr").write_text(formatted_source) + formatted_ast = parser.parse(formatted_source) if formatted_ast.model_dump_json(serialize_as_any=True) != ast.model_dump_json( From 93c834daa5637d3a752b1df89cfcecd30d8b0441 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 23 Aug 2024 22:45:03 +0200 Subject: [PATCH 72/91] Add separate draw handlers for region mode --- .../gerberx3/ast/state_tracking_visitor.py | 40 ++++++ .../test_ast/test_state_tracking_visitor.py | 130 ++++++++++-------- 2 files changed, 109 insertions(+), 61 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 38bb1535..13fd6219 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -29,6 +29,8 @@ G01, G02, G03, + G36, + G37, TA, TD, TF, @@ -225,6 +227,9 @@ class State(_StateModel): attributes: Attributes = Field(default_factory=Attributes) """Container for holding currently active attributes.""" + is_region: bool = Field(default=False) + """Flag indicating if visitor is in region mode.""" + @property def current_aperture(self) -> AD | AB: """Get currently selected aperture.""" @@ -253,6 +258,16 @@ def __init__(self) -> None: super().__init__() self.state = State() self._on_d01_handler = self.on_draw_line + self._plot_mode_to_d01_handler = { + PlotMode.LINEAR: self.on_draw_line, + PlotMode.ARC: self.on_draw_cw_arc, + PlotMode.CCW_ARC: self.on_draw_ccw_arc, + } + self._plot_mode_to_in_region_d01_handler = { + PlotMode.LINEAR: self.on_in_region_draw_line, + PlotMode.ARC: self.on_in_region_draw_cw_arc, + PlotMode.CCW_ARC: self.on_in_region_draw_ccw_arc, + } self._on_d03_handler_dispatch_table = { AD: lambda *_: throw(DirectADHandlerDispatchNotSupportedError()), ADC: self.on_flash_circle, @@ -368,13 +383,38 @@ def on_g01(self, node: G01) -> None: """Handle `G01` node.""" super().on_g01(node) self._on_d01_handler = self.on_draw_line + self.state.plot_mode = PlotMode.LINEAR def on_g02(self, node: G02) -> None: """Handle `G02` node.""" super().on_g02(node) self._on_d01_handler = self.on_draw_cw_arc + self.state.plot_mode = PlotMode.ARC def on_g03(self, node: G03) -> None: """Handle `G03` node.""" super().on_g03(node) self._on_d01_handler = self.on_draw_ccw_arc + self.state.plot_mode = PlotMode.CCW_ARC + + def on_g36(self, node: G36) -> None: + """Handle `G36` node.""" + super().on_g36(node) + self.state.is_region = True + self._on_d01_handler = self._plot_mode_to_in_region_d01_handler[ + self.state.plot_mode + ] + + def on_in_region_draw_line(self, node: D01) -> None: + """Handle `D01` node in linear interpolation mode in region.""" + + def on_in_region_draw_cw_arc(self, node: D01) -> None: + """Handle `D01` node in clockwise circular interpolation mode in region.""" + + def on_in_region_draw_ccw_arc(self, node: D01) -> None: + """Handle `D01` node in counter-clockwise circular interpolation in region.""" + + def on_g37(self, node: G37) -> None: + """Handle `G37` node.""" + super().on_g37(node) + self._on_d01_handler = self._plot_mode_to_d01_handler[self.state.plot_mode] diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py index 2428578e..ad9c0939 100644 --- a/test/gerberx3/test_ast/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -16,6 +16,7 @@ G01, G02, G03, + G36, ABclose, ABopen, ADmacro, @@ -43,8 +44,6 @@ def test_set_file_attribute() -> None: """Test if file attribute assigned is performed correctly.""" visitor = StateTrackingVisitor() node = TF_FileFunction( - source="", - location=0, file_function=FileFunction.Copper, fields=["FileFunction", "External"], ) @@ -57,8 +56,6 @@ def test_set_aperture_attribute() -> None: # Test plain assignment of TA_AperFunction node = TA_AperFunction( - source="", - location=0, function=AperFunction.ViaPad, fields=[], ) @@ -72,15 +69,11 @@ def test_set_two_aperture_attribute() -> None: # Test plain assignment of TA_AperFunction node0 = TA_AperFunction( - source="", - location=0, function=AperFunction.ViaPad, fields=[], ) visitor.on_ta_aper_function(node0) node1 = TA_DrillTolerance( - source="", - location=0, plus_tolerance=0.1, minus_tolerance=0.1, ) @@ -95,8 +88,6 @@ def test_override_aperture_attribute() -> None: # Set initial aperture attribute initial_node = TA_AperFunction( - source="", - location=0, function=AperFunction.ViaDrill, fields=[], ) @@ -104,8 +95,6 @@ def test_override_aperture_attribute() -> None: # Override the aperture attribute override_node = TA_AperFunction( - source="", - location=0, function=AperFunction.ViaPad, fields=[], ) @@ -117,7 +106,7 @@ def test_override_aperture_attribute() -> None: ) -def test_d02_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: +def test_d01_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: """Test if D02 command is handled correctly.""" on_draw_line = mocker.spy( StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ @@ -130,10 +119,7 @@ def test_d02_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: ) visitor = StateTrackingVisitor() - g_code = G01( - source="", - location=0, - ) + g_code = G01() visitor.on_g01(g_code) visitor.on_d01(default_d01) @@ -145,8 +131,6 @@ def test_d02_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: @pytest.fixture() def default_d01() -> D01: return D01( - source="", - location=0, x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), i=CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), @@ -154,7 +138,7 @@ def default_d01() -> D01: ) -def test_d02_draw_cw_arc(default_d01: D01, mocker: MockerFixture) -> None: +def test_d01_draw_cw_arc(default_d01: D01, mocker: MockerFixture) -> None: """Test if D02 command is handled correctly.""" on_draw_line = mocker.spy( StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ @@ -167,19 +151,15 @@ def test_d02_draw_cw_arc(default_d01: D01, mocker: MockerFixture) -> None: ) visitor = StateTrackingVisitor() - g_code = G02( - source="", - location=0, - ) - visitor.on_g02(g_code) - visitor.on_d01(default_d01) + G02().visit(visitor) + default_d01.visit(visitor) on_draw_line.assert_not_called() on_draw_cw_arc.assert_called() on_draw_ccw_arc.assert_not_called() -def test_d02_draw_ccw_arc(default_d01: D01, mocker: MockerFixture) -> None: +def test_d01_draw_ccw_arc(default_d01: D01, mocker: MockerFixture) -> None: """Test if D02 command is handled correctly.""" on_draw_line = mocker.spy( StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ @@ -192,12 +172,8 @@ def test_d02_draw_ccw_arc(default_d01: D01, mocker: MockerFixture) -> None: ) visitor = StateTrackingVisitor() - g_code = G03( - source="", - location=0, - ) - visitor.on_g03(g_code) - visitor.on_d01(default_d01) + G03().visit(visitor) + default_d01.visit(visitor) on_draw_line.assert_not_called() on_draw_cw_arc.assert_not_called() @@ -212,14 +188,10 @@ def test_d03_flash_circle(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() ad = ADC( - source="", - location=0, aperture_id=ApertureIdStr("D10"), diameter=0.1, ) dnn = Dnn( - source="", - location=0, aperture_id=ApertureIdStr("D10"), ) @@ -233,8 +205,6 @@ def test_d03_flash_circle(default_d03: D03, mocker: MockerFixture) -> None: @pytest.fixture() def default_d03() -> D03: return D03( - source="", - location=0, x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), ) @@ -248,15 +218,11 @@ def test_d03_flash_rectangle(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() ad = ADR( - source="", - location=0, aperture_id=ApertureIdStr("D10"), width=0.1, height=0.1, ) dnn = Dnn( - source="", - location=0, aperture_id=ApertureIdStr("D10"), ) @@ -275,15 +241,11 @@ def test_d03_flash_obround(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() ad = ADO( - source="", - location=0, aperture_id=ApertureIdStr("D10"), width=0.1, height=0.1, ) dnn = Dnn( - source="", - location=0, aperture_id=ApertureIdStr("D10"), ) @@ -302,8 +264,6 @@ def test_d03_flash_polygon(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() ad = ADP( - source="", - location=0, aperture_id=ApertureIdStr("D10"), vertices=6, outer_diameter=0.1, @@ -311,8 +271,6 @@ def test_d03_flash_polygon(default_d03: D03, mocker: MockerFixture) -> None: hole_diameter=0.05, ) dnn = Dnn( - source="", - location=0, aperture_id=ApertureIdStr("D10"), ) @@ -329,21 +287,15 @@ def test_d03_flash_macro(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() am = AM( - source="", - location=0, open=AMopen(source="", location=0, name="MACRO0"), primitives=[], close=AMclose(source="", location=0), ) ad = ADmacro( - source="", - location=0, aperture_id=ApertureIdStr("D10"), name="MACRO0", ) dnn = Dnn( - source="", - location=0, aperture_id=ApertureIdStr("D10"), ) @@ -361,15 +313,11 @@ def test_d03_flash_block(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() ab = AB( - source="", - location=0, open=ABopen(source="", location=0, aperture_id=ApertureIdStr("D10")), nodes=[], close=ABclose(source="", location=0), ) dnn = Dnn( - source="", - location=0, aperture_id=ApertureIdStr("D10"), ) @@ -378,3 +326,63 @@ def test_d03_flash_block(default_d03: D03, mocker: MockerFixture) -> None: visitor.on_d03(default_d03) spy.assert_called_once() + + +def test_switch_to_region_mode_linear_plot( + default_d01: D01, mocker: MockerFixture +) -> None: + """Check if the visitor switches to region mode handlers properly.""" + handler = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ + ) + region_mode_handler = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_in_region_draw_line.__name__ + ) + visitor = StateTrackingVisitor() + + G01().visit(visitor) + G36().visit(visitor) + default_d01.visit(visitor) + + handler.assert_not_called() + region_mode_handler.assert_called() + + +def test_switch_to_region_mode_arc_plot( + default_d01: D01, mocker: MockerFixture +) -> None: + """Check if the visitor switches to region mode handlers properly.""" + handler = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + ) + region_mode_handler = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_in_region_draw_cw_arc.__name__ + ) + visitor = StateTrackingVisitor() + + G02().visit(visitor) + G36().visit(visitor) + default_d01.visit(visitor) + + handler.assert_not_called() + region_mode_handler.assert_called() + + +def test_switch_to_region_mode_ccw_arc_plot( + default_d01: D01, mocker: MockerFixture +) -> None: + """Check if the visitor switches to region mode handlers properly.""" + handler = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + ) + region_mode_handler = mocker.spy( + StateTrackingVisitor, StateTrackingVisitor.on_in_region_draw_ccw_arc.__name__ + ) + visitor = StateTrackingVisitor() + + G03().visit(visitor) + G36().visit(visitor) + default_d01.visit(visitor) + + handler.assert_not_called() + region_mode_handler.assert_called() From 6363d27901796032ab8d216cba5fc7e4d8792ae2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 23 Aug 2024 22:50:22 +0200 Subject: [PATCH 73/91] Remove location and source parameters from hand crafted Node instances --- test/gerberx3/test_ast/test_ast_visitor.py | 370 +++++++++------------ 1 file changed, 151 insertions(+), 219 deletions(-) diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 5bc82e28..3fcdcb03 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -126,61 +126,45 @@ from pygerber.gerberx3.ast.visitor import AstVisitor NODE_SAMPLES: Dict[Type[Node], Node] = { - ABclose: ABclose(source="", location=0), - ABopen: ABopen(source="", location=0, aperture_id=ApertureIdStr("D11")), + ABclose: ABclose(location=0), + ABopen: ABopen(aperture_id=ApertureIdStr("D11")), ADC: ADC( - source="", - location=0, aperture_id=ApertureIdStr("D11"), diameter=0.1, hole_diameter=0.05, ), ADmacro: ADmacro( - source="", - location=0, aperture_id=ApertureIdStr("D11"), name="macro", params=[1, 2], ), ADO: ADO( - source="", - location=0, aperture_id=ApertureIdStr("D11"), width=0.1, height=0.05, hole_diameter=0.05, ), ADR: ADR( - source="", - location=0, aperture_id=ApertureIdStr("D11"), width=0.1, height=0.05, hole_diameter=0.05, ), ADP: ADP( - source="", - location=0, aperture_id=ApertureIdStr("D11"), outer_diameter=0.1, vertices=4, rotation=0.1, hole_diameter=0.05, ), - AMclose: AMclose(source="", location=0), - AMopen: AMopen(source="", location=0, name="macro"), - SRclose: SRclose(source="", location=0), - SRopen: SRopen(source="", location=0, x="1", y="2", i="3", j="4"), - TA_UserName: TA_UserName(source="", location=0, user_name="user"), - TA_AperFunction: TA_AperFunction( - source="", location=0, function=AperFunction.ViaDrill - ), - TA_DrillTolerance: TA_DrillTolerance( - source="", location=0, plus_tolerance=0.1, minus_tolerance=0.05 - ), + AMclose: AMclose(location=0), + AMopen: AMopen(name="macro"), + SRclose: SRclose(location=0), + SRopen: SRopen(x="1", y="2", i="3", j="4"), + TA_UserName: TA_UserName(user_name="user"), + TA_AperFunction: TA_AperFunction(function=AperFunction.ViaDrill), + TA_DrillTolerance: TA_DrillTolerance(plus_tolerance=0.1, minus_tolerance=0.05), TA_FlashText: TA_FlashText( - source="", - location=0, string="Hello World", mode="B", mirroring="R", @@ -188,255 +172,203 @@ size=None, comments=[], ), - TD: TD(source="", location=0, name="name"), - TF_UserName: TF_UserName(source="", location=0, user_name="user"), - TF_Part: TF_Part(source="", location=0, part=Part.Single), - TF_FileFunction: TF_FileFunction( - source="", location=0, file_function=FileFunction.Copper - ), - TF_FilePolarity: TF_FilePolarity(source="", location=0, polarity="Positive"), + TD: TD(name="name"), + TF_UserName: TF_UserName(user_name="user"), + TF_Part: TF_Part(part=Part.Single), + TF_FileFunction: TF_FileFunction(file_function=FileFunction.Copper), + TF_FilePolarity: TF_FilePolarity(polarity="Positive"), TF_SameCoordinates: TF_SameCoordinates( - source="", location=0, identifier="CA9C4AC4-C4BE-41B9-9754-440A126A42FF" + identifier="CA9C4AC4-C4BE-41B9-9754-440A126A42FF" ), TF_CreationDate: TF_CreationDate( - source="", - location=0, creation_date=datetime.datetime.now(tz=tzlocal.get_localzone()), ), TF_GenerationSoftware: TF_GenerationSoftware( - source="", - location=0, vendor="vendor", application="application", version="version", ), TF_ProjectId: TF_ProjectId( - source="", - location=0, name="name", guid="guid", revision="revision", ), - TF_MD5: TF_MD5(source="", location=0, md5="0" * 32), - TO_UserName: TO_UserName(source="", location=0, user_name="user"), - TO_N: TO_N(source="", location=0, net_names=["net"]), - TO_P: TO_P(source="", location=0, refdes="refdes", number="number"), - TO_C: TO_C(source="", location=0, refdes="refdes"), - TO_CRot: TO_CRot(source="", location=0, angle=0.1), - TO_CMfr: TO_CMfr(source="", location=0, manufacturer="manufacturer"), - TO_CMNP: TO_CMNP(source="", location=0, part_number="part_number"), - TO_CVal: TO_CVal(source="", location=0, value="value"), - TO_CMnt: TO_CMnt(source="", location=0, mount=Mount.SMD), - TO_CFtp: TO_CFtp(source="", location=0, footprint="footprint"), - TO_CPgN: TO_CPgN(source="", location=0, name="name"), - TO_CPgD: TO_CPgD(source="", location=0, description="description"), - TO_CHgt: TO_CHgt(source="", location=0, height=0.1), - TO_CLbN: TO_CLbN(source="", location=0, name="name"), - TO_CLbD: TO_CLbD(source="", location=0, description="description"), - TO_CSup: TO_CSup( - source="", location=0, supplier="supplier", supplier_part="supplier_part" - ), - D01: D01(source="", location=0, x=None, y=None, i=None, j=None), + TF_MD5: TF_MD5(md5="0" * 32), + TO_UserName: TO_UserName(user_name="user"), + TO_N: TO_N(net_names=["net"]), + TO_P: TO_P(refdes="refdes", number="number"), + TO_C: TO_C(refdes="refdes"), + TO_CRot: TO_CRot(angle=0.1), + TO_CMfr: TO_CMfr(manufacturer="manufacturer"), + TO_CMNP: TO_CMNP(part_number="part_number"), + TO_CVal: TO_CVal(value="value"), + TO_CMnt: TO_CMnt(mount=Mount.SMD), + TO_CFtp: TO_CFtp(footprint="footprint"), + TO_CPgN: TO_CPgN(name="name"), + TO_CPgD: TO_CPgD(description="description"), + TO_CHgt: TO_CHgt(height=0.1), + TO_CLbN: TO_CLbN(name="name"), + TO_CLbD: TO_CLbD(description="description"), + TO_CSup: TO_CSup(supplier="supplier", supplier_part="supplier_part"), + D01: D01(x=None, y=None, i=None, j=None), D02: D02( - source="", - location=0, - x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - y=CoordinateY(source="", location=0, value=PackedCoordinateStr("2")), + x=CoordinateX(value=PackedCoordinateStr("1")), + y=CoordinateY(value=PackedCoordinateStr("2")), ), D03: D03( - source="", - location=0, - x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - y=CoordinateY(source="", location=0, value=PackedCoordinateStr("2")), - ), - Dnn: Dnn(source="", location=0, aperture_id=ApertureIdStr("D11")), - G01: G01(source="", location=0), - G02: G02(source="", location=0), - G03: G03(source="", location=0), - G04: G04(source="", location=0, string="comment"), - G36: G36(source="", location=0), - G37: G37(source="", location=0), - G54: G54(source="", location=0), - G55: G55(source="", location=0), - G70: G70(source="", location=0), - G71: G71(source="", location=0), - G74: G74(source="", location=0), - G75: G75(source="", location=0), - G90: G90(source="", location=0), - G91: G91(source="", location=0), - LM: LM(source="", location=0, mirroring=Mirroring.XY), - LN: LN(source="", location=0, name="name"), - LP: LP(source="", location=0, polarity=Polarity.Clear), - LR: LR(source="", location=0, rotation=0.1), - LS: LS(source="", location=0, scale=0.1), - M00: M00(source="", location=0), - M01: M01(source="", location=0), - M02: M02(source="", location=0), + x=CoordinateX(value=PackedCoordinateStr("1")), + y=CoordinateY(value=PackedCoordinateStr("2")), + ), + Dnn: Dnn(aperture_id=ApertureIdStr("D11")), + G01: G01(location=0), + G02: G02(location=0), + G03: G03(location=0), + G04: G04(string="comment"), + G36: G36(location=0), + G37: G37(location=0), + G54: G54(location=0), + G55: G55(location=0), + G70: G70(location=0), + G71: G71(location=0), + G74: G74(location=0), + G75: G75(location=0), + G90: G90(location=0), + G91: G91(location=0), + LM: LM(mirroring=Mirroring.XY), + LN: LN(name="name"), + LP: LP(polarity=Polarity.Clear), + LR: LR(rotation=0.1), + LS: LS(scale=0.1), + M00: M00(location=0), + M01: M01(location=0), + M02: M02(location=0), Add: Add( - source="", - location=0, - head=Constant(source="", location=0, constant=1), + head=Constant(constant=1), tail=[ - Constant(source="", location=0, constant=2), + Constant(constant=2), ], ), Div: Div( - source="", - location=0, - head=Constant(source="", location=0, constant=1), + head=Constant(constant=1), tail=[ - Constant(source="", location=0, constant=2), + Constant(constant=2), ], ), Mul: Mul( - source="", - location=0, - head=Constant(source="", location=0, constant=1), + head=Constant(constant=1), tail=[ - Constant(source="", location=0, constant=2), + Constant(constant=2), ], ), Sub: Sub( - source="", - location=0, - head=Constant(source="", location=0, constant=1), + head=Constant(constant=1), tail=[ - Constant(source="", location=0, constant=2), + Constant(constant=2), ], ), Neg: Neg( - source="", - location=0, - operand=Constant(source="", location=0, constant=2), + operand=Constant(constant=2), ), Pos: Pos( - source="", - location=0, - operand=Constant(source="", location=0, constant=2), + operand=Constant(constant=2), ), Assignment: Assignment( - source="", - location=0, - variable=Variable(source="", location=0, variable="$1"), - expression=Constant(source="", location=0, constant=1), + variable=Variable(variable="$1"), + expression=Constant(constant=1), ), - Constant: Constant(source="", location=0, constant=2), + Constant: Constant(constant=2), Point: Point( - source="", - location=0, - x=Constant(source="", location=0, constant=1), - y=Constant(source="", location=0, constant=2), - ), - Variable: Variable(source="", location=0, variable="$1"), - CoordinateX: CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - CoordinateY: CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), - CoordinateI: CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), - CoordinateJ: CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), - Code0: Code0(source="", location=0, string="string"), + x=Constant(constant=1), + y=Constant(constant=2), + ), + Variable: Variable(variable="$1"), + CoordinateX: CoordinateX(value=PackedCoordinateStr("1")), + CoordinateY: CoordinateY(value=PackedCoordinateStr("1")), + CoordinateI: CoordinateI(value=PackedCoordinateStr("1")), + CoordinateJ: CoordinateJ(value=PackedCoordinateStr("1")), + Code0: Code0(string="string"), Code1: Code1( - source="", - location=0, - exposure=Constant(source="", location=0, constant=1), - diameter=Constant(source="", location=0, constant=2), - center_x=Constant(source="", location=0, constant=3), - center_y=Constant(source="", location=0, constant=4), + exposure=Constant(constant=1), + diameter=Constant(constant=2), + center_x=Constant(constant=3), + center_y=Constant(constant=4), ), Code2: Code2( - source="", - location=0, - exposure=Constant(source="", location=0, constant=1), - width=Constant(source="", location=0, constant=2), - start_x=Constant(source="", location=0, constant=3), - start_y=Constant(source="", location=0, constant=4), - end_x=Constant(source="", location=0, constant=5), - end_y=Constant(source="", location=0, constant=6), - rotation=Constant(source="", location=0, constant=7), + exposure=Constant(constant=1), + width=Constant(constant=2), + start_x=Constant(constant=3), + start_y=Constant(constant=4), + end_x=Constant(constant=5), + end_y=Constant(constant=6), + rotation=Constant(constant=7), ), Code4: Code4( - source="", - location=0, - exposure=Constant(source="", location=0, constant=1), - number_of_points=Constant(source="", location=0, constant=2), - start_x=Constant(source="", location=0, constant=3), - start_y=Constant(source="", location=0, constant=4), + exposure=Constant(constant=1), + number_of_points=Constant(constant=2), + start_x=Constant(constant=3), + start_y=Constant(constant=4), points=[ Point( - source="", - location=0, - x=Constant(source="", location=0, constant=1), - y=Constant(source="", location=0, constant=2), + x=Constant(constant=1), + y=Constant(constant=2), ) ], - rotation=Constant(source="", location=0, constant=5), + rotation=Constant(constant=5), ), Code5: Code5( - source="", - location=0, - exposure=Constant(source="", location=0, constant=1), - number_of_vertices=Constant(source="", location=0, constant=2), - center_x=Constant(source="", location=0, constant=3), - center_y=Constant(source="", location=0, constant=4), - diameter=Constant(source="", location=0, constant=5), - rotation=Constant(source="", location=0, constant=6), + exposure=Constant(constant=1), + number_of_vertices=Constant(constant=2), + center_x=Constant(constant=3), + center_y=Constant(constant=4), + diameter=Constant(constant=5), + rotation=Constant(constant=6), ), Code6: Code6( - source="", - location=0, - center_x=Constant(source="", location=0, constant=3), - center_y=Constant(source="", location=0, constant=4), - outer_diameter=Constant(source="", location=0, constant=5), - ring_thickness=Constant(source="", location=0, constant=1), - gap_between_rings=Constant(source="", location=0, constant=1), - max_ring_count=Constant(source="", location=0, constant=4), - crosshair_thickness=Constant(source="", location=0, constant=4), - crosshair_length=Constant(source="", location=0, constant=4), - rotation=Constant(source="", location=0, constant=6), + center_x=Constant(constant=3), + center_y=Constant(constant=4), + outer_diameter=Constant(constant=5), + ring_thickness=Constant(constant=1), + gap_between_rings=Constant(constant=1), + max_ring_count=Constant(constant=4), + crosshair_thickness=Constant(constant=4), + crosshair_length=Constant(constant=4), + rotation=Constant(constant=6), ), Code7: Code7( - source="", - location=0, - center_x=Constant(source="", location=0, constant=3), - center_y=Constant(source="", location=0, constant=4), - outer_diameter=Constant(source="", location=0, constant=5), - inner_diameter=Constant(source="", location=0, constant=1), - gap_thickness=Constant(source="", location=0, constant=1), - rotation=Constant(source="", location=0, constant=6), + center_x=Constant(constant=3), + center_y=Constant(constant=4), + outer_diameter=Constant(constant=5), + inner_diameter=Constant(constant=1), + gap_thickness=Constant(constant=1), + rotation=Constant(constant=6), ), Code20: Code20( - source="", - location=0, - exposure=Constant(source="", location=0, constant=1), - width=Constant(source="", location=0, constant=2), - start_x=Constant(source="", location=0, constant=3), - start_y=Constant(source="", location=0, constant=4), - end_x=Constant(source="", location=0, constant=5), - end_y=Constant(source="", location=0, constant=6), - rotation=Constant(source="", location=0, constant=7), + exposure=Constant(constant=1), + width=Constant(constant=2), + start_x=Constant(constant=3), + start_y=Constant(constant=4), + end_x=Constant(constant=5), + end_y=Constant(constant=6), + rotation=Constant(constant=7), ), Code21: Code21( - source="", - location=0, - exposure=Constant(source="", location=0, constant=1), - width=Constant(source="", location=0, constant=2), - height=Constant(source="", location=0, constant=3), - center_x=Constant(source="", location=0, constant=4), - center_y=Constant(source="", location=0, constant=5), - rotation=Constant(source="", location=0, constant=6), + exposure=Constant(constant=1), + width=Constant(constant=2), + height=Constant(constant=3), + center_x=Constant(constant=4), + center_y=Constant(constant=5), + rotation=Constant(constant=6), ), Code22: Code22( - source="", - location=0, - exposure=Constant(source="", location=0, constant=1), - width=Constant(source="", location=0, constant=2), - height=Constant(source="", location=0, constant=3), - x_lower_left=Constant(source="", location=0, constant=4), - y_lower_left=Constant(source="", location=0, constant=5), - rotation=Constant(source="", location=0, constant=6), - ), - AS: AS(source="", location=0, correspondence=AxisCorrespondence.AX_BY), + exposure=Constant(constant=1), + width=Constant(constant=2), + height=Constant(constant=3), + x_lower_left=Constant(constant=4), + y_lower_left=Constant(constant=5), + rotation=Constant(constant=6), + ), + AS: AS(correspondence=AxisCorrespondence.AX_BY), FS: FS( - source="", - location=0, zeros=Zeros.SKIP_LEADING, coordinate_mode=CoordinateMode.ABSOLUTE, x_integral=2, @@ -444,14 +376,14 @@ y_integral=4, y_decimal=5, ), - IN: IN(source="", location=0, name="name"), - IP: IP(source="", location=0, polarity=ImagePolarity.POSITIVE), - IR: IR(source="", location=0, rotation_degrees=90), - MI: MI(source="", location=0, a_mirroring=0, b_mirroring=1), - MO: MO(source="", location=0, mode=UnitMode.METRIC), - OF: OF(source="", location=0, a_offset=1, b_offset=2), - SF: SF(source="", location=0, a_scale=1.0, b_scale=1.0), - File: File(source="", location=0, nodes=[]), + IN: IN(name="name"), + IP: IP(polarity=ImagePolarity.POSITIVE), + IR: IR(rotation_degrees=90), + MI: MI(a_mirroring=0, b_mirroring=1), + MO: MO(mode=UnitMode.METRIC), + OF: OF(a_offset=1, b_offset=2), + SF: SF(a_scale=1.0, b_scale=1.0), + File: File(nodes=[]), } From 569c1f2498abc1e4db3b2aceb80c2cb0b2af0dfa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Fri, 23 Aug 2024 22:54:17 +0200 Subject: [PATCH 74/91] Remove location and source parameters from hand crafted Node instances --- .../test_ast/test_state_tracking_visitor.py | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py index ad9c0939..6e8ba479 100644 --- a/test/gerberx3/test_ast/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -131,10 +131,10 @@ def test_d01_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: @pytest.fixture() def default_d01() -> D01: return D01( - x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), - i=CoordinateI(source="", location=0, value=PackedCoordinateStr("1")), - j=CoordinateJ(source="", location=0, value=PackedCoordinateStr("1")), + x=CoordinateX(value=PackedCoordinateStr("1")), + y=CoordinateY(value=PackedCoordinateStr("1")), + i=CoordinateI(value=PackedCoordinateStr("1")), + j=CoordinateJ(value=PackedCoordinateStr("1")), ) @@ -205,8 +205,8 @@ def test_d03_flash_circle(default_d03: D03, mocker: MockerFixture) -> None: @pytest.fixture() def default_d03() -> D03: return D03( - x=CoordinateX(source="", location=0, value=PackedCoordinateStr("1")), - y=CoordinateY(source="", location=0, value=PackedCoordinateStr("1")), + x=CoordinateX(location=0, value=PackedCoordinateStr("1")), + y=CoordinateY(location=0, value=PackedCoordinateStr("1")), ) @@ -287,9 +287,9 @@ def test_d03_flash_macro(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() am = AM( - open=AMopen(source="", location=0, name="MACRO0"), + open=AMopen(name="MACRO0"), primitives=[], - close=AMclose(source="", location=0), + close=AMclose(), ) ad = ADmacro( aperture_id=ApertureIdStr("D10"), @@ -313,9 +313,9 @@ def test_d03_flash_block(default_d03: D03, mocker: MockerFixture) -> None: visitor = StateTrackingVisitor() ab = AB( - open=ABopen(source="", location=0, aperture_id=ApertureIdStr("D10")), + open=ABopen(aperture_id=ApertureIdStr("D10")), nodes=[], - close=ABclose(source="", location=0), + close=ABclose(), ) dnn = Dnn( aperture_id=ApertureIdStr("D10"), From e50d6d31e648679efe8c6da9430e54b2722bf144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 02:54:11 +0200 Subject: [PATCH 75/91] Fix expression handling & add tests for explicit_parenthesis in Formatter --- .../gerberx3/parser/pyparsing/grammar.py | 11 +- test/gerberx3/test_formatter.py | 114 ++++++++++++++++-- 2 files changed, 108 insertions(+), 17 deletions(-) diff --git a/src/pygerber/gerberx3/parser/pyparsing/grammar.py b/src/pygerber/gerberx3/parser/pyparsing/grammar.py index 41b3c00a..a03be19e 100644 --- a/src/pygerber/gerberx3/parser/pyparsing/grammar.py +++ b/src/pygerber/gerberx3/parser/pyparsing/grammar.py @@ -1397,7 +1397,7 @@ def expression(self) -> pp.ParserElement: expr <<= last_expr - return expr("expr") + return take_only(expr("expression"), "expression") @pp.cached_property def constant(self) -> pp.ParserElement: @@ -1419,9 +1419,7 @@ def variable(self) -> pp.ParserElement: def assignment(self) -> pp.ParserElement: """Create a parser element capable of parsing assignments.""" return ( - self._command( - self.variable + "=" + self.expression.set_results_name("expression") - ) + self._command(self.variable + "=" + self.expression) .set_results_name("assignment") .set_parse_action(self.make_unpack_callback(Assignment)) ) @@ -1781,3 +1779,8 @@ def sf(self) -> pp.ParserElement: .set_parse_action(self.make_unpack_callback(SF)) .set_name("SF") ) + + +def take_only(expr: pp.ParserElement, name: str) -> pp.ParserElement: + """Add parse action to extract single named parse result from a parse result.""" + return pp.TokenConverter(expr).add_parse_action(lambda t: t.as_dict()[name])(name) diff --git a/test/gerberx3/test_formatter.py b/test/gerberx3/test_formatter.py index 9805214e..f9feab11 100644 --- a/test/gerberx3/test_formatter.py +++ b/test/gerberx3/test_formatter.py @@ -83,10 +83,6 @@ def test_formatter(asset: Asset, config: Config) -> None: output_buffer.seek(0) formatted_source = output_buffer.read() - from pathlib import Path - - Path("formatted.gbr").write_text(formatted_source) - formatted_ast = parser.parse(formatted_source) if formatted_ast.model_dump_json(serialize_as_any=True) != ast.model_dump_json( @@ -116,7 +112,7 @@ def test_indent_character_space() -> None: formatted_source == """%AMDonut* 1,1,$1,$2,$3* - $4=($1x0.75)* + $4=$1x0.75* 1,0,$4,$2,$3*% """ ) @@ -125,7 +121,29 @@ def test_indent_character_space() -> None: def _format(source: str, **kwargs: Any) -> str: ast = Parser().parse(source) output_buffer = StringIO() - Formatter(**kwargs).format(ast, output_buffer) + formatter_params = { + "indent_character": " ", + "macro_body_indent": 0, + "macro_param_indent": 0, + "macro_split_mode": Formatter.MacroSplitMode.PRIMITIVES, + "macro_end_in_new_line": False, + "block_aperture_body_indent": 0, + "step_and_repeat_body_indent": 0, + "float_decimal_places": 6, + "float_trim_trailing_zeros": True, + "d01_indent": 0, + "d02_indent": 0, + "d03_indent": 0, + "line_end": "\n", + "empty_line_before_polarity_switch": False, + "keep_non_standalone_codes": True, + "remove_g54": False, + "remove_g55": False, + "explicit_parenthesis": False, + "strip_whitespace": False, + } + formatter_params.update(kwargs) + Formatter(**formatter_params).format(ast, output_buffer) # type: ignore[arg-type] output_buffer.seek(0) return output_buffer.read() @@ -143,7 +161,7 @@ def test_indent_character_tab() -> None: formatted_source == """%AMDonut* \t1,1,$1,$2,$3* -\t$4=($1x0.75)* +\t$4=$1x0.75* \t1,0,$4,$2,$3*% """ ) @@ -160,7 +178,7 @@ def test_none(self) -> None: ) assert ( formatted_source - == """%AMDonut*1,1,$1,$2,$3*$4=($1x0.75)*1,0,$4,$2,$3*% + == """%AMDonut*1,1,$1,$2,$3*$4=$1x0.75*1,0,$4,$2,$3*% """ ) @@ -176,7 +194,7 @@ def test_primitives(self) -> None: formatted_source == """%AMDonut* 1,1,$1,$2,$3* - $4=($1x0.75)* + $4=$1x0.75* 1,0,$4,$2,$3*% """ ) @@ -198,7 +216,7 @@ def test_parameters(self) -> None: $1, $2, $3* - $4=($1x0.75)* + $4=$1x0.75* 1, 0, $4, @@ -218,7 +236,7 @@ def test_none__macro_end_in_new_line(self) -> None: ) assert ( formatted_source - == """%AMDonut*1,1,$1,$2,$3*$4=($1x0.75)*1,0,$4,$2,$3* + == """%AMDonut*1,1,$1,$2,$3*$4=$1x0.75*1,0,$4,$2,$3* % """ ) @@ -236,7 +254,7 @@ def test_primitives__macro_end_in_new_line(self) -> None: formatted_source == """%AMDonut* 1,1,$1,$2,$3* - $4=($1x0.75)* + $4=$1x0.75* 1,0,$4,$2,$3* % """ @@ -259,7 +277,7 @@ def test_parameters_macro_end_in_new_line(self) -> None: $1, $2, $3* - $4=($1x0.75)* + $4=$1x0.75* 1, 0, $4, @@ -395,3 +413,73 @@ def test_step_and_repeat_body_indent() -> None: %SR*% """ ) + + +AM_TEST1 = """%AMTEST1* +$2=100-$1/1.75+$2* +% +""" + + +def test_explicit_parenthesis() -> None: + formatted_source = _format( + AM_TEST1, + explicit_parenthesis=True, + ) + assert ( + formatted_source + == """%AMTEST1* +$2=((100-($1/1.75))+$2)*% +""" + ) + + +def test_no_explicit_parenthesis() -> None: + formatted_source = _format( + AM_TEST1, + explicit_parenthesis=False, + ) + assert ( + formatted_source + == """%AMTEST1* +$2=100-$1/1.75+$2*% +""" + ) + + +def test_no_explicit_parenthesis_keep_original_same_order() -> None: + """Check if parenthesis are kept the way they were in the original source + while explicit_parenthesis is False and presence of parenthesis does not + affect expression execution order. + """ + formatted_source = _format( + """%AMTEST1* +$2=100-($1/1.75)+$2*% +""", + explicit_parenthesis=False, + ) + assert ( + formatted_source + == """%AMTEST1* +$2=100-($1/1.75)+$2*% +""" + ) + + +def test_no_explicit_parenthesis_keep_original_different_order() -> None: + """Check if parenthesis are kept the way they were in the original source + while explicit_parenthesis is False and presence of parenthesis does + affect expression execution order. + """ + formatted_source = _format( + """%AMTEST1* +$2=(100-$1)/1.75+$2*% +""", + explicit_parenthesis=False, + ) + assert ( + formatted_source + == """%AMTEST1* +$2=(100-$1)/1.75+$2*% +""" + ) From 9cf28c39209a0c2aec75d9e37322b4c9b65c8bc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 17:07:59 +0200 Subject: [PATCH 76/91] Add support for arc interpolation swapping --- .../gerberx3/ast/state_tracking_visitor.py | 130 +++++++++++++++--- .../test_ast/test_state_tracking_visitor.py | 99 ++++++++++--- 2 files changed, 192 insertions(+), 37 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 13fd6219..0afa2983 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -31,6 +31,8 @@ G03, G36, G37, + G74, + G75, TA, TD, TF, @@ -164,6 +166,16 @@ class PlotMode(Enum): """Counter-clockwise circular interpolation mode.""" +class ArcInterpolation(Enum): + """Arc interpolation mode.""" + + SINGLE_QUADRANT = "SINGLE_QUADRANT" + """Single quadrant mode.""" + + MULTI_QUADRANT = "MULTI_QUADRANT" + """Multi quadrant mode.""" + + class ApertureStorage(_StateModel): """Storage for apertures.""" @@ -194,6 +206,11 @@ class State(_StateModel): plot_mode: PlotMode = Field(default=PlotMode.LINEAR) """The plot mode. (Spec reference 4.7)""" + arc_interpolation: ArcInterpolation = Field( + default=ArcInterpolation.SINGLE_QUADRANT + ) + """The arc interpolation mode. (Spec reference: 4.7.2)""" + current_aperture_id: Optional[ApertureIdStr] = Field(default=None) """The ID of currently selected aperture. (Spec reference: 8.6)""" @@ -259,14 +276,32 @@ def __init__(self) -> None: self.state = State() self._on_d01_handler = self.on_draw_line self._plot_mode_to_d01_handler = { - PlotMode.LINEAR: self.on_draw_line, - PlotMode.ARC: self.on_draw_cw_arc, - PlotMode.CCW_ARC: self.on_draw_ccw_arc, + PlotMode.LINEAR: { + ArcInterpolation.SINGLE_QUADRANT: self.on_draw_line, + ArcInterpolation.MULTI_QUADRANT: self.on_draw_line, + }, + PlotMode.ARC: { + ArcInterpolation.SINGLE_QUADRANT: self.on_draw_cw_arc_sq, + ArcInterpolation.MULTI_QUADRANT: self.on_draw_cw_arc_mq, + }, + PlotMode.CCW_ARC: { + ArcInterpolation.SINGLE_QUADRANT: self.on_draw_ccw_arc_sq, + ArcInterpolation.MULTI_QUADRANT: self.on_draw_ccw_arc_mq, + }, } self._plot_mode_to_in_region_d01_handler = { - PlotMode.LINEAR: self.on_in_region_draw_line, - PlotMode.ARC: self.on_in_region_draw_cw_arc, - PlotMode.CCW_ARC: self.on_in_region_draw_ccw_arc, + PlotMode.LINEAR: { + ArcInterpolation.SINGLE_QUADRANT: self.on_in_region_draw_line, + ArcInterpolation.MULTI_QUADRANT: self.on_in_region_draw_line, + }, + PlotMode.ARC: { + ArcInterpolation.SINGLE_QUADRANT: self.on_in_region_draw_cw_arc_sq, + ArcInterpolation.MULTI_QUADRANT: self.on_in_region_draw_cw_arc_mq, + }, + PlotMode.CCW_ARC: { + ArcInterpolation.SINGLE_QUADRANT: self.on_in_region_draw_ccw_arc_sq, + ArcInterpolation.MULTI_QUADRANT: self.on_in_region_draw_ccw_arc_mq, + }, } self._on_d03_handler_dispatch_table = { AD: lambda *_: throw(DirectADHandlerDispatchNotSupportedError()), @@ -280,6 +315,10 @@ def __init__(self) -> None: self._on_d03_handler: Callable[[D03, AD | AB], None] = lambda *_: throw( # type: ignore[unreachable] ApertureNotSelectedError() ) + self._dispatch_d01_handler: Callable[[], None] = ( + self._dispatch_d01_handler_non_region + ) + self._dispatch_d01_handler() # Aperture @@ -328,11 +367,23 @@ def on_d01(self, node: D01) -> None: def on_draw_line(self, node: D01) -> None: """Handle `D01` node in linear interpolation mode.""" - def on_draw_cw_arc(self, node: D01) -> None: - """Handle `D01` node in clockwise circular interpolation mode.""" + def on_draw_cw_arc_sq(self, node: D01) -> None: + """Handle `D01` node in clockwise circular interpolation single quadrant + mode. + """ + + def on_draw_cw_arc_mq(self, node: D01) -> None: + """Handle `D01` node in clockwise circular interpolation multi quadrant mode.""" + + def on_draw_ccw_arc_sq(self, node: D01) -> None: + """Handle `D01` node in counter-clockwise circular interpolation single quadrant + mode. + """ - def on_draw_ccw_arc(self, node: D01) -> None: - """Handle `D01` node in counter-clockwise circular interpolation.""" + def on_draw_ccw_arc_mq(self, node: D01) -> None: + """Handle `D01` node in counter-clockwise circular interpolation multi quadrant + mode. + """ def _update_coordinates(self) -> None: if self.state.coordinate_x is not None: @@ -382,39 +433,76 @@ def on_dnn(self, node: Dnn) -> None: def on_g01(self, node: G01) -> None: """Handle `G01` node.""" super().on_g01(node) - self._on_d01_handler = self.on_draw_line self.state.plot_mode = PlotMode.LINEAR + self._dispatch_d01_handler() + + def _dispatch_d01_handler_in_region(self) -> None: + self._on_d01_handler = self._plot_mode_to_in_region_d01_handler[ + self.state.plot_mode + ][self.state.arc_interpolation] + + def _dispatch_d01_handler_non_region(self) -> None: + self._on_d01_handler = self._plot_mode_to_d01_handler[self.state.plot_mode][ + self.state.arc_interpolation + ] def on_g02(self, node: G02) -> None: """Handle `G02` node.""" super().on_g02(node) - self._on_d01_handler = self.on_draw_cw_arc self.state.plot_mode = PlotMode.ARC + self._dispatch_d01_handler() def on_g03(self, node: G03) -> None: """Handle `G03` node.""" super().on_g03(node) - self._on_d01_handler = self.on_draw_ccw_arc self.state.plot_mode = PlotMode.CCW_ARC + self._dispatch_d01_handler() def on_g36(self, node: G36) -> None: """Handle `G36` node.""" super().on_g36(node) self.state.is_region = True - self._on_d01_handler = self._plot_mode_to_in_region_d01_handler[ - self.state.plot_mode - ] + self._dispatch_d01_handler = self._dispatch_d01_handler_in_region + self._dispatch_d01_handler() def on_in_region_draw_line(self, node: D01) -> None: """Handle `D01` node in linear interpolation mode in region.""" - def on_in_region_draw_cw_arc(self, node: D01) -> None: - """Handle `D01` node in clockwise circular interpolation mode in region.""" + def on_in_region_draw_cw_arc_sq(self, node: D01) -> None: + """Handle `D01` node in clockwise circular interpolation single quadrant mode + within region statement. + """ + + def on_in_region_draw_cw_arc_mq(self, node: D01) -> None: + """Handle `D01` node in clockwise circular interpolation multi quadrant mode + within region statement. + """ + + def on_in_region_draw_ccw_arc_sq(self, node: D01) -> None: + """Handle `D01` node in counter-clockwise circular interpolation single quadrant + mode within region statement. + """ - def on_in_region_draw_ccw_arc(self, node: D01) -> None: - """Handle `D01` node in counter-clockwise circular interpolation in region.""" + def on_in_region_draw_ccw_arc_mq(self, node: D01) -> None: + """Handle `D01` node in counter-clockwise circular interpolation multi quadrant + mode within region statement. + """ def on_g37(self, node: G37) -> None: """Handle `G37` node.""" super().on_g37(node) - self._on_d01_handler = self._plot_mode_to_d01_handler[self.state.plot_mode] + self.state.is_region = False + self._dispatch_d01_handler = self._dispatch_d01_handler_non_region + self._dispatch_d01_handler() + + def on_g74(self, node: G74) -> None: + """Handle `G74` node.""" + super().on_g74(node) + self.state.arc_interpolation = ArcInterpolation.SINGLE_QUADRANT + self._dispatch_d01_handler() + + def on_g75(self, node: G75) -> None: + """Handle `G75` node.""" + super().on_g75(node) + self.state.arc_interpolation = ArcInterpolation.MULTI_QUADRANT + self._dispatch_d01_handler() diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py index 6e8ba479..4a1c52fa 100644 --- a/test/gerberx3/test_ast/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -17,6 +17,9 @@ G02, G03, G36, + G37, + G74, + G75, ABclose, ABopen, ADmacro, @@ -27,6 +30,7 @@ CoordinateX, CoordinateY, Dnn, + Node, PackedCoordinateStr, TA_AperFunction, TA_DrillTolerance, @@ -106,16 +110,18 @@ def test_override_aperture_attribute() -> None: ) -def test_d01_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: +def test_d01_draw_linear_default_quadrant( + default_d01: D01, mocker: MockerFixture +) -> None: """Test if D02 command is handled correctly.""" on_draw_line = mocker.spy( StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ ) on_draw_cw_arc = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc_sq.__name__ ) on_draw_ccw_arc = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc_sq.__name__ ) visitor = StateTrackingVisitor() @@ -130,6 +136,10 @@ def test_d01_draw_linear(default_d01: D01, mocker: MockerFixture) -> None: @pytest.fixture() def default_d01() -> D01: + return _default_d01() + + +def _default_d01() -> D01: return D01( x=CoordinateX(value=PackedCoordinateStr("1")), y=CoordinateY(value=PackedCoordinateStr("1")), @@ -138,16 +148,18 @@ def default_d01() -> D01: ) -def test_d01_draw_cw_arc(default_d01: D01, mocker: MockerFixture) -> None: +def test_d01_draw_cw_arc_default_quadrant( + default_d01: D01, mocker: MockerFixture +) -> None: """Test if D02 command is handled correctly.""" on_draw_line = mocker.spy( StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ ) on_draw_cw_arc = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc_sq.__name__ ) on_draw_ccw_arc = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc_sq.__name__ ) visitor = StateTrackingVisitor() @@ -159,16 +171,18 @@ def test_d01_draw_cw_arc(default_d01: D01, mocker: MockerFixture) -> None: on_draw_ccw_arc.assert_not_called() -def test_d01_draw_ccw_arc(default_d01: D01, mocker: MockerFixture) -> None: +def test_d01_draw_ccw_arc_default_quadrant( + default_d01: D01, mocker: MockerFixture +) -> None: """Test if D02 command is handled correctly.""" on_draw_line = mocker.spy( StateTrackingVisitor, StateTrackingVisitor.on_draw_line.__name__ ) on_draw_cw_arc = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc_sq.__name__ ) on_draw_ccw_arc = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc_sq.__name__ ) visitor = StateTrackingVisitor() @@ -328,7 +342,7 @@ def test_d03_flash_block(default_d03: D03, mocker: MockerFixture) -> None: spy.assert_called_once() -def test_switch_to_region_mode_linear_plot( +def test_switch_to_region_mode_linear_default_quadrant_plot( default_d01: D01, mocker: MockerFixture ) -> None: """Check if the visitor switches to region mode handlers properly.""" @@ -348,15 +362,15 @@ def test_switch_to_region_mode_linear_plot( region_mode_handler.assert_called() -def test_switch_to_region_mode_arc_plot( +def test_switch_to_region_mode_arc_default_quadrant_plot( default_d01: D01, mocker: MockerFixture ) -> None: """Check if the visitor switches to region mode handlers properly.""" handler = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_cw_arc_sq.__name__ ) region_mode_handler = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_in_region_draw_cw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_in_region_draw_cw_arc_sq.__name__ ) visitor = StateTrackingVisitor() @@ -368,15 +382,15 @@ def test_switch_to_region_mode_arc_plot( region_mode_handler.assert_called() -def test_switch_to_region_mode_ccw_arc_plot( +def test_switch_to_region_mode_ccw_arc_default_quadrant_plot( default_d01: D01, mocker: MockerFixture ) -> None: """Check if the visitor switches to region mode handlers properly.""" handler = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_draw_ccw_arc_sq.__name__ ) region_mode_handler = mocker.spy( - StateTrackingVisitor, StateTrackingVisitor.on_in_region_draw_ccw_arc.__name__ + StateTrackingVisitor, StateTrackingVisitor.on_in_region_draw_ccw_arc_sq.__name__ ) visitor = StateTrackingVisitor() @@ -386,3 +400,56 @@ def test_switch_to_region_mode_ccw_arc_plot( handler.assert_not_called() region_mode_handler.assert_called() + + +STATE_TRACKING_VISITOR_D01_CALLBACKS = { + StateTrackingVisitor.on_draw_line.__name__, + StateTrackingVisitor.on_draw_cw_arc_sq.__name__, + StateTrackingVisitor.on_draw_cw_arc_mq.__name__, + StateTrackingVisitor.on_draw_ccw_arc_sq.__name__, + StateTrackingVisitor.on_draw_ccw_arc_mq.__name__, +} + +STATE_TRACKING_VISITOR_D01_IN_REGION_CALLBACKS = { + StateTrackingVisitor.on_in_region_draw_line.__name__, + StateTrackingVisitor.on_in_region_draw_cw_arc_sq.__name__, + StateTrackingVisitor.on_in_region_draw_cw_arc_mq.__name__, + StateTrackingVisitor.on_in_region_draw_ccw_arc_sq.__name__, + StateTrackingVisitor.on_in_region_draw_ccw_arc_mq.__name__, +} +STATE_TRACKING_VISITOR_D01_ALL_CALLBACKS = { + *STATE_TRACKING_VISITOR_D01_CALLBACKS, + *STATE_TRACKING_VISITOR_D01_IN_REGION_CALLBACKS, +} + +_PARAMS = [ + ([G01(), G37(), G74()], StateTrackingVisitor.on_draw_line.__name__), + ([G02(), G37(), G74()], StateTrackingVisitor.on_draw_cw_arc_sq.__name__), + ([G03(), G37(), G74()], StateTrackingVisitor.on_draw_ccw_arc_sq.__name__), + ([G01(), G36(), G74()], StateTrackingVisitor.on_in_region_draw_line.__name__), + ([G02(), G36(), G74()], StateTrackingVisitor.on_in_region_draw_cw_arc_sq.__name__), + ([G03(), G36(), G74()], StateTrackingVisitor.on_in_region_draw_ccw_arc_sq.__name__), + ([G01(), G37(), G75()], StateTrackingVisitor.on_draw_line.__name__), + ([G02(), G37(), G75()], StateTrackingVisitor.on_draw_cw_arc_mq.__name__), + ([G03(), G37(), G75()], StateTrackingVisitor.on_draw_ccw_arc_mq.__name__), + ([G01(), G36(), G75()], StateTrackingVisitor.on_in_region_draw_line.__name__), + ([G02(), G36(), G75()], StateTrackingVisitor.on_in_region_draw_cw_arc_mq.__name__), + ([G03(), G36(), G75()], StateTrackingVisitor.on_in_region_draw_ccw_arc_mq.__name__), +] + + +@pytest.mark.parametrize( + ("tokens", "expect"), + _PARAMS, +) +def test_d01_dispatch( + tokens: list[Node], + expect: str, +) -> None: + """Test if D01 command is dispatched correctly.""" + visitor = StateTrackingVisitor() + + for token in tokens: + token.visit(visitor) + + assert visitor._on_d01_handler.__name__ == expect From 3a6e73e08e7413e0d831d509950cbfee09280acc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 18:12:56 +0200 Subject: [PATCH 77/91] Fix error message for D01 with no aperture selected --- src/pygerber/gerberx3/ast/errors.py | 3 ++- src/pygerber/gerberx3/ast/state_tracking_visitor.py | 5 ++--- test/gerberx3/test_ast/test_state_tracking_visitor.py | 11 +++++++++++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/pygerber/gerberx3/ast/errors.py b/src/pygerber/gerberx3/ast/errors.py index d446f0a8..e1293201 100644 --- a/src/pygerber/gerberx3/ast/errors.py +++ b/src/pygerber/gerberx3/ast/errors.py @@ -18,7 +18,8 @@ class DirectADHandlerDispatchNotSupportedError(StateTrackingVisitorError): def __init__(self) -> None: super().__init__( - "AD class can not be used to select handler. Use subclass instead." + "Aperture was not selected before flash command was issued." + " PyGerber does not support direct use of AD class as handler." ) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 0afa2983..73d2f573 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -399,7 +399,7 @@ def on_d02(self, node: D02) -> None: def on_d03(self, node: D03) -> None: """Handle `D03` node.""" super().on_d03(node) - self._on_d03_handler(node, self._current_aperture) + self._on_d03_handler(node, self.state.current_aperture) self._update_coordinates() def on_flash_circle(self, node: D03, aperture: ADC) -> None: @@ -423,9 +423,8 @@ def on_flash_block(self, node: D03, aperture: AB) -> None: def on_dnn(self, node: Dnn) -> None: """Handle `Dnn` node.""" self.state.current_aperture_id = node.aperture_id - self._current_aperture = self.state.current_aperture self._on_d03_handler = self._on_d03_handler_dispatch_table[ # type: ignore[assignment] - type(self._current_aperture) + type(self.state.current_aperture) ] # G codes diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py index 4a1c52fa..d82fe22a 100644 --- a/test/gerberx3/test_ast/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -4,6 +4,9 @@ import pytest +from pygerber.gerberx3.ast.errors import ( + ApertureNotSelectedError, +) from pygerber.gerberx3.ast.nodes import ( AB, ADC, @@ -194,6 +197,14 @@ def test_d01_draw_ccw_arc_default_quadrant( on_draw_ccw_arc.assert_called() +def test_d03_flash_no_aperture(default_d03: D03) -> None: + """Test if D03 command callbacks are correctly called.""" + visitor = StateTrackingVisitor() + + with pytest.raises(ApertureNotSelectedError): + visitor.on_d03(default_d03) + + def test_d03_flash_circle(default_d03: D03, mocker: MockerFixture) -> None: """Test if D03 command callbacks are correctly called.""" spy = mocker.spy( From bb5b335b421df6d88113f5f967754b8e494f7c51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 18:46:25 +0200 Subject: [PATCH 78/91] Add StateTrackingVisitor support for G90 & G91 --- src/pygerber/gerberx3/ast/nodes/enums.py | 2 +- .../gerberx3/ast/nodes/properties/FS.py | 4 +- .../gerberx3/ast/state_tracking_visitor.py | 42 +++++++++++++++---- test/gerberx3/test_ast/test_ast_visitor.py | 4 +- 4 files changed, 40 insertions(+), 12 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/enums.py b/src/pygerber/gerberx3/ast/nodes/enums.py index 8f9d93f4..d1e41766 100644 --- a/src/pygerber/gerberx3/ast/nodes/enums.py +++ b/src/pygerber/gerberx3/ast/nodes/enums.py @@ -22,7 +22,7 @@ def __repr__(self) -> str: __str__ = __repr__ -class CoordinateMode(Enum): +class CoordinateNotation(Enum): """Coordinate mode enumeration.""" ABSOLUTE = "A" diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py index 7a30be58..337cb4fd 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/FS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.ast.nodes.enums import CoordinateMode, Zeros +from pygerber.gerberx3.ast.nodes.enums import CoordinateNotation, Zeros if TYPE_CHECKING: from typing_extensions import Self @@ -17,7 +17,7 @@ class FS(Node): """Represents FS Gerber extended command.""" zeros: Zeros - coordinate_mode: CoordinateMode + coordinate_mode: CoordinateNotation x_integral: int x_decimal: int diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 73d2f573..751546e8 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -31,8 +31,12 @@ G03, G36, G37, + G70, + G71, G74, G75, + G90, + G91, TA, TD, TF, @@ -45,7 +49,7 @@ ) from pygerber.gerberx3.ast.nodes.enums import ( AxisCorrespondence, - CoordinateMode, + CoordinateNotation, ImagePolarity, Mirroring, UnitMode, @@ -61,14 +65,14 @@ class _StateModel(BaseModel): class CoordinateFormat(_StateModel): """Coordinate format information.""" - zeros: Zeros - coordinate_mode: CoordinateMode + zeros: Zeros = Field(default=Zeros.SKIP_LEADING) + coordinate_mode: CoordinateNotation = Field(default=CoordinateNotation.ABSOLUTE) - x_integral: int - x_decimal: int + x_integral: int = Field(default=2) + x_decimal: int = Field(default=6) - y_integral: int - y_decimal: int + y_integral: int = Field(default=2) + y_decimal: int = Field(default=6) def __init__(self, **kwargs: Any) -> None: super().__init__(**kwargs) @@ -494,6 +498,16 @@ def on_g37(self, node: G37) -> None: self._dispatch_d01_handler = self._dispatch_d01_handler_non_region self._dispatch_d01_handler() + def on_g70(self, node: G70) -> None: + """Handle `G70` node.""" + super().on_g70(node) + self.state.unit_mode = UnitMode.IMPERIAL + + def on_g71(self, node: G71) -> None: + """Handle `G71` node.""" + super().on_g71(node) + self.state.unit_mode = UnitMode.METRIC + def on_g74(self, node: G74) -> None: """Handle `G74` node.""" super().on_g74(node) @@ -505,3 +519,17 @@ def on_g75(self, node: G75) -> None: super().on_g75(node) self.state.arc_interpolation = ArcInterpolation.MULTI_QUADRANT self._dispatch_d01_handler() + + def on_g90(self, node: G90) -> None: + """Handle `G90` node.""" + super().on_g90(node) + if self.state.coordinate_format is None: + self.state.coordinate_format = CoordinateFormat() + self.state.coordinate_format.coordinate_mode = CoordinateNotation.ABSOLUTE + + def on_g91(self, node: G91) -> None: + """Handle `G91` node.""" + super().on_g91(node) + if self.state.coordinate_format is None: + self.state.coordinate_format = CoordinateFormat() + self.state.coordinate_format.coordinate_mode = CoordinateNotation.INCREMENTAL diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 3fcdcb03..47b8c1e4 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -63,7 +63,7 @@ from pygerber.gerberx3.ast.nodes.d_codes.D02 import D02 from pygerber.gerberx3.ast.nodes.d_codes.D03 import D03 from pygerber.gerberx3.ast.nodes.d_codes.Dnn import Dnn -from pygerber.gerberx3.ast.nodes.enums import CoordinateMode, ImagePolarity, Zeros +from pygerber.gerberx3.ast.nodes.enums import CoordinateNotation, ImagePolarity, Zeros from pygerber.gerberx3.ast.nodes.file import File from pygerber.gerberx3.ast.nodes.g_codes.G01 import G01 from pygerber.gerberx3.ast.nodes.g_codes.G02 import G02 @@ -370,7 +370,7 @@ AS: AS(correspondence=AxisCorrespondence.AX_BY), FS: FS( zeros=Zeros.SKIP_LEADING, - coordinate_mode=CoordinateMode.ABSOLUTE, + coordinate_mode=CoordinateNotation.ABSOLUTE, x_integral=2, x_decimal=3, y_integral=4, From 2ca357242c6268dcaf2dfabd41055d0198d7a24c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 18:53:36 +0200 Subject: [PATCH 79/91] Add StateTrackingVisitor support for load commands --- src/pygerber/gerberx3/ast/nodes/load/LM.py | 2 +- src/pygerber/gerberx3/ast/nodes/load/LP.py | 2 +- .../gerberx3/ast/state_tracking_visitor.py | 34 +++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/load/LM.py b/src/pygerber/gerberx3/ast/nodes/load/LM.py index de4cfefb..0d98630d 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LM.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LM.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.state_enums import Mirroring +from pygerber.gerberx3.ast.nodes.enums import Mirroring if TYPE_CHECKING: from typing_extensions import Self diff --git a/src/pygerber/gerberx3/ast/nodes/load/LP.py b/src/pygerber/gerberx3/ast/nodes/load/LP.py index b47369ef..168c785b 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LP.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LP.py @@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Callable from pygerber.gerberx3.ast.nodes.base import Node -from pygerber.gerberx3.state_enums import Polarity +from pygerber.gerberx3.ast.nodes.enums import Polarity if TYPE_CHECKING: from typing_extensions import Self diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 751546e8..f00ad60d 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -37,6 +37,11 @@ G75, G90, G91, + LM, + LN, + LP, + LR, + LS, TA, TD, TF, @@ -52,6 +57,7 @@ CoordinateNotation, ImagePolarity, Mirroring, + Polarity, UnitMode, Zeros, ) @@ -147,6 +153,9 @@ class Attributes(_StateModel): class Transform(_StateModel): """Aperture transformations.""" + polarity: Polarity = Field(default=Polarity.Dark) + """Aperture polarity set with LP command. (Spec reference: 4.9.2)""" + mirroring: Mirroring = Field(default=Mirroring.NONE) """Aperture mirroring set with LM command. (Spec reference: 4.9.3)""" @@ -533,3 +542,28 @@ def on_g91(self, node: G91) -> None: if self.state.coordinate_format is None: self.state.coordinate_format = CoordinateFormat() self.state.coordinate_format.coordinate_mode = CoordinateNotation.INCREMENTAL + + def on_lm(self, node: LM) -> None: + """Handle `LM` node.""" + super().on_lm(node) + self.state.transform.mirroring = node.mirroring + + def on_ln(self, node: LN) -> None: + """Handle `LN` node.""" + super().on_ln(node) + self.state.attributes.file_name = node.name + + def on_lp(self, node: LP) -> None: + """Handle `LP` node.""" + super().on_lp(node) + self.state.transform.polarity = node.polarity + + def on_lr(self, node: LR) -> None: + """Handle `LR` node.""" + super().on_lr(node) + self.state.transform.rotation = node.rotation + + def on_ls(self, node: LS) -> None: + """Handle `LS` node.""" + super().on_ls(node) + self.state.transform.scaling = node.scale From c209d478f1d28611cc7af5206ebee02117b0f536 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 19:56:53 +0200 Subject: [PATCH 80/91] Add StateTrackingVisitor support for M00 & M02 --- .../gerberx3/ast/state_tracking_visitor.py | 37 ++++++++++++++++++- src/pygerber/gerberx3/ast/visitor.py | 35 +++++++++--------- 2 files changed, 53 insertions(+), 19 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index f00ad60d..699bbb90 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -4,6 +4,7 @@ from __future__ import annotations +from contextlib import suppress from enum import Enum from typing import Any, Callable, Optional @@ -42,6 +43,8 @@ LP, LR, LS, + M00, + M02, TA, TD, TF, @@ -50,8 +53,10 @@ ApertureIdStr, Dnn, Double, + File, PackedCoordinateStr, ) +from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.enums import ( AxisCorrespondence, CoordinateNotation, @@ -275,6 +280,14 @@ def current_aperture(self) -> AD | AB: raise ApertureNotFoundError(self.current_aperture_id) +class ProgramStop(Exception): # noqa: N818 + """Exception raised when M00 or M02 command is encountered.""" + + def __init__(self, node: M00 | M02) -> None: + self.node = node + super().__init__() + + class StateTrackingVisitor(AstVisitor): """`StateTrackingVisitor` is a visitor class that tracks the internal state defined in the GerberX3 specification and modifies it according to Gerber @@ -284,8 +297,10 @@ class StateTrackingVisitor(AstVisitor): interface of `AstVisitor` class. """ - def __init__(self) -> None: + def __init__(self, *, ignore_program_stop: bool = False) -> None: super().__init__() + self._ignore_program_stop = ignore_program_stop + self.state = State() self._on_d01_handler = self.on_draw_line self._plot_mode_to_d01_handler = { @@ -567,3 +582,23 @@ def on_ls(self, node: LS) -> None: """Handle `LS` node.""" super().on_ls(node) self.state.transform.scaling = node.scale + + def on_m00(self, node: M00) -> None: + """Handle `M00` node.""" + raise ProgramStop(node) + + def on_m02(self, node: M02) -> None: + """Handle `M02` node.""" + raise ProgramStop(node) + + def on_file(self, node: File) -> None: + """Handle `File` node.""" + with suppress(ProgramStop): + super().on_file(node) + + def on_exception(self, node: Node, exception: Exception) -> bool: # noqa: ARG002 + """Handle exception.""" + if isinstance(exception, ProgramStop): + return bool(self._ignore_program_stop) + + return False diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/visitor.py index afc3d810..2b55606f 100644 --- a/src/pygerber/gerberx3/ast/visitor.py +++ b/src/pygerber/gerberx3/ast/visitor.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, List, Optional +from typing import TYPE_CHECKING if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes import ( @@ -129,10 +129,6 @@ class AstVisitor: https://refactoring.guru/design-patterns/visitor """ - def __init__(self) -> None: - self._current_node_index = -1 - self._nodes: List[Node] = [] - # Aperture def on_ab(self, node: AB) -> None: @@ -649,19 +645,22 @@ def on_sf(self, node: SF) -> None: def on_file(self, node: File) -> None: """Handle `File` node.""" - self._current_node_index = 0 - self._nodes = node.nodes try: for command in node.nodes: - command.visit(self) + try: + command.visit(self) + except Exception as e: # noqa: PERF203 + if self.on_exception(command, e): + raise finally: - self._current_node_index = -1 - self._nodes = [] - - def get_next_node(self) -> Optional[Node]: - """Get next node from the iterator.""" - if -1 < self._current_node_index < len(self._nodes): - node = self._nodes[self._current_node_index] - self._current_node_index += 1 - return node - return None + self.on_end_of_file(node) + + def on_end_of_file(self, node: File) -> None: + """Handle end of file.""" + + def on_exception(self, node: Node, exception: Exception) -> bool: # noqa: ARG002 + """Handle exception. + + If return value is True, exception will be re-raised. + """ + return True From 738890ce812b082e00f3772a0e93ae2aebd69828 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 20:01:05 +0200 Subject: [PATCH 81/91] Fix MD5 checking --- src/pygerber/gerberx3/ast/errors.py | 17 +++++++++++++ .../gerberx3/ast/nodes/attribute/TF.py | 6 ++++- .../test_nodes/test_attribute/test_fs.py | 24 ------------------- .../test_nodes/test_attribute/test_tf.py | 21 ++++++++++++++++ 4 files changed, 43 insertions(+), 25 deletions(-) delete mode 100644 test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py create mode 100644 test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py diff --git a/src/pygerber/gerberx3/ast/errors.py b/src/pygerber/gerberx3/ast/errors.py index e1293201..8564f6b1 100644 --- a/src/pygerber/gerberx3/ast/errors.py +++ b/src/pygerber/gerberx3/ast/errors.py @@ -2,8 +2,17 @@ from __future__ import annotations +from typing import TYPE_CHECKING + from pygerber.gerberx3.ast.nodes.types import ApertureIdStr +if TYPE_CHECKING: + from pygerber.gerberx3.ast.nodes import TF_MD5 + + +class AstError(Exception): + """Base class for all errors raised by AST.""" + class VisitorError(Exception): """Base class for all errors raised by visitors.""" @@ -40,3 +49,11 @@ def __init__(self, aperture_number: ApertureIdStr) -> None: super().__init__( f"Aperture {aperture_number} not found in the aperture dictionary." ) + + +class SourceNotAvailableError(AstError): + """Raised when source is not available for MD5 check.""" + + def __init__(self, node: TF_MD5) -> None: + super().__init__("Source is not available for MD5 check.") + self.node = node diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py index e32042b5..910eb082 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -8,6 +8,7 @@ from pydantic import Field +from pygerber.gerberx3.ast.errors import SourceNotAvailableError from pygerber.gerberx3.ast.nodes.base import Node from pygerber.gerberx3.ast.nodes.enums import FileFunction, Part @@ -220,8 +221,11 @@ def visit(self, visitor: AstVisitor) -> None: def check_source_hash(self) -> bool: """Validate MD5 attribute.""" + if self.source_info is None: + raise SourceNotAvailableError(self) + source = ( - self.source[: self.location - 1] + self.source_info.source[: self.source_info.location - 1] .replace("\n", "") .replace("\r", "") .encode("utf-8") diff --git a/test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py b/test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py deleted file mode 100644 index 8bb70708..00000000 --- a/test/gerberx3/test_ast/test_nodes/test_attribute/test_fs.py +++ /dev/null @@ -1,24 +0,0 @@ -from __future__ import annotations - -from pathlib import Path - -from pygerber.gerberx3.ast.nodes.attribute.TF import TF_MD5 -from pygerber.gerberx3.ast.visitor import AstVisitor -from pygerber.gerberx3.parser.pyparsing.parser import Parser - - -class TestFS_MD4: # noqa: N801 - def test_check_source_hash(self) -> None: - """Test check_source_hash.""" - source = Path( - "test/assets/gerberx3/AltiumGerberX2/PCB1_Profile.gbr" - ).read_text() - parser = Parser() - - output = parser.parse(source, strict=True) - - class CheckMD5(AstVisitor): - def on_tf_md5(self, node: TF_MD5) -> None: - assert node.check_source_hash() is True - - CheckMD5().on_file(output) diff --git a/test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py b/test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py new file mode 100644 index 00000000..aa8f5524 --- /dev/null +++ b/test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py @@ -0,0 +1,21 @@ +from __future__ import annotations + +from pathlib import Path + +from pygerber.gerberx3.ast.nodes.attribute.TF import TF_MD5 +from pygerber.gerberx3.ast.visitor import AstVisitor +from pygerber.gerberx3.parser.pyparsing.parser import Parser + + +def test_check_source_hash() -> None: + """Test check_source_hash.""" + source = Path("test/assets/gerberx3/AltiumGerberX2/PCB1_Profile.gbr").read_text() + parser = Parser() + + output = parser.parse(source, strict=True) + + class CheckMD5(AstVisitor): + def on_tf_md5(self, node: TF_MD5) -> None: + assert node.check_source_hash() is True + + CheckMD5().on_file(output) From 70e42f1af39738405140d839a759f615f5b06cd1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 20:05:18 +0200 Subject: [PATCH 82/91] Rename visitor.py module to ast_visitor.py for consistency --- src/pygerber/gerberx3/ast/{visitor.py => ast_visitor.py} | 0 src/pygerber/gerberx3/ast/errors.py | 3 +-- src/pygerber/gerberx3/ast/nodes/aperture/AB.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/ADC.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/ADO.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/ADP.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/ADR.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/AM.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/SR.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py | 2 +- src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py | 2 +- src/pygerber/gerberx3/ast/nodes/attribute/TA.py | 2 +- src/pygerber/gerberx3/ast/nodes/attribute/TD.py | 2 +- src/pygerber/gerberx3/ast/nodes/attribute/TF.py | 2 +- src/pygerber/gerberx3/ast/nodes/attribute/TO.py | 2 +- src/pygerber/gerberx3/ast/nodes/base.py | 2 +- src/pygerber/gerberx3/ast/nodes/d_codes/D01.py | 2 +- src/pygerber/gerberx3/ast/nodes/d_codes/D02.py | 2 +- src/pygerber/gerberx3/ast/nodes/d_codes/D03.py | 2 +- src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py | 2 +- src/pygerber/gerberx3/ast/nodes/file.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G01.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G02.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G03.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G04.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G36.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G37.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G54.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G55.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G70.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G71.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G74.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G75.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G90.py | 2 +- src/pygerber/gerberx3/ast/nodes/g_codes/G91.py | 2 +- src/pygerber/gerberx3/ast/nodes/load/LM.py | 2 +- src/pygerber/gerberx3/ast/nodes/load/LN.py | 2 +- src/pygerber/gerberx3/ast/nodes/load/LP.py | 2 +- src/pygerber/gerberx3/ast/nodes/load/LR.py | 2 +- src/pygerber/gerberx3/ast/nodes/load/LS.py | 2 +- src/pygerber/gerberx3/ast/nodes/m_codes/M00.py | 2 +- src/pygerber/gerberx3/ast/nodes/m_codes/M01.py | 2 +- src/pygerber/gerberx3/ast/nodes/m_codes/M02.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/assignment.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/constant.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/expression.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/parenthesis.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/point.py | 2 +- src/pygerber/gerberx3/ast/nodes/math/variable.py | 2 +- src/pygerber/gerberx3/ast/nodes/other/coordinate.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_0.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_1.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_2.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_20.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_21.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_22.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_4.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_5.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_6.py | 2 +- src/pygerber/gerberx3/ast/nodes/primitives/code_7.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/AS.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/FS.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/IN.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/IP.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/IR.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/MI.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/MO.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/OF.py | 2 +- src/pygerber/gerberx3/ast/nodes/properties/SF.py | 2 +- src/pygerber/gerberx3/ast/state_tracking_visitor.py | 2 +- src/pygerber/gerberx3/formatter.py | 2 +- test/gerberx3/test_ast/test_ast_visitor.py | 2 +- test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py | 2 +- 84 files changed, 83 insertions(+), 84 deletions(-) rename src/pygerber/gerberx3/ast/{visitor.py => ast_visitor.py} (100%) diff --git a/src/pygerber/gerberx3/ast/visitor.py b/src/pygerber/gerberx3/ast/ast_visitor.py similarity index 100% rename from src/pygerber/gerberx3/ast/visitor.py rename to src/pygerber/gerberx3/ast/ast_visitor.py diff --git a/src/pygerber/gerberx3/ast/errors.py b/src/pygerber/gerberx3/ast/errors.py index 8564f6b1..2c51fcda 100644 --- a/src/pygerber/gerberx3/ast/errors.py +++ b/src/pygerber/gerberx3/ast/errors.py @@ -4,10 +4,9 @@ from typing import TYPE_CHECKING -from pygerber.gerberx3.ast.nodes.types import ApertureIdStr - if TYPE_CHECKING: from pygerber.gerberx3.ast.nodes import TF_MD5 + from pygerber.gerberx3.ast.nodes.types import ApertureIdStr class AstError(Exception): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB.py index ac0c1c0a..44ce460c 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class AB(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py index a3d8cdf6..9552d7f7 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_close.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class ABclose(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py index b0c6bdfb..816388fb 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB_open.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class ABopen(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py index 57d20278..f26f1862 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADC.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class ADC(AD): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py index 510c9255..5487c3d9 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADO.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class ADO(AD): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py index d516a267..ca2de499 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADP.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class ADP(AD): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py index f14e79d1..510c60fb 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADR.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class ADR(AD): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py index e88f7cea..1e4ad6fd 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/ADmacro.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class ADmacro(AD): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py index ee02f567..9d380e6f 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class AM(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py index a37bd89f..d1b6877e 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM_close.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class AMclose(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py index b7b96530..cfd83036 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM_open.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class AMopen(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR.py index a3adbd34..fdb5f675 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/SR.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class SR(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py index 51bbca86..36e95b25 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR_close.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class SRclose(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py index eeb3ada2..16f00bdb 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR_open.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class SRopen(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py index 027358d7..9ef30773 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TA.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TA.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class TA(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TD.py b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py index 60d53cbb..dab662f3 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TD.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TD.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class TD(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py index 910eb082..773a153f 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TF.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TF.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class TF(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py index 33114421..17e669eb 100644 --- a/src/pygerber/gerberx3/ast/nodes/attribute/TO.py +++ b/src/pygerber/gerberx3/ast/nodes/attribute/TO.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class TO(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/base.py b/src/pygerber/gerberx3/ast/nodes/base.py index 2ce7f25c..14dffd73 100644 --- a/src/pygerber/gerberx3/ast/nodes/base.py +++ b/src/pygerber/gerberx3/ast/nodes/base.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class SourceInfo(ModelType): diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py index 79657aba..2f43523d 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D01.py @@ -17,7 +17,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class D01(D): diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py index 83e98e3f..9cb56726 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D02.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class D02(D): diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py index 783b7cf8..ab04e439 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/D03.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class D03(D): diff --git a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py index 85fd7472..f306d00d 100644 --- a/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py +++ b/src/pygerber/gerberx3/ast/nodes/d_codes/Dnn.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Dnn(D): diff --git a/src/pygerber/gerberx3/ast/nodes/file.py b/src/pygerber/gerberx3/ast/nodes/file.py index fc68d2ca..73b2f0ff 100644 --- a/src/pygerber/gerberx3/ast/nodes/file.py +++ b/src/pygerber/gerberx3/ast/nodes/file.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class File(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py index 2c2de900..eeedefe2 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G01.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G01(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py index 036454a2..034aaf5a 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G02.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G02(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py index c142ade4..71b4516a 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G03.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G03(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py index 6bc9fe22..908d7412 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G04.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G04(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py index 847757c4..57c3d1fe 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G36.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G36(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py index ecd559a9..2e934e48 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G37.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G37(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py index e3f4f4a9..ff2eb28c 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G54.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G54(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py index 0020e45b..4ece2c2f 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G55.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G55(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py index 83c342eb..5c2f7fae 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G70.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G70(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py index 594fbe0d..36fc7127 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G71.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G71(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py index a26fc376..8daf0786 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G74.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G74(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py index 100f0415..8b405317 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G75.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G75(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py index aac6cc47..1d339aa4 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G90.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G90(G): diff --git a/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py index 3f093a41..8c1f78c0 100644 --- a/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py +++ b/src/pygerber/gerberx3/ast/nodes/g_codes/G91.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class G91(G): diff --git a/src/pygerber/gerberx3/ast/nodes/load/LM.py b/src/pygerber/gerberx3/ast/nodes/load/LM.py index 0d98630d..f73bcf2d 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LM.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LM.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class LM(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/load/LN.py b/src/pygerber/gerberx3/ast/nodes/load/LN.py index bbea2b7b..0a3baa60 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LN.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LN.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class LN(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/load/LP.py b/src/pygerber/gerberx3/ast/nodes/load/LP.py index 168c785b..993e1ad0 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LP.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LP.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class LP(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/load/LR.py b/src/pygerber/gerberx3/ast/nodes/load/LR.py index ec6ee728..093d8d9b 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LR.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LR.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class LR(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/load/LS.py b/src/pygerber/gerberx3/ast/nodes/load/LS.py index 67fcb2ad..d1f28791 100644 --- a/src/pygerber/gerberx3/ast/nodes/load/LS.py +++ b/src/pygerber/gerberx3/ast/nodes/load/LS.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class LS(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py index 5db53937..d5d0f6d2 100644 --- a/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M00.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class M00(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py index 331aa9b0..5bfccdd6 100644 --- a/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M01.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class M01(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py b/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py index cb6b6a21..8d4a451f 100644 --- a/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py +++ b/src/pygerber/gerberx3/ast/nodes/m_codes/M02.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class M02(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/math/assignment.py b/src/pygerber/gerberx3/ast/nodes/math/assignment.py index bd63c736..70192826 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/assignment.py +++ b/src/pygerber/gerberx3/ast/nodes/math/assignment.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Assignment(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/math/constant.py b/src/pygerber/gerberx3/ast/nodes/math/constant.py index 50c84bba..d4cbb84a 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/constant.py +++ b/src/pygerber/gerberx3/ast/nodes/math/constant.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Constant(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/expression.py b/src/pygerber/gerberx3/ast/nodes/math/expression.py index e9b3ed5c..03827479 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/expression.py +++ b/src/pygerber/gerberx3/ast/nodes/math/expression.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Expression(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py index 61d096e1..cbd89dc4 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/add.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Add(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py index 01e0c095..fc6bb0e5 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/div.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Div(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py index ffc56d2a..497f5f64 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/mul.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Mul(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py index 2547331e..e80e6808 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/binary/sub.py @@ -13,7 +13,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Sub(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py index 31a717ae..980dfbd4 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/neg.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Neg(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py index cfd2e9a1..a15f3987 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py +++ b/src/pygerber/gerberx3/ast/nodes/math/operators/unary/pos.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Pos(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/parenthesis.py b/src/pygerber/gerberx3/ast/nodes/math/parenthesis.py index 3d94de67..3d528ead 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/parenthesis.py +++ b/src/pygerber/gerberx3/ast/nodes/math/parenthesis.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Parenthesis(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/math/point.py b/src/pygerber/gerberx3/ast/nodes/math/point.py index 3433675c..70790e7a 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/point.py +++ b/src/pygerber/gerberx3/ast/nodes/math/point.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Point(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/math/variable.py b/src/pygerber/gerberx3/ast/nodes/math/variable.py index 27e1d723..d15dec62 100644 --- a/src/pygerber/gerberx3/ast/nodes/math/variable.py +++ b/src/pygerber/gerberx3/ast/nodes/math/variable.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Variable(Expression): diff --git a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py index 6e98d7c2..36580c22 100644 --- a/src/pygerber/gerberx3/ast/nodes/other/coordinate.py +++ b/src/pygerber/gerberx3/ast/nodes/other/coordinate.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Coordinate(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py index 53df3952..e517de36 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_0.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code0(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py index e19e19d6..3b3e946e 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_1.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code1(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py index 66b23720..c2368ded 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_2.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code2(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py index 7f55950c..72e87f8c 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_20.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code20(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py index 0cc1c178..aba88536 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_21.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code21(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py index cf29180c..e6ca5799 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_22.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code22(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py index a0b1ea92..f7c6dfc0 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_4.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code4(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py index f4769a3a..74131e0f 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_5.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code5(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py index f762d135..01a1ac2b 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_6.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code6(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py index 46e684e1..215e698b 100644 --- a/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py +++ b/src/pygerber/gerberx3/ast/nodes/primitives/code_7.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class Code7(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/AS.py b/src/pygerber/gerberx3/ast/nodes/properties/AS.py index 79320442..6c69d465 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/AS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/AS.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class AS(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/FS.py b/src/pygerber/gerberx3/ast/nodes/properties/FS.py index 337cb4fd..1adeb036 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/FS.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/FS.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class FS(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IN.py b/src/pygerber/gerberx3/ast/nodes/properties/IN.py index e202b7c7..b46b761a 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IN.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IN.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class IN(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IP.py b/src/pygerber/gerberx3/ast/nodes/properties/IP.py index 33289036..0615bab5 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IP.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IP.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class IP(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/IR.py b/src/pygerber/gerberx3/ast/nodes/properties/IR.py index bedf8648..ee5caa41 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/IR.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/IR.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class IR(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MI.py b/src/pygerber/gerberx3/ast/nodes/properties/MI.py index 5447fcba..dd98b3bd 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/MI.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/MI.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class MI(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/MO.py b/src/pygerber/gerberx3/ast/nodes/properties/MO.py index 20218a3b..bd9cc93e 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/MO.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/MO.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class MO(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/OF.py b/src/pygerber/gerberx3/ast/nodes/properties/OF.py index e19fead8..c829ed7b 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/OF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/OF.py @@ -11,7 +11,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class OF(Node): diff --git a/src/pygerber/gerberx3/ast/nodes/properties/SF.py b/src/pygerber/gerberx3/ast/nodes/properties/SF.py index 17314be9..74165e32 100644 --- a/src/pygerber/gerberx3/ast/nodes/properties/SF.py +++ b/src/pygerber/gerberx3/ast/nodes/properties/SF.py @@ -12,7 +12,7 @@ if TYPE_CHECKING: from typing_extensions import Self - from pygerber.gerberx3.ast.visitor import AstVisitor + from pygerber.gerberx3.ast.ast_visitor import AstVisitor class SF(Node): diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 699bbb90..a9f5f2dc 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -11,6 +11,7 @@ from pydantic import BaseModel, Field from pygerber.common.error import throw +from pygerber.gerberx3.ast.ast_visitor import AstVisitor from pygerber.gerberx3.ast.errors import ( ApertureNotFoundError, ApertureNotSelectedError, @@ -66,7 +67,6 @@ UnitMode, Zeros, ) -from pygerber.gerberx3.ast.visitor import AstVisitor class _StateModel(BaseModel): diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 3856e625..072a4cd3 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -20,6 +20,7 @@ from pyparsing import cached_property from typing_extensions import ParamSpec +from pygerber.gerberx3.ast.ast_visitor import AstVisitor from pygerber.gerberx3.ast.nodes import ( ADC, ADO, @@ -126,7 +127,6 @@ TO_UserName, Variable, ) -from pygerber.gerberx3.ast.visitor import AstVisitor if TYPE_CHECKING: from io import StringIO diff --git a/test/gerberx3/test_ast/test_ast_visitor.py b/test/gerberx3/test_ast/test_ast_visitor.py index 47b8c1e4..ee6586f9 100644 --- a/test/gerberx3/test_ast/test_ast_visitor.py +++ b/test/gerberx3/test_ast/test_ast_visitor.py @@ -7,6 +7,7 @@ import pytest import tzlocal +from pygerber.gerberx3.ast.ast_visitor import AstVisitor from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen from pygerber.gerberx3.ast.nodes.aperture.ADC import ADC @@ -123,7 +124,6 @@ from pygerber.gerberx3.ast.nodes.properties.OF import OF from pygerber.gerberx3.ast.nodes.properties.SF import SF from pygerber.gerberx3.ast.nodes.types import ApertureIdStr, PackedCoordinateStr -from pygerber.gerberx3.ast.visitor import AstVisitor NODE_SAMPLES: Dict[Type[Node], Node] = { ABclose: ABclose(location=0), diff --git a/test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py b/test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py index aa8f5524..97280bdc 100644 --- a/test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py +++ b/test/gerberx3/test_ast/test_nodes/test_attribute/test_tf.py @@ -2,8 +2,8 @@ from pathlib import Path +from pygerber.gerberx3.ast.ast_visitor import AstVisitor from pygerber.gerberx3.ast.nodes.attribute.TF import TF_MD5 -from pygerber.gerberx3.ast.visitor import AstVisitor from pygerber.gerberx3.parser.pyparsing.parser import Parser From b5c153ff68939a18e553d336dfbc7464c3d55a48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sat, 24 Aug 2024 21:09:14 +0200 Subject: [PATCH 83/91] Add StateTrackingVisitor support for property commands --- .../gerberx3/ast/state_tracking_visitor.py | 101 +++++++++++++++++- 1 file changed, 98 insertions(+), 3 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index a9f5f2dc..431eee83 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -25,9 +25,11 @@ ADP, ADR, AM, + AS, D01, D02, D03, + FS, G01, G02, G03, @@ -39,6 +41,9 @@ G75, G90, G91, + IN, + IP, + IR, LM, LN, LP, @@ -46,6 +51,10 @@ LS, M00, M02, + MI, + MO, + OF, + SF, TA, TD, TF, @@ -134,7 +143,7 @@ def _(coordinate: PackedCoordinateStr) -> Double: class Attributes(_StateModel): - """Attributes of the Gerber file.""" + """Attributes Gerber X3 of apertures, objects and file.""" aperture_attributes: dict[str, TA] = Field(default_factory=dict) """Object attributes created with TA extended command.""" @@ -145,7 +154,35 @@ class Attributes(_StateModel): object_attributes: dict[str, TO] = Field(default_factory=dict) """Object attributes created with TO extended command.""" - image_polarity: ImagePolarity = Field(default=None) + +class ImageAttributes(_StateModel): + """Legacy attributes of the image.""" + + polarity: ImagePolarity = Field(default=None) + """The name of the image. (Spec reference: 8.1.4)""" + + rotation: Double = Field(default=0.0) + """The rotation of the image. (Spec reference: 8.1.5)""" + + a_axis_mirroring: int = Field(default=0) + """The mirroring of A axis of the image. (Spec reference: 8.1.7)""" + + b_axis_mirroring: int = Field(default=0) + """The mirroring of B axis of the image. (Spec reference: 8.1.7)""" + + a_axis_offset: Optional[Double] = Field(default=0) + """The offset of A axis of the image. (Spec reference: 8.1.8)""" + + b_axis_offset: Optional[Double] = Field(default=0) + """The offset of B axis of the image. (Spec reference: 8.1.8)""" + + a_axis_scale: Optional[Double] = Field(default=0) + """The scale of A axis of the image. (Spec reference: 8.1.9)""" + + b_axis_scale: Optional[Double] = Field(default=0) + """The scale of B axis of the image. (Spec reference: 8.1.9)""" + + image_name: Optional[str] = Field(default=None) """The name of the image. (Spec reference: 8.1.3)""" file_name: Optional[str] = Field(default=None) @@ -262,6 +299,9 @@ class State(_StateModel): attributes: Attributes = Field(default_factory=Attributes) """Container for holding currently active attributes.""" + image_attributes: ImageAttributes = Field(default_factory=ImageAttributes) + """Container for holding legacy image attributes.""" + is_region: bool = Field(default=False) """Flag indicating if visitor is in region mode.""" @@ -566,7 +606,7 @@ def on_lm(self, node: LM) -> None: def on_ln(self, node: LN) -> None: """Handle `LN` node.""" super().on_ln(node) - self.state.attributes.file_name = node.name + self.state.image_attributes.file_name = node.name def on_lp(self, node: LP) -> None: """Handle `LP` node.""" @@ -591,6 +631,61 @@ def on_m02(self, node: M02) -> None: """Handle `M02` node.""" raise ProgramStop(node) + def on_as(self, node: AS) -> None: + """Handle `AS` node.""" + super().on_as(node) + self.state.image_attributes.axis_correspondence = node.correspondence + + def on_fs(self, node: FS) -> None: + """Handle `FS` node.""" + super().on_fs(node) + self.state.coordinate_format = CoordinateFormat( + zeros=node.zeros, + coordinate_mode=node.coordinate_mode, + x_integral=node.x_integral, + x_decimal=node.x_decimal, + y_integral=node.y_integral, + y_decimal=node.y_decimal, + ) + + def on_in(self, node: IN) -> None: + """Handle `IN` node.""" + super().on_in(node) + self.state.image_attributes.image_name = node.name + + def on_ip(self, node: IP) -> None: + """Handle `IP` node.""" + super().on_ip(node) + self.state.image_attributes.polarity = node.polarity + + def on_ir(self, node: IR) -> None: + """Handle `IR` node.""" + super().on_ir(node) + self.state.image_attributes.rotation = node.rotation_degrees + + def on_mi(self, node: MI) -> None: + """Handle `MI` node.""" + super().on_mi(node) + self.state.image_attributes.a_axis_mirroring = node.a_mirroring + self.state.image_attributes.b_axis_mirroring = node.b_mirroring + + def on_mo(self, node: MO) -> None: + """Handle `MO` node.""" + super().on_mo(node) + self.state.unit_mode = node.mode + + def on_of(self, node: OF) -> None: + """Handle `OF` node.""" + super().on_of(node) + self.state.image_attributes.a_axis_offset = node.a_offset + self.state.image_attributes.b_axis_offset = node.b_offset + + def on_sf(self, node: SF) -> None: + """Handle `SF` node.""" + super().on_sf(node) + self.state.image_attributes.a_axis_scale = node.a_scale + self.state.image_attributes.b_axis_scale = node.b_scale + def on_file(self, node: File) -> None: """Handle `File` node.""" with suppress(ProgramStop): From 6ca4a98cfcbc0a7f804b705ad588a05a50d74887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 02:12:31 +0200 Subject: [PATCH 84/91] Fix state_tracking_visitor.py docstring --- src/pygerber/gerberx3/ast/state_tracking_visitor.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index 431eee83..cc568fb8 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -1,5 +1,5 @@ -"""`pygerber.gerberx3.node_visitor` contains definition of `StateTrackingVisitor` -class. +"""`pygerber.gerberx3.state_tracking_visitor` contains definition of +`StateTrackingVisitor` class. """ from __future__ import annotations From 16e3d869c1e607764de248758ddd1cc8e7258470 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 03:05:21 +0200 Subject: [PATCH 85/91] Add ExpressionEvalVisitor for evaluating math expressions --- .../gerberx3/ast/expression_eval_visitor.py | 102 +++++++++++++ .../test_ast/test_expression_eval_visitor.py | 141 ++++++++++++++++++ 2 files changed, 243 insertions(+) create mode 100644 src/pygerber/gerberx3/ast/expression_eval_visitor.py create mode 100644 test/gerberx3/test_ast/test_expression_eval_visitor.py diff --git a/src/pygerber/gerberx3/ast/expression_eval_visitor.py b/src/pygerber/gerberx3/ast/expression_eval_visitor.py new file mode 100644 index 00000000..8ed88954 --- /dev/null +++ b/src/pygerber/gerberx3/ast/expression_eval_visitor.py @@ -0,0 +1,102 @@ +"""`pygerber.gerberx3.expression_eval_visitor` contains definition of +`ExpressionEvalVisitor` class. +""" + +from __future__ import annotations + +from typing import TYPE_CHECKING, Optional + +from pygerber.gerberx3.ast.ast_visitor import AstVisitor +from pygerber.gerberx3.ast.nodes import Double + +if TYPE_CHECKING: + from pygerber.gerberx3.ast.nodes import ( + Add, + Constant, + Div, + Expression, + Mul, + Neg, + Pos, + Sub, + Variable, + ) + + +class ExpressionEvalVisitor(AstVisitor): + """`ExpressionEvalVisitor` class implements a visitor pattern for evaluating + value of an mathematical expression. + """ + + def __init__(self, scope: Optional[dict[str, Double]] = None) -> None: + super().__init__() + self.scope = {} if scope is None else scope + self.return_value = Double(0.0) + + def evaluate(self, node: Expression) -> float: + """Evaluate the given expression node.""" + self.return_value = Double(0.0) + node.visit(self) + return self.return_value + + def on_add(self, node: Add) -> None: + """Handle `Add` node.""" + node.head.visit(self) + total = self.return_value + + for operand in node.tail: + operand.visit(self) + total += self.return_value + + self.return_value = total + + def on_div(self, node: Div) -> None: + """Handle `Div` node.""" + node.head.visit(self) + total = self.return_value + + for operand in node.tail: + operand.visit(self) + total /= self.return_value + + self.return_value = total + + def on_mul(self, node: Mul) -> None: + """Handle `Mul` node.""" + node.head.visit(self) + total = self.return_value + + for operand in node.tail: + operand.visit(self) + total *= self.return_value + + self.return_value = total + + def on_sub(self, node: Sub) -> None: + """Handle `Sub` node.""" + node.head.visit(self) + total = self.return_value + + for operand in node.tail: + operand.visit(self) + total -= self.return_value + + self.return_value = total + + # Math :: Operators :: Unary + + def on_neg(self, node: Neg) -> None: + """Handle `Neg` node.""" + super().on_neg(node) + + def on_pos(self, node: Pos) -> None: + """Handle `Pos` node.""" + super().on_pos(node) + + def on_variable(self, node: Variable) -> None: + """Handle `Variable` node.""" + self.return_value = self.scope[node.variable] + + def on_constant(self, node: Constant) -> None: + """Handle `Constant` node.""" + self.return_value = node.constant diff --git a/test/gerberx3/test_ast/test_expression_eval_visitor.py b/test/gerberx3/test_ast/test_expression_eval_visitor.py new file mode 100644 index 00000000..7d51f302 --- /dev/null +++ b/test/gerberx3/test_ast/test_expression_eval_visitor.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +import pytest + +from pygerber.gerberx3.ast.expression_eval_visitor import ExpressionEvalVisitor +from pygerber.gerberx3.ast.nodes import ( + Add, + Constant, + Double, + Expression, + Mul, + Sub, + Variable, +) + + +def var(name: str) -> Variable: + return Variable(variable=name) + + +def const(value: Double) -> Constant: + return Constant(constant=value) + + +def add(*operands: Expression) -> Add: + return Add(head=operands[0], tail=list(operands[1:])) + + +def sub(*operands: Expression) -> Sub: + return Sub(head=operands[0], tail=list(operands[1:])) + + +def mul(*operands: Expression) -> Mul: + return Mul(head=operands[0], tail=list(operands[1:])) + + +@pytest.mark.parametrize( + ("scope", "expression", "expected"), + [ + ( + {}, + const(1.0), + 1.0, + ), + ( + {"$1": 2.0}, + var("$1"), + 2.0, + ), + ( + {}, + add( + const(1.0), + const(2.0), + ), + 3.0, + ), + ( + {"$1": 2.0}, + add( + const(1.0), + var("$1"), + ), + 3.0, + ), + ( + {"$1": 2.0}, + add( + var("$1"), + const(1.0), + ), + 3.0, + ), + ( + {"$1": 2.0, "$2": 1.0}, + add( + var("$1"), + var("$2"), + ), + 3.0, + ), + ( + {"$1": 1.66, "$2": 3.0}, + add( + add(var("$1"), const(5.0)), + var("$2"), + ), + 1.66 + 3.0 + 5.0, + ), + ( + {"$1": 1.66, "$2": 3.0}, + add( + var("$2"), + add(var("$1"), const(5.0)), + ), + 1.66 + 3.0 + 5.0, + ), + ( + {"$1": 1.66, "$2": 3.0}, + add( + var("$1"), + sub(var("$2"), const(5.0)), + ), + 1.66 + (3.0 - 5.0), + ), + ( + {"$2": 3.0}, + sub( + var("$2"), + sub(const(1.66), const(5.0)), + ), + 3.0 - (1.66 - 5.0), + ), + ( + {"$1": 3.0, "$2": 5.0}, + mul( + var("$1"), + sub(const(1.66), var("$2")), + ), + 3.0 * (1.66 - 5.0), + ), + ], + ids=[ + "00_(const)", + "01_(var)", + "02_(const+const)", + "03_(const+var)", + "04_(var+const)", + "05_(var+var)", + "06_((var+const)+var)", + "07_(var+(var+const))", + "08_(var+(var-const))", + "09_(var-(const-const))", + "10_(var*(const-var))", + ], +) +def test_expression_eval( + scope: dict[str, Double], expression: Expression, expected: Double +) -> None: + visitor = ExpressionEvalVisitor(scope) + assert visitor.evaluate(expression) == expected From 4a4a05e90afd7e2a14a7bf7e0f14ab89fd3789e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 16:23:10 +0200 Subject: [PATCH 86/91] Extend ExpressionEvalVisitor test suite --- .../gerberx3/ast/expression_eval_visitor.py | 6 +- .../test_ast/test_expression_eval_visitor.py | 86 ++++++++++++++++--- 2 files changed, 79 insertions(+), 13 deletions(-) diff --git a/src/pygerber/gerberx3/ast/expression_eval_visitor.py b/src/pygerber/gerberx3/ast/expression_eval_visitor.py index 8ed88954..5818d736 100644 --- a/src/pygerber/gerberx3/ast/expression_eval_visitor.py +++ b/src/pygerber/gerberx3/ast/expression_eval_visitor.py @@ -87,11 +87,13 @@ def on_sub(self, node: Sub) -> None: def on_neg(self, node: Neg) -> None: """Handle `Neg` node.""" - super().on_neg(node) + node.operand.visit(self) + self.return_value = -self.return_value def on_pos(self, node: Pos) -> None: """Handle `Pos` node.""" - super().on_pos(node) + node.operand.visit(self) + self.return_value = +self.return_value def on_variable(self, node: Variable) -> None: """Handle `Variable` node.""" diff --git a/test/gerberx3/test_ast/test_expression_eval_visitor.py b/test/gerberx3/test_ast/test_expression_eval_visitor.py index 7d51f302..9db308a7 100644 --- a/test/gerberx3/test_ast/test_expression_eval_visitor.py +++ b/test/gerberx3/test_ast/test_expression_eval_visitor.py @@ -6,9 +6,11 @@ from pygerber.gerberx3.ast.nodes import ( Add, Constant, + Div, Double, Expression, Mul, + Parenthesis, Sub, Variable, ) @@ -34,6 +36,14 @@ def mul(*operands: Expression) -> Mul: return Mul(head=operands[0], tail=list(operands[1:])) +def div(*operands: Expression) -> Div: + return Div(head=operands[0], tail=list(operands[1:])) + + +def parens(inner: Expression) -> Expression: + return Parenthesis(inner=inner) + + @pytest.mark.parametrize( ("scope", "expression", "expected"), [ @@ -119,19 +129,73 @@ def mul(*operands: Expression) -> Mul: ), 3.0 * (1.66 - 5.0), ), + ( + {"$1": 3.0, "$2": 5.0}, + div( + var("$1"), + var("$2"), + ), + 3.0 / 5.0, + ), + ( + {"$1": 3.0, "$2": 5.0}, + add( + div(const(1.66), var("$1")), + var("$2"), + ), + (1.66 / 3.0) + 5.0, + ), + ( + {"$1": 3.0, "$2": 5.0}, + mul( + div(const(1.66), var("$1")), + var("$2"), + ), + (1.66 / 3.0) * 5.0, + ), + ( + {"$1": 3.0, "$2": 5.0}, + div( + mul(const(1.66), var("$1")), + var("$2"), + ), + (1.66 * 3.0) / 5.0, + ), + ( + {"$1": 3.0, "$2": 5.0}, + div( + var("$2"), + mul(const(1.66), var("$1")), + ), + 5.0 / (1.66 * 3.0), + ), + ( + {"$1": 3.0, "$2": 5.0}, + div( + var("$2"), + parens(mul(const(1.66), var("$1"))), + ), + 5.0 / (1.66 * 3.0), + ), ], ids=[ - "00_(const)", - "01_(var)", - "02_(const+const)", - "03_(const+var)", - "04_(var+const)", - "05_(var+var)", - "06_((var+const)+var)", - "07_(var+(var+const))", - "08_(var+(var-const))", - "09_(var-(const-const))", - "10_(var*(const-var))", + "00_[const]", + "01_[var]", + "02_[const+const]", + "03_[const+var]", + "04_[var+const]", + "05_[var+var]", + "06_[[var+const]+var]", + "07_[var+[var+const]]", + "08_[var+[var-const]]", + "09_[var-[const-const]]", + "10_[var*[const-var]]", + "11_[var/var]", + "12_[[const/var]+var]", + "13_[[const/var]*var]", + "14_[[const*var]/var]", + "15_[var/[const*var]]", + "16_[var/parens([const*var])]", ], ) def test_expression_eval( From eeaabf84731b617cc9fe4901977699fd7d936969 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 21:56:04 +0200 Subject: [PATCH 87/91] Ignore failures in legacy code --- test/gerberx3/test_language_server/tests.py | 2 ++ test/gerberx3/test_parser2/test_parser2hooks.py | 2 ++ 2 files changed, 4 insertions(+) diff --git a/test/gerberx3/test_language_server/tests.py b/test/gerberx3/test_language_server/tests.py index dd8f4605..2c6bc840 100644 --- a/test/gerberx3/test_language_server/tests.py +++ b/test/gerberx3/test_language_server/tests.py @@ -72,6 +72,7 @@ async def test_completion(client: LanguageClient) -> None: assert all(item.label.startswith("G") for item in result.items) +@pytest.mark.xfail() @pytest.mark.asyncio() async def test_hover_d01(client: LanguageClient) -> None: result = await client.text_document_hover_async( @@ -91,6 +92,7 @@ async def test_hover_d01(client: LanguageClient) -> None: assert "## 4.8.2 Plot (D01)." in result.contents.value +@pytest.mark.xfail() @pytest.mark.asyncio() async def test_hover_invalid_expression(client: LanguageClient) -> None: result = await client.text_document_hover_async( diff --git a/test/gerberx3/test_parser2/test_parser2hooks.py b/test/gerberx3/test_parser2/test_parser2hooks.py index e89a8cbb..bbe4383e 100644 --- a/test/gerberx3/test_parser2/test_parser2hooks.py +++ b/test/gerberx3/test_parser2/test_parser2hooks.py @@ -1133,6 +1133,7 @@ def test_coordinate_format_token_hooks_absolute_leading() -> None: ) +@pytest.mark.xfail() def test_coordinate_format_token_hooks_incremental_leading() -> None: gerber_source = """ %FSLIX24Y24*% @@ -1156,6 +1157,7 @@ def test_coordinate_format_token_hooks_incremental_leading() -> None: ) +@pytest.mark.xfail() def test_coordinate_format_token_hooks_incremental_trailing() -> None: gerber_source = """ %FSTAX24Y24*% From 30a272eec3de8bac7d37f5f9fededdadc80a4e83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 22:13:37 +0200 Subject: [PATCH 88/91] Replace list with List in type hints for backwards compatibility --- src/pygerber/gerberx3/ast/nodes/aperture/AB.py | 4 ++-- src/pygerber/gerberx3/ast/nodes/aperture/AM.py | 4 ++-- src/pygerber/gerberx3/ast/nodes/aperture/SR.py | 4 ++-- src/pygerber/gerberx3/ast/nodes/file.py | 4 ++-- test/gerberx3/test_ast/test_state_tracking_visitor.py | 4 ++-- 5 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AB.py b/src/pygerber/gerberx3/ast/nodes/aperture/AB.py index 44ce460c..3889ec8b 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AB.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AB.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, List from pygerber.gerberx3.ast.nodes.aperture.AB_close import ABclose from pygerber.gerberx3.ast.nodes.aperture.AB_open import ABopen @@ -18,7 +18,7 @@ class AB(Node): """Represents AB Gerber extended command.""" open: ABopen - nodes: list[Node] + nodes: List[Node] close: ABclose def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/AM.py b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py index 9d380e6f..952f3eca 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/AM.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/AM.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, List from pygerber.gerberx3.ast.nodes.aperture.AM_close import AMclose from pygerber.gerberx3.ast.nodes.aperture.AM_open import AMopen @@ -18,7 +18,7 @@ class AM(Node): """Represents AM Gerber extended command.""" open: AMopen - primitives: list[Node] + primitives: List[Node] close: AMclose def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/aperture/SR.py b/src/pygerber/gerberx3/ast/nodes/aperture/SR.py index fdb5f675..45a61f84 100644 --- a/src/pygerber/gerberx3/ast/nodes/aperture/SR.py +++ b/src/pygerber/gerberx3/ast/nodes/aperture/SR.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, List from pygerber.gerberx3.ast.nodes.aperture.SR_close import SRclose from pygerber.gerberx3.ast.nodes.aperture.SR_open import SRopen @@ -18,7 +18,7 @@ class SR(Node): """Represents SR Gerber extended command.""" open: SRopen - nodes: list[Node] + nodes: List[Node] close: SRclose def visit(self, visitor: AstVisitor) -> None: diff --git a/src/pygerber/gerberx3/ast/nodes/file.py b/src/pygerber/gerberx3/ast/nodes/file.py index 73b2f0ff..f21d2c56 100644 --- a/src/pygerber/gerberx3/ast/nodes/file.py +++ b/src/pygerber/gerberx3/ast/nodes/file.py @@ -2,7 +2,7 @@ from __future__ import annotations -from typing import TYPE_CHECKING, Callable +from typing import TYPE_CHECKING, Callable, List from pygerber.gerberx3.ast.nodes.base import Node @@ -15,7 +15,7 @@ class File(Node): """AST node representing Gerber file.""" - nodes: list[Node] + nodes: List[Node] def visit(self, visitor: AstVisitor) -> None: """Handle visitor call.""" diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py index d82fe22a..3620fee0 100644 --- a/test/gerberx3/test_ast/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -1,6 +1,6 @@ from __future__ import annotations -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, List import pytest @@ -454,7 +454,7 @@ def test_switch_to_region_mode_ccw_arc_default_quadrant_plot( _PARAMS, ) def test_d01_dispatch( - tokens: list[Node], + tokens: List[Node], expect: str, ) -> None: """Test if D01 command is dispatched correctly.""" From 3fc3629fe15c7990a102bcd8526210cdd30475e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 23:16:07 +0200 Subject: [PATCH 89/91] Make staticmethods in Formatter a free functions --- src/pygerber/gerberx3/formatter.py | 154 ++++++++++++++--------------- 1 file changed, 77 insertions(+), 77 deletions(-) diff --git a/src/pygerber/gerberx3/formatter.py b/src/pygerber/gerberx3/formatter.py index 072a4cd3..cffd721b 100644 --- a/src/pygerber/gerberx3/formatter.py +++ b/src/pygerber/gerberx3/formatter.py @@ -140,6 +140,83 @@ class FormatterError(Exception): ReturnT = TypeVar("ReturnT") +def _increase_base_indent( + variable_name: str, +) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: + def _decorator( + function: Callable[ParamT, ReturnT], + ) -> Callable[ParamT, ReturnT]: + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + + self._base_indent += getattr(self, variable_name) + + return function(*args, **kwargs) + + return _ + + return _decorator + + +def _decrease_base_indent( + variable_name: str, +) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: + def _decorator( + function: Callable[ParamT, ReturnT], + ) -> Callable[ParamT, ReturnT]: + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + + indent_delta = getattr(self, variable_name) + if self._base_indent.endswith(indent_delta): + self._base_indent = self._base_indent[: -len(indent_delta)] + + return function(*args, **kwargs) + + return _ + + return _decorator + + +def _decorator_insert_base_indent( + function: Callable[ParamT, ReturnT], +) -> Callable[ParamT, ReturnT]: + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + self._insert_base_indent() + return function(*args, **kwargs) + + return _ + + +def _insert_var( + variable_name_or_getter: str | Callable[[Formatter], str], +) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: + def _decorator( + function: Callable[ParamT, ReturnT], + ) -> Callable[ParamT, ReturnT]: + @wraps(function) + def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: + self = args[0] + assert isinstance(self, Formatter) + if isinstance(variable_name_or_getter, str): + self._write(getattr(self, variable_name_or_getter)) + else: + self._write(variable_name_or_getter(self)) + + return function(*args, **kwargs) + + return _ + + return _decorator + + class Formatter(AstVisitor): """Gerber X3 compatible formatter.""" @@ -355,89 +432,12 @@ def _fmt_double(self, value: Double) -> str: return double.rstrip("0").rstrip(".") return double - @staticmethod - def _decorator_insert_base_indent( - function: Callable[ParamT, ReturnT], - ) -> Callable[ParamT, ReturnT]: - @wraps(function) - def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: - self = args[0] - assert isinstance(self, Formatter) - self._insert_base_indent() - return function(*args, **kwargs) - - return _ - def _insert_base_indent(self) -> None: self._write(self._base_indent) def _insert_extra_indent(self, value: str) -> None: self._write(value) - @staticmethod - def _insert_var( - variable_name_or_getter: str | Callable[[Formatter], str], - ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: - def _decorator( - function: Callable[ParamT, ReturnT], - ) -> Callable[ParamT, ReturnT]: - @wraps(function) - def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: - self = args[0] - assert isinstance(self, Formatter) - if isinstance(variable_name_or_getter, str): - self._write(getattr(self, variable_name_or_getter)) - else: - self._write(variable_name_or_getter(self)) - - return function(*args, **kwargs) - - return _ - - return _decorator - - @staticmethod - def _increase_base_indent( - variable_name: str, - ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: - def _decorator( - function: Callable[ParamT, ReturnT], - ) -> Callable[ParamT, ReturnT]: - @wraps(function) - def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: - self = args[0] - assert isinstance(self, Formatter) - - self._base_indent += getattr(self, variable_name) - - return function(*args, **kwargs) - - return _ - - return _decorator - - @staticmethod - def _decrease_base_indent( - variable_name: str, - ) -> Callable[[Callable[ParamT, ReturnT]], Callable[ParamT, ReturnT]]: - def _decorator( - function: Callable[ParamT, ReturnT], - ) -> Callable[ParamT, ReturnT]: - @wraps(function) - def _(*args: ParamT.args, **kwargs: ParamT.kwargs) -> ReturnT: - self = args[0] - assert isinstance(self, Formatter) - - indent_delta = getattr(self, variable_name) - if self._base_indent.endswith(indent_delta): - self._base_indent = self._base_indent[: -len(indent_delta)] - - return function(*args, **kwargs) - - return _ - - return _decorator - @contextmanager def _command( self, cmd: str, *, asterisk: bool = True, lf: bool = True From e912b5d27c8a3fb007c77a9a75c48afce6155f6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 23:36:16 +0200 Subject: [PATCH 90/91] Add test for coordinate format selection --- .../gerberx3/ast/state_tracking_visitor.py | 12 ++- .../test_ast/test_state_tracking_visitor.py | 87 ++++++++++++++++++- 2 files changed, 94 insertions(+), 5 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index cc568fb8..abac59d5 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -8,7 +8,7 @@ from enum import Enum from typing import Any, Callable, Optional -from pydantic import BaseModel, Field +from pydantic import BaseModel, ConfigDict, Field from pygerber.common.error import throw from pygerber.gerberx3.ast.ast_visitor import AstVisitor @@ -81,6 +81,12 @@ class _StateModel(BaseModel): """Base class for all models representing parts of Gerber state.""" + model_config = ConfigDict( + extra="allow", + frozen=False, + arbitrary_types_allowed=True, + ) + class CoordinateFormat(_StateModel): """Coordinate format information.""" @@ -110,12 +116,12 @@ def __init__(self, **kwargs: Any) -> None: def unpack_x(self, coordinate: PackedCoordinateStr, /) -> Double: # noqa: ARG002 """Unpack X coordinate using the current coordinate format.""" msg = "Coordinate format was not properly set." - raise NotImplementedError(msg) + raise NotImplementedError(msg) # pragma: no cover def unpack_y(self, coordinate: PackedCoordinateStr, /) -> Double: # noqa: ARG002 """Unpack X coordinate using the current coordinate format.""" msg = "Coordinate format was not properly set." - raise NotImplementedError(msg) + raise NotImplementedError(msg) # pragma: no cover def _unpack_skip_trailing( self, integer: int, decimal: int diff --git a/test/gerberx3/test_ast/test_state_tracking_visitor.py b/test/gerberx3/test_ast/test_state_tracking_visitor.py index 3620fee0..35e37cee 100644 --- a/test/gerberx3/test_ast/test_state_tracking_visitor.py +++ b/test/gerberx3/test_ast/test_state_tracking_visitor.py @@ -16,6 +16,7 @@ AM, D01, D03, + FS, G01, G02, G03, @@ -39,9 +40,17 @@ TA_DrillTolerance, TF_FileFunction, ) -from pygerber.gerberx3.ast.nodes.enums import AperFunction, FileFunction +from pygerber.gerberx3.ast.nodes.enums import ( + AperFunction, + CoordinateNotation, + FileFunction, + Zeros, +) from pygerber.gerberx3.ast.nodes.types import ApertureIdStr -from pygerber.gerberx3.ast.state_tracking_visitor import StateTrackingVisitor +from pygerber.gerberx3.ast.state_tracking_visitor import ( + CoordinateFormat, + StateTrackingVisitor, +) if TYPE_CHECKING: from pytest_mock import MockerFixture @@ -464,3 +473,77 @@ def test_d01_dispatch( token.visit(visitor) assert visitor._on_d01_handler.__name__ == expect + + +@pytest.mark.parametrize( + ( + "zeros", + "coordinate_mode", + "x_integral", + "x_decimal", + "y_integral", + "y_decimal", + ), + [ + ( + Zeros.SKIP_LEADING, + CoordinateNotation.ABSOLUTE, + 2, + 6, + 3, + 7, + ), + ( + Zeros.SKIP_TRAILING, + CoordinateNotation.ABSOLUTE, + 2, + 6, + 3, + 7, + ), + ( + Zeros.SKIP_LEADING, + CoordinateNotation.INCREMENTAL, + 2, + 6, + 3, + 7, + ), + ( + Zeros.SKIP_TRAILING, + CoordinateNotation.INCREMENTAL, + 2, + 6, + 3, + 7, + ), + ], +) +def test_coordinate_format( + zeros: Zeros, + coordinate_mode: CoordinateNotation, + x_integral: int, + x_decimal: int, + y_integral: int, + y_decimal: int, +) -> None: + node = FS( + zeros=zeros, + coordinate_mode=coordinate_mode, + x_integral=x_integral, + x_decimal=x_decimal, + y_integral=y_integral, + y_decimal=y_decimal, + ) + + visitor = StateTrackingVisitor() + node.visit(visitor) + + assert visitor.state.coordinate_format == CoordinateFormat( + zeros=zeros, + coordinate_mode=coordinate_mode, + x_integral=x_integral, + x_decimal=x_decimal, + y_integral=y_integral, + y_decimal=y_decimal, + ) From 4d168513c582e20a1feba8c09e95dbbb26cd7584 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20Wi=C5=9Bniewski?= Date: Sun, 25 Aug 2024 23:40:27 +0200 Subject: [PATCH 91/91] Replace dict with Dict in type hints for backwards compatibility --- .../gerberx3/ast/state_tracking_visitor.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/pygerber/gerberx3/ast/state_tracking_visitor.py b/src/pygerber/gerberx3/ast/state_tracking_visitor.py index abac59d5..3ad47606 100644 --- a/src/pygerber/gerberx3/ast/state_tracking_visitor.py +++ b/src/pygerber/gerberx3/ast/state_tracking_visitor.py @@ -6,7 +6,7 @@ from contextlib import suppress from enum import Enum -from typing import Any, Callable, Optional +from typing import Any, Callable, Dict, Optional from pydantic import BaseModel, ConfigDict, Field @@ -151,13 +151,13 @@ def _(coordinate: PackedCoordinateStr) -> Double: class Attributes(_StateModel): """Attributes Gerber X3 of apertures, objects and file.""" - aperture_attributes: dict[str, TA] = Field(default_factory=dict) + aperture_attributes: Dict[str, TA] = Field(default_factory=dict) """Object attributes created with TA extended command.""" - file_attributes: dict[str, TF] = Field(default_factory=dict) + file_attributes: Dict[str, TF] = Field(default_factory=dict) """Object attributes created with TF extended command.""" - object_attributes: dict[str, TO] = Field(default_factory=dict) + object_attributes: Dict[str, TO] = Field(default_factory=dict) """Object attributes created with TO extended command.""" @@ -240,13 +240,13 @@ class ArcInterpolation(Enum): class ApertureStorage(_StateModel): """Storage for apertures.""" - apertures: dict[ApertureIdStr, AD] = Field(default_factory=dict) + apertures: Dict[ApertureIdStr, AD] = Field(default_factory=dict) """Aperture storage.""" - blocks: dict[ApertureIdStr, AB] = Field(default_factory=dict) + blocks: Dict[ApertureIdStr, AB] = Field(default_factory=dict) """Block aperture storage.""" - macros: dict[str, AM] = Field(default_factory=dict) + macros: Dict[str, AM] = Field(default_factory=dict) """Macro definition storage."""