Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

PyGerber 3.0.0 comment attributes handling #297

Open
Argmaster opened this issue Sep 21, 2024 · 0 comments
Open

PyGerber 3.0.0 comment attributes handling #297

Argmaster opened this issue Sep 21, 2024 · 0 comments
Assignees
Labels
enhancement New feature or request
Milestone

Comments

@Argmaster
Copy link
Owner

Argmaster commented Sep 21, 2024

Standard attributes as comments are birefly mentioned in The Gerber Layer Format Specification 2024.05 in section 4.1 Comment (G04), dedicated description is available in section 5.1.1 Comment attributes.

There are at least two ways to approach how this can be handled from perspective of AST node classes:

  • create separate collection of classes mrroring src/pygerber/gerberx3/ast/nodes/attribute but dedicated for comment attributes.
  • add is_comment member to each attribute base class (TA, TD, TF, TO)

First approach requires a lot of code duplication, will require separate set of on_<attr-name> callbacks on AstVisitor and in general will unnecessarily complicate implementation, in my humble opition, since comment attributes are bound to behave like the X3 standalone counterparts.

Therefore I would advise choosing second approach. This will require adding is_comment=False for all existing construction calls for attributes in Gerber grammar.

src/pygerber/gerberx3/ast/nodes/attribute/TA.py

class TA(Node):
    """Represents TA Gerber extended command."""

    is_standalone: bool

    @property
    def attribute_name(self) -> str:
        """Get attribute name."""
        raise NotImplementedError

Afterwards, separate grammar definition for all the attributes will be needed, as % signs must be a part of standalone attributes while comment attributes must not contain them.

src/pygerber/gerberx3/parser/pyparsing/grammar.py:582

    @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)
                )
            )
            .set_name("TA<UserName>")
            .set_parse_action(self.make_unpack_callback(TA_UserName, is_standalone=True))
        )

G04 with pp.MatchFirst for comment attributes must be placed before general G04.

src/pygerber/gerberx3/parser/pyparsing/grammar.py:1174

    @pp.cached_property
    def g_codes(self) -> pp.ParserElement:
        """Create a parser element capable of parsing G-codes."""
        g04_comment = self._command(
            pp.Regex(r"G0*4") + pp.Opt(self.string),
        ).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))

        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))
            )

        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_non_standalone

        return pp.MatchFirst(
            [
                # ----------------------------------------------------------
                # COMMENT ATTRIBUTES SHOULD BE INSERTED HERE
                # ----------------------------------------------------------
                g04_comment,
                *(
                    _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,
                            G02,
                            G03,
                            G36,
                            G37,
                            G54,
                            G55,
                            G70,
                            G71,
                            G74,
                            G75,
                            G90,
                            G91,
                        )
                    )
                ),
            ]
        )

Comment attributes syntax should be defined similarily to standalone attributes:

src/pygerber/gerberx3/parser/pyparsing/grammar.py:558

    @pp.cached_property
    def attribute(self) -> pp.ParserElement:
        """Create a parser element capable of parsing attributes."""
        return pp.MatchFirst(
            [
                self.ta(),
                self.td(),
                self.tf(),
                self.to(),
            ]
        )

src/pygerber/gerberx3/parser/pyparsing/grammar.py:570

    def ta(self) -> pp.ParserElement:
        """Create a parser element capable of parsing TA attributes."""
        return pp.MatchFirst(
            [
                self._ta_user_name,
                self._ta_aper_function,
                self._ta_drill_tolerance,
                self._ta_flash_text,
            ]
        )

But with _comment suffix. eg.

    def ta_comment(self) -> pp.ParserElement:
        """Create a parser element capable of parsing TA comment attributes."""
        return pp.MatchFirst(
            [
                self._ta_user_name_comment,
                self._ta_aper_function_comment,
                self._ta_drill_tolerance_comment,
                self._ta_flash_text_comment,
            ]
        )

Keep in mind that you cannot use self._extended_command as it includes % at the beginning and the end. I suppose example comment attribute definition could look like this:

    @pp.cached_property
    def _ta_user_name_comment(self) -> pp.ParserElement:
        return (
            self._comment_attribute(
                self._ta
                + self.user_name
                + pp.ZeroOrMore(
                    self.comma
                    + self.field.set_results_name("fields", list_all_matches=True)
                )
            )
            .set_name("TA<UserName>")
            .set_parse_action(self.make_unpack_callback(TA_UserName, is_standalone=False))
        )

Where self._comment_attribute has to be implemented and could look similarily to self._extended_command, lets say:

    def _comment_attribute(self, inner: pp.ParserElement) -> pp.ParserElement:
        return Literal("G04") + Literal("#@!") + inner + self._asterisk

Notice that G04 is a separate literal to allow undefined amount of whitespace characters between G04 and #@!. Make sure that I didn't mess up the special sequence, I am not sure that it is #@!

All of this will inevitebly require changes in test suite to account for new is_standalone member of attribute node classes.
It would also be good to prepare test assets similar to test/assets/gerberx3/tokens/attribute for comment attributes. Gerber assets created should automatically get included in test suite thanks to how test/gerberx3/test_parser/test_pyparsing/test_parser.py:test_pyparsing_parser_grammar() works.

@Argmaster Argmaster added the enhancement New feature or request label Sep 21, 2024
@Argmaster Argmaster added this to the 3.0.0a2 milestone Sep 21, 2024
@Argmaster Argmaster mentioned this issue Sep 21, 2024
16 tasks
@Argmaster Argmaster modified the milestones: 3.0.0a2, 3.0.0a3 Sep 30, 2024
@Argmaster Argmaster mentioned this issue Sep 30, 2024
13 tasks
@Argmaster Argmaster mentioned this issue Oct 13, 2024
14 tasks
@Argmaster Argmaster modified the milestones: 3.0.0a3, 3.0.0a4 Oct 13, 2024
@Argmaster Argmaster mentioned this issue Nov 24, 2024
38 tasks
@Argmaster Argmaster modified the milestones: 3.0.0a4, 3.0.0b1 Nov 24, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Status: Todo
Development

No branches or pull requests

2 participants