autoeagle.core API documentation

# autoeagle.core

View Source

      1import argparse
      2import inspect
      3import itertools
      4import shutil
      5from pathlib import Path
      6from xml.etree import ElementTree
      7
      8from autoeagle import autoeagle_config
      9
     10
     11class EagleFile:
     12    """Base class representing and parsing an Eagle file."""
     13
     14    def __init__(self, file: str = None):
     15        """:param file: Path to an Eagle file.
     16        If None, getting the path from command line args
     17        will be attempted. If that fails, the user will
     18        be prompted for a file path.
     19
     20        A backup of the file will be created during the
     21        initialization."""
     22        if file:
     23            file = Path(file)
     24        else:
     25            args = self.get_args()
     26            file = args.file
     27        self.path = file
     28        self.tree = ElementTree.parse(self.path)
     29        self.root = self.tree.getroot()
     30
     31        self.drawing = self.root.find("drawing")
     32
     33        self.settings = self.drawing.find("settings").findall("setting")
     34        self.grid = self.drawing.find("grid")
     35        self.layers = self.drawing.find("layers").findall("layer")
     36
     37        shutil.copy(self.path, str(self.path.with_stem(f"{self.path.stem}_Bckup")))
     38
     39    def get_parser(self) -> argparse.ArgumentParser:
     40        """Returns an argparse ArgumentParser instance.
     41        This is in a separate function so additional args
     42        can be added in subclass without having to
     43        override the rest of the self.get_args() function.
     44
     45        Only arg added here is for a file path."""
     46        parser = argparse.ArgumentParser()
     47        parser.add_argument(
     48            "file",
     49            type=str,
     50            nargs="?",
     51            default=None,
     52            help=""" Path to an Eagle file. """,
     53        )
     54        return parser
     55
     56    def get_args(self) -> argparse.Namespace:
     57        """Get file path from command line
     58        or prompt user for file path."""
     59        parser = self.get_parser()
     60        args = parser.parse_args()
     61        if not args.file:
     62            args.file = input("Enter path to an Eagle file: ")
     63        args.file = Path(args.file)
     64        return args
     65
     66    def save(self):
     67        self.tree.write(str(self.path), encoding="utf-8")
     68
     69    def get_names(self, elements: list[ElementTree.Element]) -> list[str]:
     70        """Return a list of 'name' attributes for each
     71        ElementTree Element in 'elements' list."""
     72        return self.get_attribute("name", elements)
     73
     74    def get_attribute(
     75        self, attribute: str, elements: list[ElementTree.Element]
     76    ) -> list[str]:
     77        """Return a list of the specified attribute for a list of elements.
     78
     79        >>> schem = autoeagle.Schematic("schem.sch")
     80        >>> part_values = schem.get_attribute("value", schem.parts)
     81        >>> brd = autoeagle.Board("board.brd")
     82        >>> layer_colors = brd.get_attribute("color", brd.layers)"""
     83        return [element.get(attribute) for element in elements]
     84
     85
     86class Schematic(EagleFile):
     87    """Class representing an Eagle schematic file."""
     88
     89    def __init__(self, *args, **kwargs):
     90        super().__init__(*args, **kwargs)
     91
     92        self.schematic = self.drawing.find("schematic")
     93
     94        self.libraries = self.schematic.find("libraries").findall("library")
     95        self.classes = self.schematic.find("classes")
     96        self.parts = self.schematic.find("parts").findall("part")
     97        self.sheets = self.schematic.find("sheets").findall("sheet")
     98
     99
    100class Board(EagleFile):
    101    """Class representing an Eagle board file."""
    102
    103    def __init__(self, *args, **kwargs):
    104        super().__init__(*args, **kwargs)
    105
    106        self.board = self.drawing.find("board")
    107
    108        # This contains the 'wire' elements outlining the board
    109        # No idea why Autodesk calls it 'plain'
    110        self.plain = self.board.find("plain")
    111        self.outline = self.plain
    112        self.libraries = self.board.find("libraries").findall("library")
    113        self.classes = self.board.find("classes").findall("class")
    114        self.designrules = self.board.find("designrules").findall("param")
    115        self.autorouter = self.board.find("autorouter")
    116        # These are all the actual parts on the board
    117        self.elements = self.board.find("elements").findall("element")
    118        self.parts = self.elements
    119        self.signals = self.board.find("signals").findall("signal")
    120
    121    def get_packages(self, attribute: str = None) -> list[str | ElementTree.Element]:
    122        """Return a list of package elements that are used
    123        in the board file.
    124
    125        :param attribute: If given, the returned list will only
    126        contain the given attribute for each element instead of
    127        the whole ElementTree.Element ."""
    128        packages = [
    129            package
    130            for package in self.board.findall(".//libraries/library/packages/package")
    131        ]
    132        return self.get_attribute(attribute, packages) if attribute else packages
    133
    134    def get_smd_packages(
    135        self, attribute: str = None
    136    ) -> list[str | ElementTree.Element]:
    137        """Return a list of smd packages used in the board file.
    138
    139        :param attribute: If given, the returned list will only
    140        contain the given attribute for each element instead of
    141        the whole ElementTree.Element ."""
    142        packages = [
    143            package
    144            for package in self.get_packages()
    145            if type(package.find("smd")) is ElementTree.Element
    146        ]
    147        return self.get_attribute(attribute, packages) if attribute else packages
    148
    149    def get_smd_parts(self, attribute: str = None) -> list[str | ElementTree.Element]:
    150        """Filter self.parts for smd parts.
    151
    152        :param attribute: If given, the returned list will only
    153        contain the given attribute for each element instead of
    154        the whole ElementTree.Element ."""
    155        smd_packages = self.get_smd_packages("name")
    156        parts = [part for part in self.parts if part.get("package") in smd_packages]
    157        return self.get_attribute(attribute, parts) if attribute else parts
    158
    159    def get_bounds(self) -> dict[str, float]:
    160        """Return a dictionary representing
    161        the minimum and maximum x and y coordinates
    162        of the board."""
    163        wires = [
    164            wire for wire in self.plain.findall("wire") if wire.get("layer") == "20"
    165        ]
    166        get_coords = lambda n: [
    167            float(v)
    168            for wire in wires
    169            for v in itertools.chain([wire.get(f"{n}1"), wire.get(f"{n}2")])
    170        ]
    171        x = get_coords("x")
    172        y = get_coords("y")
    173        return {"x0": min(x), "xf": max(x), "y0": min(y), "yf": max(y)}
    174
    175    def get_dimensions(self) -> tuple[float, float]:
    176        """Returns a two-tuple containing
    177        the width and height of the board's
    178        bounding box."""
    179        bounds = self.get_bounds()
    180        return (bounds["xf"] - bounds["x0"], bounds["yf"] - bounds["y0"])
    181
    182    def get_center(self) -> tuple[float, float]:
    183        """Returns a two-tuple containing
    184        the x,y center point of the board's
    185        bounding box."""
    186        bounds = self.get_bounds()
    187        midpoint = lambda n: (bounds[f"{n}f"] + bounds[f"{n}0"]) * 0.5
    188        return (midpoint("x"), midpoint("y"))
    189
    190    def get_area(self) -> float:
    191        """Returns the area of the board's bounding box.
    192        Note: This is not the same as the board's
    193        physical area for shapes other than rectangular ones.
    194        It also doesn't account for cut-outs."""
    195        wxh = self.get_dimensions()
    196        return wxh[0] * wxh[1]
    197
    198    def get_visible_layers(self) -> list[str]:
    199        """Returns a list of visible layers."""
    200        return [
    201            layer.get("name") for layer in self.layers if layer.get("visible") == "yes"
    202        ]
    203
    204
    205class ScriptWriter:
    206    """Class for writing Eagle script files.
    207    These files use the Editor command syntax
    208    as documented in the Eagle manual.
    209    Scipts generated by this class will be invoked
    210    and executed within Eagle after returning
    211    from the invoking Ulp file for your Python
    212    script.
    213
    214    The preferred usage of this class is
    215    with a context manager. When used with a context
    216    manager, you don't need to remember to
    217    call the save() method at the end of the script.
    218    You also don't need to remember to add 'write;'
    219    as the last command, as the save() function adds it for you.
    220    Commands can be added with the '+=' operator.
    221    The Eagle syntax specifies each command ends with
    222    ';'. If you leave it off of an added command,
    223    it will be added for you.
    224    Some script commands are provided as member functions.
    225    For further script options, refer to the Eagle help menu.
    226
    227    e.x. Moving the center of the board to (0,0)
    228    >>> brd = Board()
    229    >>> with ScriptWriter(brd.path) as scr:
    230    >>>     center = brd.get_center()
    231    >>>     scr.group_all() # Adds "group all;" to commands
    232    >>>     scr += f"move (>0 0) (-{center[0]} -{center[1]})"""
    233
    234    def __init__(
    235        self,
    236        script_name: str = None,
    237    ):
    238        """The .scr file will be saved to a "scripts" subfolder within the same
    239        directory as the eagle_file param.
    240
    241        :param eagle_file: The path to the Eagle file
    242        the script is being written for.
    243
    244        :param script_name: The name of the script creating the instance.
    245        If None, the inspect module will be used to determine the script name."""
    246        if not script_name:
    247            script_name = (Path(inspect.stack()[1].filename)).stem
    248        scriptsdir = Path(autoeagle_config.load_config()["scriptsdir"])
    249        self.path = scriptsdir / f"{script_name}.scr"
    250        self.commands = []
    251
    252    def __enter__(self):
    253        return self
    254
    255    def __exit__(self, *args):
    256        self.save()
    257
    258    def __iadd__(self, command: str):
    259        if not command.endswith(";"):
    260            command += ";"
    261        self.commands.append(command)
    262        return self
    263
    264    def save(self):
    265        self += "write"
    266        self.path.write_text("\n".join(self.commands))
    267
    268    def group_all(self):
    269        """Put everything on a visible layer into a group."""
    270        self += "group all"
    271
    272    def display_all(self):
    273        """Make every layer in the design visible."""
    274        self += "display all"
    275
    276    def display_layers(self, layers: list[str]):
    277        """Displays only the layers in 'layers'."""
    278        self += f"display none {' '.join(layers)}"
    279
    280    def _coordinate_string(self, coordinates: list[tuple[float, float]]) -> str:
    281        """Return command snippet for coordinates to be used with draw commands.
    282
    283        :param coordinates: A list of two-tuples. A string will
    284        be returned specifying the coordinates in the same order
    285        as the param."""
    286        return " ".join(
    287            f"({coordinate[0]} {coordinate[1]})" for coordinate in coordinates
    288        )
    289
    290    def _rectangle_coordinates(
    291        self, x0: float, xf: float, y0: float, yf: float
    292    ) -> list[tuple[float, float]]:
    293        """Return a list of two-tuples that describe
    294        a rectangle given the coordinates of the sides.
    295
    296        :param x0,xf,y0,yf: Coordinates describing
    297        the sides of a rectangle."""
    298
    299        return [(x0, y0), (x0, yf), (xf, yf), (xf, y0), (x0, y0)]
    300
    301    def draw_group(self, coordinates: list[tuple[float, float]]):
    302        """Draw a group bounded by 'coordinates'.
    303
    304        :param coordinates: A list of two-tuples specifying
    305        the path to take when drawing the group."""
    306        self += f"group {self._coordinate_string(coordinates)}"
    307
    308    def draw_rectangle_group(self, x0: float, xf: float, y0: float, yf: float):
    309        """Draw a rectangular group whose sides are described by the given coordinates."""
    310        self.draw_group(self._rectangle_coordinates(x0, xf, y0, yf))
    311
    312    def draw(
    313        self,
    314        coordinates: list[tuple[float, float]],
    315        layer: str = None,
    316        width: float = None,
    317    ):
    318        """Draw a line throught the given series of coordinate points.
    319
    320        :param coordinates: A list of two-tuples specifying
    321        the path to take when drawing the line.
    322
    323        :param layer: The layer to draw the line on.
    324        If None, the currently active layer is used.
    325
    326        :param width: The width of the line.
    327        If None, the currently active width is used.
    328
    329        >>> # Draw a line of width 1 on layer "tplace" from (0 0) to (10 0) to (3 5)
    330        >>> draw([(0, 0), (10, 0), (3,5)], "tPlace", 1)"""
    331
    332        if layer:
    333            self += f"layer {layer}"
    334        if width:
    335            self += f"width {width}"
    336        self += f"line {self._coordinate_string(coordinates)}"
    337
    338    def draw_rectangle(
    339        self,
    340        x0: float,
    341        xf: float,
    342        y0: float,
    343        yf: float,
    344        layer: str = None,
    345        width: float = None,
    346    ):
    347        """Draw a rectangle whose sides are described by the given
    348        coordinates: x0, xf, y0, yf.
    349
    350        :param layer: The layer to draw the line on.
    351        If None, the currently active layer is used.
    352
    353        :param width: The width of the line.
    354        If None, the currently active width is used.
    355
    356        >>> # Draw a 10 x 10 board outline of width 1 centered at (0, 0)
    357        >>> draw_rectangle(-5, 5, -5, 5, "Dimension", 1)"""
    358        self += self.draw(self._rectangle_coordinates(x0, xf, y0, yf), layer, width)
    359
    360    def rats_nest(self, ripup_polygons: bool = True):
    361        """Run the rats nest command
    362
    363        :param ripup_polygons: If True, run 'ripup @'
    364        after running rats nest. Eagle gets real
    365        slow when polygons are visible."""
    366
    367        self += "ratsnest"
    368        if ripup_polygons:
    369            self += "ripup @"

