autoeagle.core
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 @"
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.
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.
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.
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.
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.
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)
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.
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
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.
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.
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 .
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 .
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 .
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.
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.
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.
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.
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
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]})
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.
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.
273 def display_all(self): 274 """Make every layer in the design visible.""" 275 self += "display all"
Make every layer in the design visible.
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'.
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.
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.
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)
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)
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.