BlinkenArea - GitList
Repositories
Blog
Wiki
partlib
Code
Commits
Branches
Tags
Search
Tree:
b93456c
Branches
Tags
master
stefan.experimental
partlib
pcb_footpr_conv
pcbfp2dxf.py
begin of PCB footprint parser
Stefan Schuermans
commited
b93456c
at 2021-02-17 19:13:39
pcbfp2dxf.py
Blame
History
Raw
#! /usr/bin/env python3 import ezdxf import re class StringBuffer: """ Store a string, an index and provide some operations. """ def __init__(self, string: str): self._string = string self._index = 0 self._stack = [] def __enter__(self): self.enter() return self def __exit__(self, exc_type, _exc_value, _traceback): if exc_type is None: self.complete() else: self.backtrack() def advance(self, length: int): """ Advance the index by length. """ self._index = min(len(self._string), self._index + length) def backtrack(self): """ Track back from parsing something. Pop index from stack and use it as the new current index. """ self._index = self._stack[-1] del self._stack[-1] def check(self, s: str) -> bool: """ Check if string s follows index. If so, advance by length of s and return True Otherwise, keep index and return False. """ if self.peek(len(s)) == s: self.advance(len(s)) return True return False def complete(self): """ Complete parsing something. Pop index from stack and throw it away. """ del self._stack[-1] def enter(self): """ Enter parsing something. Push current index onto stack. """ self._stack.append(self._index) def expect(self, s: str): """ Check that string s follows index. If so, advance. If not, throw ValueError. """ if not self.check(s): raise ValueError def get(self, length: int) -> str: """ Return the next length characters and advance the index. """ s = self.peek(length) self.advance(length) return s def getRe(self, ptrn: re.Pattern): """ Return the matching the regular expression pattern and advance index or throw ValueError. """ m = ptrn.match(self._string[self._index:]) if not m: raise ValueError() self._index += len(m.group(0)) return m def peek(self, length: int) -> str: """ Return the next length characters without advancing the index. """ return self._string[self._index:self._index + length] class PcbBaseParser(StringBuffer): """ Parser for the basic element of GNU PCB files. """ re_float = re.compile(r'^[+-]?(?:[0-9]+(?:\.[0-9]*)?|\.[0-9]+)' r'(?:[]eE[+-]?[0-9]\+)?') re_int = re.compile(r'^[+-]?[0-9]+') re_quoted = re.compile(r'^"([^"]*)"') re_space = re.compile(r'^\s+', re.MULTILINE) re_space_opt = re.compile(r'^\s*', re.MULTILINE) def parseCoord(self) -> float: """ Parse a coordinate. """ with self: f = self.parseFloat() if self.check('mil'): f *= 100 # native unit == .01mil elif self.check('mm'): f *= 1e5/25.4 # 25.4mm == 1inch == 1e5 * .01mil return f def parseFloat(self) -> float: """ Parse a floating point value. """ with self: return float(self.getRe(self.re_float).group(0)) def parseInt(self) -> int: """ Parse a decimal integer. """ with self: return int(self.getRe(self.re_int).group(0)) def parseQuoted(self): """ Parse quoted string. """ with self: return self.getRe(self.re_quoted).group(1) def parseSpace(self): """ Parse at least one whitespace character or more. """ with self: self.getRe(self.re_space) def parseSpaceOpt(self): """ Parse any number of whitespace characters. """ with self: self.getRe(self.re_space_opt) class PcbFootprintParser(PcbBaseParser): """ Parser for a GNU PCB footprint file. """ def parseBody(self, contents: list): """ Parse a body of a block. contents: list of lambdas with options for recursive descent """ with self: self.parseOpen() while True: # end of body try: self.parseClose() break except ValueError: pass # try options for c in contents: try: c() break # option succeded, go to next content except ValueError: pass # an option failed, try next option # all options failed else: raise ValueError() def parseClose(self): """ Parse a closing parenthesis. """ with self: self.parseSpaceOpt() self.expect(')') def parseElementBlock(self): """ Parse a GNU PCB element block. """ with self: self.parseElementClause() self.parseBody([ lambda: self.parseElementLineClause(), lambda: self.parsePadClause() ]) def parseElementClause(self): """ Parse a GNU PCB element clause. """ with self: self.parseSpaceOpt() self.expect('Element') self.parseSpaceOpt() self.expect('[') self.parseSpaceOpt() s_flags = self.parseQuoted() self.parseSpace() desc = self.parseQuoted() self.parseSpace() name = self.parseQuoted() self.parseSpace() value = self.parseQuoted() self.parseSpace() m_x = self.parseCoord() self.parseSpace() m_y = self.parseCoord() self.parseSpace() t_x = self.parseCoord() self.parseSpace() t_y = self.parseCoord() self.parseSpace() t_dir = self.parseInt() self.parseSpace() t_scale = self.parseInt() self.parseSpace() t_s_flags = self.parseQuoted() self.parseSpaceOpt() self.expect(']') def parseElementLineClause(self): """ Parse a GNU PCB element line clause. """ with self: self.parseSpaceOpt() self.expect('ElementLine') self.parseSpaceOpt() self.expect('[') self.parseSpaceOpt() r_x1 = self.parseCoord() self.parseSpace() r_y1 = self.parseCoord() self.parseSpace() r_x2 = self.parseCoord() self.parseSpace() r_y2 = self.parseCoord() self.parseSpace() thickness = self.parseCoord() self.parseSpaceOpt() self.expect(']') def parseOpen(self): """ Parse an opening parenthesis. """ with self: self.parseSpaceOpt() self.expect('(') def parsePadClause(self): """ Parse a GNU PCB pad clause. """ with self: self.parseSpaceOpt() self.expect('Pad') self.parseSpaceOpt() self.expect('[') self.parseSpaceOpt() r_x1 = self.parseCoord() self.parseSpace() r_y1 = self.parseCoord() self.parseSpace() r_x2 = self.parseCoord() self.parseSpace() r_y2 = self.parseCoord() self.parseSpace() thickness = self.parseCoord() self.parseSpace() clearance = self.parseCoord() self.parseSpace() mask = self.parseCoord() self.parseSpace() name = self.parseQuoted() self.parseSpace() number = self.parseQuoted() self.parseSpace() s_flags = self.parseQuoted() self.parseSpaceOpt() self.expect(']') class PcbFootprint: """ A footprint of GNU PCB. """ def __init__(self): self.reset() def readPcb(self, file_name: str): """ Read a PCB footprint file. """ self.reset() with open(file_name, 'r') as f: s = f.read() p = PcbFootprintParser(s) p.parseElementBlock() def reset(self): pass def write_dxf(file_name: str): doc = ezdxf.new('R12') msp = doc.modelspace() msp.add_circle((1, 2), radius=3) doc.saveas(file_name) def main(): fp = PcbFootprint() fp.readPcb('../pcb_footpr/SMD_1206') if __name__ == '__main__': main()