class EagleFile: View Source

    12class EagleFile:
    13    """Base class representing and parsing an Eagle file."""
    14
    15    def __init__(self, file: str = None):
    16        """:param file: Path to an Eagle file.
    17        If None, getting the path from command line args
    18        will be attempted. If that fails, the user will
    19        be prompted for a file path.
    20
    21        A backup of the file will be created during the
    22        initialization."""
    23        if file:
    24            file = Path(file)
    25        else:
    26            args = self.get_args()
    27            file = args.file
    28        self.path = file
    29        self.tree = ElementTree.parse(self.path)
    30        self.root = self.tree.getroot()
    31
    32        self.drawing = self.root.find("drawing")
    33
    34        self.settings = self.drawing.find("settings").findall("setting")
    35        self.grid = self.drawing.find("grid")
    36        self.layers = self.drawing.find("layers").findall("layer")
    37
    38        shutil.copy(self.path, str(self.path.with_stem(f"{self.path.stem}_Bckup")))
    39
    40    def get_parser(self) -> argparse.ArgumentParser:
    41        """Returns an argparse ArgumentParser instance.
    42        This is in a separate function so additional args
    43        can be added in subclass without having to
    44        override the rest of the self.get_args() function.
    45
    46        Only arg added here is for a file path."""
    47        parser = argparse.ArgumentParser()
    48        parser.add_argument(
    49            "file",
    50            type=str,
    51            nargs="?",
    52            default=None,
    53            help=""" Path to an Eagle file. """,
    54        )
    55        return parser
    56
    57    def get_args(self) -> argparse.Namespace:
    58        """Get file path from command line
    59        or prompt user for file path."""
    60        parser = self.get_parser()
    61        args = parser.parse_args()
    62        if not args.file:
    63            args.file = input("Enter path to an Eagle file: ")
    64        args.file = Path(args.file)
    65        return args
    66
    67    def save(self):
    68        self.tree.write(str(self.path), encoding="utf-8")
    69
    70    def get_names(self, elements: list[ElementTree.Element]) -> list[str]:
    71        """Return a list of 'name' attributes for each
    72        ElementTree Element in 'elements' list."""
    73        return self.get_attribute("name", elements)
    74
    75    def get_attribute(
    76        self, attribute: str, elements: list[ElementTree.Element]
    77    ) -> list[str]:
    78        """Return a list of the specified attribute for a list of elements.
    79
    80        >>> schem = autoeagle.Schematic("schem.sch")
    81        >>> part_values = schem.get_attribute("value", schem.parts)
    82        >>> brd = autoeagle.Board("board.brd")
    83        >>> layer_colors = brd.get_attribute("color", brd.layers)"""
    84        return [element.get(attribute) for element in elements]

