From 0958273a71dd19c2a90471a182ccc5b90b14e5b4 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Sat, 7 Jan 2017 14:00:46 +0100 Subject: Renaming 'schematic' to 'project'. Renaming 'kicad-bom-to-ttl' to 'kicad-import-project'. Renaming 'digikey-download-for-schematic' to 'digikey-download-for-project'. Splitting out the Export xml file code into its own module. init: putting project.url and project.file in config.ini. init: putting db.update-url in config.ini if given on the command line. kicad-import-project: by default, assume that the user want to update local database, optionally write the ttl file to disk. cli.write_graph: create any missing parent directories. --- trygvis/eda/kicad/export/__init__.py | 250 +++++++++++++++++++++++++++++++++++ 1 file changed, 250 insertions(+) create mode 100644 trygvis/eda/kicad/export/__init__.py (limited to 'trygvis/eda/kicad/export/__init__.py') diff --git a/trygvis/eda/kicad/export/__init__.py b/trygvis/eda/kicad/export/__init__.py new file mode 100644 index 0000000..64b82d2 --- /dev/null +++ b/trygvis/eda/kicad/export/__init__.py @@ -0,0 +1,250 @@ +from typing import List +import functools +import re +import os.path +from trygvis.eda import EdaException +from trygvis.eda.kicad import rdf as kicad_rdf +from urllib.parse import quote_plus +import xml.etree.ElementTree + + +def _clean(s): + if s is None: + return None + s = s.strip() + return None if len(s) == 0 else s + + +def _cleaned_text(e, name): + child = e.find(name) + if child is None: + return None + return child.text + + +def _cleaned_attr(e, name): + value = e.get(name) + if value is None: + return None + return _clean(value) + + +def _iso_size_to_factor(size): + if size == 'k': + return 3 + if size == 'M': + return 6 + if size == 'm': + return -3 + if size == 'u' or size == '\N{GREEK SMALL LETTER MU}' or size == '\N{MICRO SIGN}': + return -6 + if size == 'n': + return -9 + if size == 'p': + return -12 + return None + + +@functools.total_ordering +class Value: + valueRe = re.compile('([0-9]+\.?[0-9]*)(.*)') + typeRe = re.compile('(.*)(H|Hz)$') + + def __init__(self, text, value, factor, typ): + self.text = text + self.value = value + self.factor = factor + self.type = typ + + def __eq__(self, other): + return self.text == other.text + + def __lt__(self, other): + if self.value is not None: + if other.value is None: + return 1 + else: + return (self.factor, self.value) < (other.factor, other.value) + else: + if other.value is None: + return self.text < other.text + else: + return -1 + + def __str__(self): + if self.value is not None: + s = str(self.value) + 'e' + str(self.factor) + if self.type is not None: + s = s + ' ' + self.type + return s + if self.text is not None: + return self.text + return 'unknown' + + @staticmethod + def parse(text): + if text is None: + return Value(None, None, None, None) + + m = Value.valueRe.match(text) + if m: + value_int = float(m.group(1)) + suffix = m.group(2) + m = Value.typeRe.match(suffix) + if m: + suffix = m.group(1) + typ = m.group(2) + else: + typ = None + + factor = _iso_size_to_factor(suffix) + # cli.info(text + ': suffix:' + suffix + ' => value_int: ' + str(value_int) + ', factor: ' + str( + # factor) + ', type=' + str(type)) + + if value_int is not None and factor is not None: + return Value(text, value_int, factor, typ) + + return Value(text, None, None, None) + + +class Component: + refRe = re.compile('([a-zA-Z]+)([0-9]+)') + + def __init__(self, ref, value, footprint, fields): + self.ref = ref # type: str + self.value = Value.parse(value) # type: Value + self.footprint = footprint # type: str + self.fields = fields + m = Component.refRe.match(ref) + if m: + self.ref_class = m.group(1) + self.ref_instance = int(m.group(2)) + else: + self.ref_class = None + self.ref_instance = None + + def __str__(self): + s = 'ref=' + self.ref + if self.value is not None: + s += ', value=' + str(self.value) + if self.footprint is not None: + s += ', footprint=' + self.footprint + for name, value in self.fields.items(): + s += ', ' + name + '=' + value + return s + + def find_field(self, key): + return self.fields[key] if key in self.fields else None + + @staticmethod + def from_xml(e): + ref = e.get('ref') + value = _cleaned_text(e, 'value') + footprint = _cleaned_text(e, 'footprint') + fs = {} + + fields = e.find('fields') + if fields is not None: + for field in fields.findall('field'): + fs[field.get('name')] = field.text + + if ref is not None: + return Component(ref, value, footprint, fs) + + return None + + +class TitleBlock(object): + def __init__(self, title=None, rev=None, date=None, source=None): + self.title = title + self.rev = rev + self.date = date + self.source = source + + @staticmethod + def from_xml(tb): + title = _cleaned_text(tb, 'title') + rev = _cleaned_text(tb, 'rev') + date = _cleaned_text(tb, 'date') + source = _cleaned_text(tb, 'source') + return TitleBlock(title, rev, date, source) + + +class Sheet(object): + def __init__(self, number, name, title_block): + self.number = number + self.name = name + self.title_block = title_block + + @staticmethod + def from_xml(s): + number = _cleaned_attr(s, 'number') + name = _cleaned_attr(s, 'name') + node = s.find('title_block') + title_block = TitleBlock.from_xml(node) if node is not None else TitleBlock() + return Sheet(int(number) if number is not None else None, name, title_block) + + +class Design(object): + def __init__(self, source=None, date=None, tool=None, sheets=None): + self.source = source + self.date = date + self.tool = tool + self.sheets = sheets if sheets is not None else [] + + @staticmethod + def from_xml(d): + source = _cleaned_text(d, 'source') + date = _cleaned_text(d, 'date') + tool = _cleaned_text(d, 'tool') + sheets = [] + for s in d.iterfind('sheet'): + sheets.append(Sheet.from_xml(s)) + return Design(source, date, tool, sheets) + + +class Export(object): + def __init__(self, design, components): + self.design = design if design is not None else Design() # type: Design + self.components = components # type: List[Component] + + @staticmethod + def from_xml_document(doc) -> 'Export': + cs = [] + + node = doc.find('design') + design = Design.from_xml(node) if node is not None else None + + components = doc.find('components') + for e in components.iter('comp'): + c = Component.from_xml(e) + if c is not None: + cs.append(c) + + return Export(design, cs) + + @staticmethod + def from_xml_file(src) -> 'Export': + tree = xml.etree.ElementTree.parse(src) + root = tree.getroot() + return Export.from_xml_document(root) + + def component_fields(self): + fs = set() + for c in self.components: + for key, value in c.fields.items(): + fs.add(key) + return fs + + # def generate_project_url(self): + # s = next((s for s in self.design.sheets if s.number == 1), None) + # title = s.title_block.title if s is not None else None + # if title is None: + # if self.design.source is not None: + # title = _clean(os.path.basename(self.design.source)) + # + # if title is None: + # raise EdaException('Could not generate a stable, identifying URL for the project') + # + # title = quote_plus(title) + # return kicad_rdf.KICAD_BOARD[title] -- cgit v1.2.3