aboutsummaryrefslogtreecommitdiff
path: root/trygvis/eda/cli/kicad_bom_to_ttl.py
diff options
context:
space:
mode:
Diffstat (limited to 'trygvis/eda/cli/kicad_bom_to_ttl.py')
-rwxr-xr-xtrygvis/eda/cli/kicad_bom_to_ttl.py387
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')