Base class representing and parsing an Eagle file.

EagleFile(file: str = None) View Source

    15    def __init__(self, file: str = None):
    16        """:param file: Path to an Eagle file.
    17        If None, getting the path from command line args
    18        will be attempted. If that fails, the user will
    19        be prompted for a file path.
    20
    21        A backup of the file will be created during the
    22        initialization."""
    23        if file:
    24            file = Path(file)
    25        else:
    26            args = self.get_args()
    27            file = args.file
    28        self.path = file
    29        self.tree = ElementTree.parse(self.path)
    30        self.root = self.tree.getroot()
    31
    32        self.drawing = self.root.find("drawing")
    33
    34        self.settings = self.drawing.find("settings").findall("setting")
    35        self.grid = self.drawing.find("grid")
    36        self.layers = self.drawing.find("layers").findall("layer")
    37
    38        shutil.copy(self.path, str(self.path.with_stem(f"{self.path.stem}_Bckup")))

###### Parameters

- file: Path to an Eagle file. If None, getting the path from command line args will be attempted. If that fails, the user will be prompted for a file path.

A backup of the file will be created during the initialization.

def get_parser(self) -> argparse.ArgumentParser: View Source

    40    def get_parser(self) -> argparse.ArgumentParser:
    41        """Returns an argparse ArgumentParser instance.
    42        This is in a separate function so additional args
    43        can be added in subclass without having to
    44        override the rest of the self.get_args() function.
    45
    46        Only arg added here is for a file path."""
    47        parser = argparse.ArgumentParser()
    48        parser.add_argument(
    49            "file",
    50            type=str,
    51            nargs="?",
    52            default=None,
    53            help=""" Path to an Eagle file. """,
    54        )
    55        return parser

