diff options
author | Trygve Laugstøl <trygvis@inamo.no> | 2017-01-07 14:00:46 +0100 |
---|---|---|
committer | Trygve Laugstøl <trygvis@inamo.no> | 2017-01-07 14:00:46 +0100 |
commit | 0958273a71dd19c2a90471a182ccc5b90b14e5b4 (patch) | |
tree | 8e33385ca9df94b80ce9b1f8ba06438b807f137a /trygvis/eda/cli/kicad_bom_to_ttl.py | |
parent | 5d7fc9c4b14536006f2435b1379887f95937e096 (diff) | |
download | eda-rdf-0958273a71dd19c2a90471a182ccc5b90b14e5b4.tar.gz eda-rdf-0958273a71dd19c2a90471a182ccc5b90b14e5b4.tar.bz2 eda-rdf-0958273a71dd19c2a90471a182ccc5b90b14e5b4.tar.xz eda-rdf-0958273a71dd19c2a90471a182ccc5b90b14e5b4.zip |
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.
Diffstat (limited to 'trygvis/eda/cli/kicad_bom_to_ttl.py')
-rwxr-xr-x | trygvis/eda/cli/kicad_bom_to_ttl.py | 387 |
1 files changed, 0 insertions, 387 deletions
diff --git a/trygvis/eda/cli/kicad_bom_to_ttl.py b/trygvis/eda/cli/kicad_bom_to_ttl.py deleted file mode 100755 index 9cd3f8c..0000000 --- a/trygvis/eda/cli/kicad_bom_to_ttl.py +++ /dev/null @@ -1,387 +0,0 @@ -import functools -import os.path -import re -import sys -import xml.etree.ElementTree -from urllib.parse import quote_plus -from operator import attrgetter -import itertools -from typing import List - -import rdflib -from rdflib import Literal, URIRef -from rdflib.namespace import RDF, RDFS - -from .. import cli -from ..kicad import rdf as kicad_rdf - - -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, type): - self.text = text - self.value = value - self.factor = factor - self.type = type - - 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) - type = m.group(2) - else: - type = 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, type) - - 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(doc): - 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) - - 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_schematic_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 cli.CliException('Could not generate a stable, identifying URL for the schematic') - - title = quote_plus(title) - return kicad_rdf.KICAD_BOARD[title] - - -def run(args): - if args.input is not None: - src = open(args.input, 'r') - else: - src = sys.stdin - - if args.output is not None: - parent = os.path.dirname(args.output) - if not os.path.exists(parent): - os.mkdir(parent) - dst = open(args.output, 'wb') - else: - dst = sys.stdout.buffer - - schematic_url = args.schematic_url if hasattr(args, 'schematic_url') else None - - with src, dst: - process(src, dst, schematic_url) - - -def process(src, dst, schematic_url): - tree = xml.etree.ElementTree.parse(src) - root = tree.getroot() - - export = Export.from_xml(root) - - # print('components:') - # for c in export.components: - # print(c) - - # for name in export.component_fields(): - # cli.info(name) - - # cli.info('components:') - # fmt = '%2s%3s' - # cli.info(fmt % ('Ref', '')) - - components = sorted(export.components, key=attrgetter('ref')) - - component_count = 0 - part_count = 0 - has_part = 0 - has_digikey = 0 - - for cls, cs in itertools.groupby(components, key=attrgetter('ref_class')): - # cli.info('--- Class: %s ---' % cls) - cs = sorted(cs, key=attrgetter('value')) - for c in cs: - component_count += 1 - - value = str(c.value) - part = c.find_field('part') - digikey = c.find_field('digikey') - footprint = c.footprint - - if c.ref_class is not None: - ref_class = c.ref_class - ref_instance = c.ref_instance - else: - ref_class = c.ref - ref_instance = '' - - # cli.info(fmt % (ref_class, ref_instance)) - # cli.info(' Value: %s' % c.value.text) - # if c.footprint: - # cli.info(' Footprint: %s' % c.footprint) - - if part is not None: - if part != 'NA': - has_part += 1 if part is not None else 0 - part_count += 1 - - if digikey is not None: - has_digikey += 1 - else: - part = None - else: - part_count += 1 - part = 'MISSING' - - # if part is not None: - # cli.info(' Part: %s' % part) - # if digikey is not None: - # cli.info(' Digikey: %s' % digikey) - - schematic_url = schematic_url if schematic_url is not None else export.generate_schematic_url() - - # cli.info() - # cli.info('=== Summary ===') - # cli.info('Schematic URL: %s' % schematic_url) - # cli.info('Number of components: %d' % component_count) - # cli.info('Number of parts: %d' % part_count) - # cli.info('Assigned part: %d / %2.f%%' % (has_part, (has_part / part_count) * 100)) - # cli.info('Assigned digikey: %d / %2.f%%' % (has_digikey, (has_digikey / part_count) * 100)) - - g = cli.create_graph(kicad=True) - - schematic = URIRef(schematic_url) - g.add((schematic, RDF.type, kicad_rdf.KICAD_TYPE.schematic)) - - # The components and fields could/should have been a BNodes. Their generated names are not very nice. - # TODO: try using a hash of the current value and put that under a special generated namespace. - # 'http://example.org/my-board' + ref='C10' => 'http://example.org/my-boardC10' - # hash('http://example.org/my-boardC10') => 123456 - # add_prefix(hash) => 'https://trygvis.io/purl/kicad/generated#123456' - footprints = set() - for c in export.components: - ns = rdflib.Namespace(schematic_url) - node = ns[c.ref] - g.add((schematic, kicad_rdf.KICAD.component, node)) - - g.add((node, RDF.type, kicad_rdf.KICAD_TYPE.schematic_component)) - g.add((node, RDFS.label, Literal(c.ref))) - g.add((node, kicad_rdf.KICAD.value, Literal(c.value.text))) - - footprint_uri = URIRef(kicad_rdf.KICAD_FOOTPRINT[quote_plus(c.footprint)]) - if not footprint_uri in footprints: - g.add((footprint_uri, RDF.type, kicad_rdf.KICAD_TYPE.footprint)) - g.add((footprint_uri, RDFS.label, Literal(c.footprint))) - footprints.add(footprint_uri) - - g.add((node, kicad_rdf.KICAD.footprint, footprint_uri)) - - for name, value in c.fields.items(): - f = ns['%s-%s' % (c.ref, name)] - g.add((node, kicad_rdf.KICAD.field, f)) - g.add((f, RDF.type, kicad_rdf.KICAD_TYPE.field)) - g.add((f, kicad_rdf.KICAD.field_name, Literal(name))) - g.add((f, kicad_rdf.KICAD.field_value, Literal(value))) - - # TODO: serialize the data from <design> too - - g.serialize(destination=dst, encoding='utf-8', format='turtle') |