Returns an argparse ArgumentParser instance. This is in a separate function so additional args can be added in subclass without having to override the rest of the self.get_args() function.

Only arg added here is for a file path.

def get_args(self) -> argparse.Namespace: View Source

    57    def get_args(self) -> argparse.Namespace:
    58        """Get file path from command line
    59        or prompt user for file path."""
    60        parser = self.get_parser()
    61        args = parser.parse_args()
    62        if not args.file:
    63            args.file = input("Enter path to an Eagle file: ")
    64        args.file = Path(args.file)
    65        return args

Get file path from command line or prompt user for file path.

def save(self): View Source

    67    def save(self):
    68        self.tree.write(str(self.path), encoding="utf-8")

def get_names(self, elements: list[xml.etree.ElementTree.Element]) -> list[str]: View Source

    70    def get_names(self, elements: list[ElementTree.Element]) -> list[str]:
    71        """Return a list of 'name' attributes for each
    72        ElementTree Element in 'elements' list."""
    73        return self.get_attribute("name", elements)

Return a list of 'name' attributes for each ElementTree Element in 'elements' list.

def get_attribute( self, attribute: str, elements: list[xml.etree.ElementTree.Element]) -> list[str]: View Source

    75    def get_attribute(
    76        self, attribute: str, elements: list[ElementTree.Element]
    77    ) -> list[str]:
    78        """Return a list of the specified attribute for a list of elements.
    79
    80        >>> schem = autoeagle.Schematic("schem.sch")
    81        >>> part_values = schem.get_attribute("value", schem.parts)
    82        >>> brd = autoeagle.Board("board.brd")
    83        >>> layer_colors = brd.get_attribute("color", brd.layers)"""
    84        return [element.get(attribute) for element in elements]

Return a list of the specified attribute for a list of elements.

    >>> schem = autoeagle.Schematic("schem.sch")
    >>> part_values = schem.get_attribute("value", schem.parts)
    >>> brd = autoeagle.Board("board.brd")
    >>> layer_colors = brd.get_attribute("color", brd.layers)

class Schematic(EagleFile): View Source

    87class Schematic(EagleFile):
    88    """Class representing an Eagle schematic file."""
    89
    90    def __init__(self, *args, **kwargs):
    91        super().__init__(*args, **kwargs)
    92
    93        self.schematic = self.drawing.find("schematic")
    94
    95        self.libraries = self.schematic.find("libraries").findall("library")
    96        self.classes = self.schematic.find("classes")
    97        self.parts = self.schematic.find("parts").findall("part")
    98        self.sheets = self.schematic.find("sheets").findall("sheet")

Class representing an Eagle schematic file.

Schematic(*args, **kwargs) View Source

    90    def __init__(self, *args, **kwargs):
    91        super().__init__(*args, **kwargs)
    92
    93        self.schematic = self.drawing.find("schematic")
    94
    95        self.libraries = self.schematic.find("libraries").findall("library")
    96        self.classes = self.schematic.find("classes")
    97        self.parts = self.schematic.find("parts").findall("part")
    98        self.sheets = self.schematic.find("sheets").findall("sheet")

###### Parameters

- file: Path to an Eagle file. If None, getting the path from command line args will be attempted. If that fails, the user will be prompted for a file path.

A backup of the file will be created during the initialization.

##### Inherited Members

EagleFile
  get_parser
  get_args
  save
  get_names
  get_attribute

class Board(EagleFile): View Source

    101class Board(EagleFile):
    102    """Class representing an Eagle board file."""
    103
    104    def __init__(self, *args, **kwargs):
    105        super().__init__(*args, **kwargs)
    106
    107        self.board = self.drawing.find("board")
    108
    109        # This contains the 'wire' elements outlining the board
    110        # No idea why Autodesk calls it 'plain'
    111        self.plain = self.board.find("plain")
    112        self.outline = self.plain
    113        self.libraries = self.board.find("libraries").findall("library")
    114        self.classes = self.board.find("classes").findall("class")
    115        self.designrules = self.board.find("designrules").findall("param")
    116        self.autorouter = self.board.find("autorouter")
    117        # These are all the actual parts on the board
    118        self.elements = self.board.find("elements").findall("element")
    119        self.parts = self.elements
    120        self.signals = self.board.find("signals").findall("signal")
    121
    122    def get_packages(self, attribute: str = None) -> list[str | ElementTree.Element]:
    123        """Return a list of package elements that are used
    124        in the board file.
    125
    126        :param attribute: If given, the returned list will only
    127        contain the given attribute for each element instead of
    128        the whole ElementTree.Element ."""
    129        packages = [
    130            package
    131            for package in self.board.findall(".//libraries/library/packages/package")
    132        ]
    133        return self.get_attribute(attribute, packages) if attribute else packages
    134
    135    def get_smd_packages(
    136        self, attribute: str = None
    137    ) -> list[str | ElementTree.Element]:
    138        """Return a list of smd packages used in the board file.
    139
    140        :param attribute: If given, the returned list will only
    141        contain the given attribute for each element instead of
    142        the whole ElementTree.Element ."""
    143        packages = [
    144            package
    145            for package in self.get_packages()
    146            if type(package.find("smd")) is ElementTree.Element
    147        ]
    148        return self.get_attribute(attribute, packages) if attribute else packages
    149
    150    def get_smd_parts(self, attribute: str = None) -> list[str | ElementTree.Element]:
    151        """Filter self.parts for smd parts.
    152
    153        :param attribute: If given, the returned list will only
    154        contain the given attribute for each element instead of
    155        the whole ElementTree.Element ."""
    156        smd_packages = self.get_smd_packages("name")
    157        parts = [part for part in self.parts if part.get("package") in smd_packages]
    158        return self.get_attribute(attribute, parts) if attribute else parts
    159
    160    def get_bounds(self) -> dict[str, float]:
    161        """Return a dictionary representing
    162        the minimum and maximum x and y coordinates
    163        of the board."""
    164        wires = [
    165            wire for wire in self.plain.findall("wire") if wire.get("layer") == "20"
    166        ]
    167        get_coords = lambda n: [
    168            float(v)
    169            for wire in wires
    170            for v in itertools.chain([wire.get(f"{n}1"), wire.get(f"{n}2")])
    171        ]
    172        x = get_coords("x")
    173        y = get_coords("y")
    174        return {"x0": min(x), "xf": max(x), "y0": min(y), "yf": max(y)}
    175
    176    def get_dimensions(self) -> tuple[float, float]:
    177        """Returns a two-tuple containing
    178        the width and height of the board's
    179        bounding box."""
    180        bounds = self.get_bounds()
    181        return (bounds["xf"] - bounds["x0"], bounds["yf"] - bounds["y0"])
    182
    183    def get_center(self) -> tuple[float, float]:
    184        """Returns a two-tuple containing
    185        the x,y center point of the board's
    186        bounding box."""
    187        bounds = self.get_bounds()
    188        midpoint = lambda n: (bounds[f"{n}f"] + bounds[f"{n}0"]) * 0.5
    189        return (midpoint("x"), midpoint("y"))
    190
    191    def get_area(self) -> float:
    192        """Returns the area of the board's bounding box.
    193        Note: This is not the same as the board's
    194        physical area for shapes other than rectangular ones.
    195        It also doesn't account for cut-outs."""
    196        wxh = self.get_dimensions()
    197        return wxh[0] * wxh[1]
    198
    199    def get_visible_layers(self) -> list[str]:
    200        """Returns a list of visible layers."""
    201        return [
    202            layer.get("name") for layer in self.layers if layer.get("visible") == "yes"
    203        ]

Class representing an Eagle board file.

Board(*args, **kwargs) View Source

    104    def __init__(self, *args, **kwargs):
    105        super().__init__(*args, **kwargs)
    106
    107        self.board = self.drawing.find("board")
    108
    109        # This contains the 'wire' elements outlining the board
    110        # No idea why Autodesk calls it 'plain'
    111        self.plain = self.board.find("plain")
    112        self.outline = self.plain
    113        self.libraries = self.board.find("libraries").findall("library")
    114        self.classes = self.board.find("classes").findall("class")
    115        self.designrules = self.board.find("designrules").findall("param")
    116        self.autorouter = self.board.find("autorouter")
    117        # These are all the actual parts on the board
    118        self.elements = self.board.find("elements").findall("element")
    119        self.parts = self.elements
    120        self.signals = self.board.find("signals").findall("signal")

###### Parameters

- file: Path to an Eagle file. If None, getting the path from command line args will be attempted. If that fails, the user will be prompted for a file path.

A backup of the file will be created during the initialization.

def get_packages(self, attribute: str = None) -> list[str | xml.etree.ElementTree.Element]: View Source

    122    def get_packages(self, attribute: str = None) -> list[str | ElementTree.Element]:
    123        """Return a list of package elements that are used
    124        in the board file.
    125
    126        :param attribute: If given, the returned list will only
    127        contain the given attribute for each element instead of
    128        the whole ElementTree.Element ."""
    129        packages = [
    130            package
    131            for package in self.board.findall(".//libraries/library/packages/package")
    132        ]
    133        return self.get_attribute(attribute, packages) if attribute else packages

Return a list of package elements that are used in the board file.

###### Parameters

- attribute: If given, the returned list will only contain the given attribute for each element instead of the whole ElementTree.Element .

def get_smd_packages(self, attribute: str = None) -> list[str | xml.etree.ElementTree.Element]: View Source

    135    def get_smd_packages(
    136        self, attribute: str = None
    137    ) -> list[str | ElementTree.Element]:
    138        """Return a list of smd packages used in the board file.
    139
    140        :param attribute: If given, the returned list will only
    141        contain the given attribute for each element instead of
    142        the whole ElementTree.Element ."""
    143        packages = [
    144            package
    145            for package in self.get_packages()
    146            if type(package.find("smd")) is ElementTree.Element
    147        ]
    148        return self.get_attribute(attribute, packages) if attribute else packages

Return a list of smd packages used in the board file.

###### Parameters

- attribute: If given, the returned list will only contain the given attribute for each element instead of the whole ElementTree.Element .

def get_smd_parts(self, attribute: str = None) -> list[str | xml.etree.ElementTree.Element]: View Source

    150    def get_smd_parts(self, attribute: str = None) -> list[str | ElementTree.Element]:
    151        """Filter self.parts for smd parts.
    152
    153        :param attribute: If given, the returned list will only
    154        contain the given attribute for each element instead of
    155        the whole ElementTree.Element ."""
    156        smd_packages = self.get_smd_packages("name")
    157        parts = [part for part in self.parts if part.get("package") in smd_packages]
    158        return self.get_attribute(attribute, parts) if attribute else parts

Filter self.parts for smd parts.

###### Parameters

- attribute: If given, the returned list will only contain the given attribute for each element instead of the whole ElementTree.Element .

def get_bounds(self) -> dict[str, float]: View Source

    160    def get_bounds(self) -> dict[str, float]:
    161        """Return a dictionary representing
    162        the minimum and maximum x and y coordinates
    163        of the board."""
    164        wires = [
    165            wire for wire in self.plain.findall("wire") if wire.get("layer") == "20"
    166        ]
    167        get_coords = lambda n: [
    168            float(v)
    169            for wire in wires
    170            for v in itertools.chain([wire.get(f"{n}1"), wire.get(f"{n}2")])
    171        ]
    172        x = get_coords("x")
    173        y = get_coords("y")
    174        return {"x0": min(x), "xf": max(x), "y0": min(y), "yf": max(y)}

Return a dictionary representing the minimum and maximum x and y coordinates of the board.

def get_dimensions(self) -> tuple[float, float]: View Source

    176    def get_dimensions(self) -> tuple[float, float]:
    177        """Returns a two-tuple containing
    178        the width and height of the board's
    179        bounding box."""
    180        bounds = self.get_bounds()
    181        return (bounds["xf"] - bounds["x0"], bounds["yf"] - bounds["y0"])

Returns a two-tuple containing the width and height of the board's bounding box.

def get_center(self) -> tuple[float, float]: View Source

    183    def get_center(self) -> tuple[float, float]:
    184        """Returns a two-tuple containing
    185        the x,y center point of the board's
    186        bounding box."""
    187        bounds = self.get_bounds()
    188        midpoint = lambda n: (bounds[f"{n}f"] + bounds[f"{n}0"]) * 0.5
    189        return (midpoint("x"), midpoint("y"))

Returns a two-tuple containing the x,y center point of the board's bounding box.

def get_area(self) -> float: View Source

    191    def get_area(self) -> float:
    192        """Returns the area of the board's bounding box.
    193        Note: This is not the same as the board's
    194        physical area for shapes other than rectangular ones.
    195        It also doesn't account for cut-outs."""
    196        wxh = self.get_dimensions()
    197        return wxh[0] * wxh[1]

Returns the area of the board's bounding box. Note: This is not the same as the board's physical area for shapes other than rectangular ones. It also doesn't account for cut-outs.

def get_visible_layers(self) -> list[str]: View Source

    199    def get_visible_layers(self) -> list[str]:
    200        """Returns a list of visible layers."""
    201        return [
    202            layer.get("name") for layer in self.layers if layer.get("visible") == "yes"
    203        ]

Returns a list of visible layers.

##### Inherited Members

EagleFile
  get_parser
  get_args
  save
  get_names
  get_attribute

class ScriptWriter: View Source

    206class ScriptWriter:
    207    """Class for writing Eagle script files.
    208    These files use the Editor command syntax
    209    as documented in the Eagle manual.
    210    Scipts generated by this class will be invoked
    211    and executed within Eagle after returning
    212    from the invoking Ulp file for your Python
    213    script.
    214
    215    The preferred usage of this class is
    216    with a context manager. When used with a context
    217    manager, you don't need to remember to
    218    call the save() method at the end of the script.
    219    You also don't need to remember to add 'write;'
    220    as the last command, as the save() function adds it for you.
    221    Commands can be added with the '+=' operator.
    222    The Eagle syntax specifies each command ends with
    223    ';'. If you leave it off of an added command,
    224    it will be added for you.
    225    Some script commands are provided as member functions.
    226    For further script options, refer to the Eagle help menu.
    227
    228    e.x. Moving the center of the board to (0,0)
    229    >>> brd = Board()
    230    >>> with ScriptWriter(brd.path) as scr:
    231    >>>     center = brd.get_center()
    232    >>>     scr.group_all() # Adds "group all;" to commands
    233    >>>     scr += f"move (>0 0) (-{center[0]} -{center[1]})"""
    234
    235    def __init__(
    236        self,
    237        script_name: str = None,
    238    ):
    239        """The .scr file will be saved to a "scripts" subfolder within the same
    240        directory as the eagle_file param.
    241
    242        :param eagle_file: The path to the Eagle file
    243        the script is being written for.
    244
    245        :param script_name: The name of the script creating the instance.
    246        If None, the inspect module will be used to determine the script name."""
    247        if not script_name:
    248            script_name = (Path(inspect.stack()[1].filename)).stem
    249        scriptsdir = Path(autoeagle_config.load_config()["scriptsdir"])
    250        self.path = scriptsdir / f"{script_name}.scr"
    251        self.commands = []
    252
    253    def __enter__(self):
    254        return self
    255
    256    def __exit__(self, *args):
    257        self.save()
    258
    259    def __iadd__(self, command: str):
    260        if not command.endswith(";"):
    261            command += ";"
    262        self.commands.append(command)
    263        return self
    264
    265    def save(self):
    266        self += "write"
    267        self.path.write_text("\n".join(self.commands))
    268
    269    def group_all(self):
    270        """Put everything on a visible layer into a group."""
    271        self += "group all"
    272
    273    def display_all(self):
    274        """Make every layer in the design visible."""
    275        self += "display all"
    276
    277    def display_layers(self, layers: list[str]):
    278        """Displays only the layers in 'layers'."""
    279        self += f"display none {' '.join(layers)}"
    280
    281    def _coordinate_string(self, coordinates: list[tuple[float, float]]) -> str:
    282        """Return command snippet for coordinates to be used with draw commands.
    283
    284        :param coordinates: A list of two-tuples. A string will
    285        be returned specifying the coordinates in the same order
    286        as the param."""
    287        return " ".join(
    288            f"({coordinate[0]} {coordinate[1]})" for coordinate in coordinates
    289        )
    290
    291    def _rectangle_coordinates(
    292        self, x0: float, xf: float, y0: float, yf: float
    293    ) -> list[tuple[float, float]]:
    294        """Return a list of two-tuples that describe
    295        a rectangle given the coordinates of the sides.
    296
    297        :param x0,xf,y0,yf: Coordinates describing
    298        the sides of a rectangle."""
    299
    300        return [(x0, y0), (x0, yf), (xf, yf), (xf, y0), (x0, y0)]
    301
    302    def draw_group(self, coordinates: list[tuple[float, float]]):
    303        """Draw a group bounded by 'coordinates'.
    304
    305        :param coordinates: A list of two-tuples specifying
    306        the path to take when drawing the group."""
    307        self += f"group {self._coordinate_string(coordinates)}"
    308
    309    def draw_rectangle_group(self, x0: float, xf: float, y0: float, yf: float):
    310        """Draw a rectangular group whose sides are described by the given coordinates."""
    311        self.draw_group(self._rectangle_coordinates(x0, xf, y0, yf))
    312
    313    def draw(
    314        self,
    315        coordinates: list[tuple[float, float]],
    316        layer: str = None,
    317        width: float = None,
    318    ):
    319        """Draw a line throught the given series of coordinate points.
    320
    321        :param coordinates: A list of two-tuples specifying
    322        the path to take when drawing the line.
    323
    324        :param layer: The layer to draw the line on.
    325        If None, the currently active layer is used.
    326
    327        :param width: The width of the line.
    328        If None, the currently active width is used.
    329
    330        >>> # Draw a line of width 1 on layer "tplace" from (0 0) to (10 0) to (3 5)
    331        >>> draw([(0, 0), (10, 0), (3,5)], "tPlace", 1)"""
    332
    333        if layer:
    334            self += f"layer {layer}"
    335        if width:
    336            self += f"width {width}"
    337        self += f"line {self._coordinate_string(coordinates)}"
    338
    339    def draw_rectangle(
    340        self,
    341        x0: float,
    342        xf: float,
    343        y0: float,
    344        yf: float,
    345        layer: str = None,
    346        width: float = None,
    347    ):
    348        """Draw a rectangle whose sides are described by the given
    349        coordinates: x0, xf, y0, yf.
    350
    351        :param layer: The layer to draw the line on.
    352        If None, the currently active layer is used.
    353
    354        :param width: The width of the line.
    355        If None, the currently active width is used.
    356
    357        >>> # Draw a 10 x 10 board outline of width 1 centered at (0, 0)
    358        >>> draw_rectangle(-5, 5, -5, 5, "Dimension", 1)"""
    359        self += self.draw(self._rectangle_coordinates(x0, xf, y0, yf), layer, width)
    360
    361    def rats_nest(self, ripup_polygons: bool = True):
    362        """Run the rats nest command
    363
    364        :param ripup_polygons: If True, run 'ripup @'
    365        after running rats nest. Eagle gets real
    366        slow when polygons are visible."""
    367
    368        self += "ratsnest"
    369        if ripup_polygons:
    370            self += "ripup @"

Class for writing Eagle script files. These files use the Editor command syntax as documented in the Eagle manual. Scipts generated by this class will be invoked and executed within Eagle after returning from the invoking Ulp file for your Python script.

The preferred usage of this class is with a context manager. When used with a context manager, you don't need to remember to call the save() method at the end of the script. You also don't need to remember to add 'write;' as the last command, as the save() function adds it for you. Commands can be added with the '+=' operator. The Eagle syntax specifies each command ends with ';'. If you leave it off of an added command, it will be added for you. Some script commands are provided as member functions. For further script options, refer to the Eagle help menu.

e.x. Moving the center of the board to (0,0)

    >>> brd = Board()
    >>> with ScriptWriter(brd.path) as scr:
    >>>     center = brd.get_center()
    >>>     scr.group_all() # Adds "group all;" to commands
    >>>     scr += f"move (>0 0) (-{center[0]} -{center[1]})

ScriptWriter(script_name: str = None) View Source

    235    def __init__(
    236        self,
    237        script_name: str = None,
    238    ):
    239        """The .scr file will be saved to a "scripts" subfolder within the same
    240        directory as the eagle_file param.
    241
    242        :param eagle_file: The path to the Eagle file
    243        the script is being written for.
    244
    245        :param script_name: The name of the script creating the instance.
    246        If None, the inspect module will be used to determine the script name."""
    247        if not script_name:
    248            script_name = (Path(inspect.stack()[1].filename)).stem
    249        scriptsdir = Path(autoeagle_config.load_config()["scriptsdir"])
    250        self.path = scriptsdir / f"{script_name}.scr"
    251        self.commands = []

The .scr file will be saved to a "scripts" subfolder within the same directory as the eagle_file param.

###### Parameters

- eagle_file: The path to the Eagle file the script is being written for.

- script_name: The name of the script creating the instance. If None, the inspect module will be used to determine the script name.

def save(self): View Source

    265    def save(self):
    266        self += "write"
    267        self.path.write_text("\n".join(self.commands))

def group_all(self): View Source

    269    def group_all(self):
    270        """Put everything on a visible layer into a group."""
    271        self += "group all"

Put everything on a visible layer into a group.

def display_all(self): View Source

    273    def display_all(self):
    274        """Make every layer in the design visible."""
    275        self += "display all"

Make every layer in the design visible.

def display_layers(self, layers: list[str]): View Source

    277    def display_layers(self, layers: list[str]):
    278        """Displays only the layers in 'layers'."""
    279        self += f"display none {' '.join(layers)}"

Displays only the layers in 'layers'.

def draw_group(self, coordinates: list[tuple[float, float]]): View Source

    302    def draw_group(self, coordinates: list[tuple[float, float]]):
    303        """Draw a group bounded by 'coordinates'.
    304
    305        :param coordinates: A list of two-tuples specifying
    306        the path to take when drawing the group."""
    307        self += f"group {self._coordinate_string(coordinates)}"

Draw a group bounded by 'coordinates'.

###### Parameters

- coordinates: A list of two-tuples specifying the path to take when drawing the group.

def draw_rectangle_group(self, x0: float, xf: float, y0: float, yf: float): View Source

    309    def draw_rectangle_group(self, x0: float, xf: float, y0: float, yf: float):
    310        """Draw a rectangular group whose sides are described by the given coordinates."""
    311        self.draw_group(self._rectangle_coordinates(x0, xf, y0, yf))

Draw a rectangular group whose sides are described by the given coordinates.

def draw( self, coordinates: list[tuple[float, float]], layer: str = None, width: float = None): View Source

    313    def draw(
    314        self,
    315        coordinates: list[tuple[float, float]],
    316        layer: str = None,
    317        width: float = None,
    318    ):
    319        """Draw a line throught the given series of coordinate points.
    320
    321        :param coordinates: A list of two-tuples specifying
    322        the path to take when drawing the line.
    323
    324        :param layer: The layer to draw the line on.
    325        If None, the currently active layer is used.
    326
    327        :param width: The width of the line.
    328        If None, the currently active width is used.
    329
    330        >>> # Draw a line of width 1 on layer "tplace" from (0 0) to (10 0) to (3 5)
    331        >>> draw([(0, 0), (10, 0), (3,5)], "tPlace", 1)"""
    332
    333        if layer:
    334            self += f"layer {layer}"
    335        if width:
    336            self += f"width {width}"
    337        self += f"line {self._coordinate_string(coordinates)}"

Draw a line throught the given series of coordinate points.

###### Parameters

- coordinates: A list of two-tuples specifying the path to take when drawing the line.

- layer: The layer to draw the line on. If None, the currently active layer is used.

- width: The width of the line. If None, the currently active width is used.

    >>> # Draw a line of width 1 on layer "tplace" from (0 0) to (10 0) to (3 5)
    >>> draw([(0, 0), (10, 0), (3,5)], "tPlace", 1)

def draw_rectangle( self, x0: float, xf: float, y0: float, yf: float, layer: str = None, width: float = None): View Source

    339    def draw_rectangle(
    340        self,
    341        x0: float,
    342        xf: float,
    343        y0: float,
    344        yf: float,
    345        layer: str = None,
    346        width: float = None,
    347    ):
    348        """Draw a rectangle whose sides are described by the given
    349        coordinates: x0, xf, y0, yf.
    350
    351        :param layer: The layer to draw the line on.
    352        If None, the currently active layer is used.
    353
    354        :param width: The width of the line.
    355        If None, the currently active width is used.
    356
    357        >>> # Draw a 10 x 10 board outline of width 1 centered at (0, 0)
    358        >>> draw_rectangle(-5, 5, -5, 5, "Dimension", 1)"""
    359        self += self.draw(self._rectangle_coordinates(x0, xf, y0, yf), layer, width)

Draw a rectangle whose sides are described by the given coordinates: x0, xf, y0, yf.

###### Parameters

- layer: The layer to draw the line on. If None, the currently active layer is used.

- width: The width of the line. If None, the currently active width is used.

    >>> # Draw a 10 x 10 board outline of width 1 centered at (0, 0)
    >>> draw_rectangle(-5, 5, -5, 5, "Dimension", 1)

def rats_nest(self, ripup_polygons: bool = True): View Source

    361    def rats_nest(self, ripup_polygons: bool = True):
    362        """Run the rats nest command
    363
    364        :param ripup_polygons: If True, run 'ripup @'
    365        after running rats nest. Eagle gets real
    366        slow when polygons are visible."""
    367
    368        self += "ratsnest"
    369        if ripup_polygons:
    370            self += "ripup @"

Run the rats nest command

###### Parameters

- ripup_polygons: If True, run 'ripup @' after running rats nest. Eagle gets real slow when polygons are visible.