diff options
Diffstat (limited to 'trygvis')
-rw-r--r-- | trygvis/eda/__init__.py | 2 | ||||
-rw-r--r-- | trygvis/eda/cli/__init__.py | 27 | ||||
-rwxr-xr-x | trygvis/eda/cli/db_stats.py | 12 | ||||
-rwxr-xr-x | trygvis/eda/cli/digikey_download_for_project.py (renamed from trygvis/eda/cli/digikey_download_for_schematic.py) | 48 | ||||
-rw-r--r-- | trygvis/eda/cli/eda_rdf.py | 64 | ||||
-rwxr-xr-x | trygvis/eda/cli/init.py | 70 | ||||
-rwxr-xr-x | trygvis/eda/cli/kicad_import_project.py | 173 | ||||
-rwxr-xr-x | trygvis/eda/cli/make_bom.py | 26 | ||||
-rw-r--r--[-rwxr-xr-x] | trygvis/eda/kicad/export/__init__.py (renamed from trygvis/eda/cli/kicad_bom_to_ttl.py) | 195 |
9 files changed, 352 insertions, 265 deletions
diff --git a/trygvis/eda/__init__.py b/trygvis/eda/__init__.py index e69de29..408bd7c 100644 --- a/trygvis/eda/__init__.py +++ b/trygvis/eda/__init__.py @@ -0,0 +1,2 @@ +class EdaException(Exception): + pass diff --git a/trygvis/eda/cli/__init__.py b/trygvis/eda/cli/__init__.py index 308e1bd..aa61021 100644 --- a/trygvis/eda/cli/__init__.py +++ b/trygvis/eda/cli/__init__.py @@ -1,16 +1,26 @@ import sys import logging from genericpath import isfile +from os import mkdir +from os.path import dirname, isdir from rdflib import store, ConjunctiveGraph, Graph, RDF, RDFS from rdflib.plugins.sparql import prepareQuery import rdflib.plugins.stores.sparqlstore as sparqlstore import configparser +# noinspection PyUnresolvedReferences +import argparse from ..digikey import rdf as digikey_rdf from ..kicad import rdf as kicad_rdf +class CliCommand(object): + def __init__(self, key, description): + self.key = key + self.description = description + + class CliException(Exception): pass @@ -26,6 +36,12 @@ def info(msg=None): sys.stderr.write("\n") +def debug(msg=None): + if msg is not None: + sys.stderr.write('D: %s' % msg) + sys.stderr.write("\n") + + def do_exit(msg=None): sys.exit(msg) @@ -45,7 +61,7 @@ def with_database(tx): raise CliException("The database is corrupt: %s" % path) elif db_type == 'sparql': query_endpoint = config["db"]["url"] - update_endpoint = config["db"].get("update_url") + update_endpoint = config["db"].get("update-url") if update_endpoint is None: g = sparqlstore.SPARQLStore() @@ -57,7 +73,6 @@ def with_database(tx): raise CliException("Unknown db.type: %s" % db_type) try: - print("g=%s" % g) tx(g) if isinstance(g, sparqlstore.SPARQLUpdateStore): g.commit() @@ -81,9 +96,13 @@ def create_graph(digikey=False, kicad=False) -> Graph: return g -def write_graph(gen_g, filename: str = None, force_write: bool = False): +def write_graph(gen_g: Graph, filename: str = None, force_write: bool = False): if filename is not None: if force_write or not isfile(filename): + parent = dirname(filename) + if not isdir(parent): + mkdir(parent) + g = gen_g() if g is None: @@ -129,7 +148,7 @@ def write_config(config: configparser.ConfigParser): config.write(configfile) -def read_config(): +def read_config() -> configparser.ConfigParser: try: with open('.eda-rdf/config.ini', 'r') as f: config = configparser.ConfigParser() diff --git a/trygvis/eda/cli/db_stats.py b/trygvis/eda/cli/db_stats.py index aabc46a..babc53b 100755 --- a/trygvis/eda/cli/db_stats.py +++ b/trygvis/eda/cli/db_stats.py @@ -1,21 +1,21 @@ from trygvis.eda import cli -def run(): +def run(args: object): def db_stats(g): res = cli.sparql(g, """ -SELECT ?schematic ?label +SELECT ?project ?label WHERE { - ?schematic a kicad-type:schematic + ?project a kicad-type:project OPTIONAL { - ?schematic rdfs:label ?label + ?project rdfs:label ?label } } """) - cli.info("Found %d schematics in database" % len(res)) + cli.info("Found %d projects in database" % len(res)) for row in res: name = row.label if row.label is not None else "<unnamed>" - url = row.schematic + url = row.project cli.info("%s:" % name) cli.info(" URL: %s" % url) diff --git a/trygvis/eda/cli/digikey_download_for_schematic.py b/trygvis/eda/cli/digikey_download_for_project.py index bf4f0ce..02e3cb1 100755 --- a/trygvis/eda/cli/digikey_download_for_schematic.py +++ b/trygvis/eda/cli/digikey_download_for_project.py @@ -1,3 +1,4 @@ +import argparse import rdflib import rdflib.term @@ -5,28 +6,41 @@ from trygvis.eda import cli from trygvis.eda.digikey import * -def work(schematic_url, force, g): +class DigikeyDownloadForProjectCommand(cli.CliCommand): + def __init__(self): + super().__init__("digikey-download-for-project", "Download missing data from digikey.com") + + def run(self, argv): + p = argparse.ArgumentParser(prog=self.key, description=self.description) + p.add_argument("--project", required=False) + p.add_argument("-f", "--force", default=False, action='store_true') + args = p.parse_args(argv) + + run(args) + + +def work(project_url, force, g): client = DigikeyClient() db = DigikeyDatabase() download_category_tree(db, client) - # Dump schematic: - # SELECT ?schematic + # Dump project: + # SELECT ?project # WHERE { - # ?schematic a kicad-type:schematic . + # ?project a kicad-type:project . # b:rateboard-2_a ? ?. # ?s ?predicate ?object . # } - # allComponentsQ = prepareQuery('SELECT ?ref ?value WHERE { ?schematic_url a kicad-type:schematic . ?schematic_url kicad:component ?o . ?o rdfs:label ?ref . ?o kicad:value ?value . }', initNs = initNs) + # allComponentsQ = prepareQuery('SELECT ?ref ?value WHERE { ?project_url a kicad-type:project . ?project_url kicad:component ?o . ?o rdfs:label ?ref . ?o kicad:value ?value . }', initNs = initNs) # - # for row in g.query(allComponentsQ, initBindings={'schematic_url': schematic_url}): + # for row in g.query(allComponentsQ, initBindings={'project_url': project_url}): # print("ref=%s, value=%s" % (row.ref, row.value)) - res = cli.sparql(g, "SELECT ?schematic WHERE {?schematic a kicad-type:schematic}") - print("Found %d schematics in database" % len(res)) + res = cli.sparql(g, "SELECT ?project WHERE {?project a kicad-type:project}") + print("Found %d projects in database" % len(res)) for row in res: - print("schematic: %s" % row.schematic) + print("project: %s" % row.project) res = cli.sparql(g, "SELECT ?dk_part ?dk_part_number WHERE {?dk_part a dk:part ; dk:partNumber ?dk_part_number}") print("Found %d Digikey parts in database" % len(res)) @@ -38,7 +52,7 @@ SELECT ?digikey_pn (group_concat(distinct ?ref;separator=";") as ?refs) WHERE { - ?schematic_url kicad:component ?cmp . + ?project_url kicad:component ?cmp . ?cmp rdfs:label ?ref ; kicad:value ?value . OPTIONAL { @@ -53,11 +67,11 @@ WHERE { } GROUP BY ?digikey_pn ORDER BY ?digikey_pn -""", init_bindings={'schematic_url': rdflib.term.URIRef(schematic_url)}) +""", init_bindings={'project_url': rdflib.term.URIRef(project_url)}) size = len(res) if size == 0: - cli.info('Could not find an parts for the schematic, did you use the correct URL: %s?' % schematic_url) + cli.info('Could not find an parts for the project, did you use the correct URL: %s?' % project_url) for row in res: pn = row.digikey_pn @@ -82,6 +96,10 @@ ORDER BY ?digikey_pn cli.write_graph(download_graph, filename, force_write=force) -def run(schematic_url, args): - cli.info("Schematic: %s" % schematic_url) - cli.with_database(lambda g: work(schematic_url, args.force, g)) +def run(args): + config = cli.read_config() + + project_url = config['project']['url'] + + cli.info("Project: %s" % project_url) + cli.with_database(lambda g: work(project_url, args.force, g)) diff --git a/trygvis/eda/cli/eda_rdf.py b/trygvis/eda/cli/eda_rdf.py index ee4b0dd..0df66ac 100644 --- a/trygvis/eda/cli/eda_rdf.py +++ b/trygvis/eda/cli/eda_rdf.py @@ -1,12 +1,7 @@ -import argparse -import sys -import trygvis.eda.cli as cli - - -class CliCommand(object): - def __init__(self, key, description): - self.key = key - self.description = description +from trygvis.eda.cli.digikey_download_for_project import DigikeyDownloadForProjectCommand +from trygvis.eda.cli.init import InitCommand +from trygvis.eda.cli.kicad_import_project import KicadImportProjectCommand +from . import * # TODO: move all of the command classes to the file they delegate to. @@ -33,35 +28,7 @@ class DbStats(CliCommand): args = p.parse_args(argv) from trygvis.eda.cli import db_stats - db_stats.run() - - -class KicadBomToTtl(CliCommand): - def __init__(self): - super().__init__("kicad-bom-to-ttl", "Create RDF triples from a KiCAD BOM.xml file") - - def run(self, argv): - p = argparse.ArgumentParser(prog=self.key, description=self.description) - p.add_argument("-o", "--output", required=False) - p.add_argument("-i", "--input", required=False) - args = p.parse_args(argv) - - from trygvis.eda.cli import kicad_bom_to_ttl - kicad_bom_to_ttl.run(args) - - -class DigikeyDownloadForSchematic(CliCommand): - def __init__(self): - super().__init__("digikey-download-for-schematic", "Download missing data from digikey.com") - - def run(self, argv): - p = argparse.ArgumentParser(prog=self.key, description=self.description) - p.add_argument("--schematic", required=True) - p.add_argument("-f", "--force", default=False, action='store_true') - args = p.parse_args(argv) - - from trygvis.eda.cli import digikey_download_for_schematic - digikey_download_for_schematic.run(args.schematic, args) + db_stats.run(args) class DigikeyDownloadMetadata(CliCommand): @@ -107,29 +74,16 @@ class MakeBom(CliCommand): make_bom.run(args.schematic) -class Init(CliCommand): - def __init__(self): - super().__init__("init", "Initialize a EDA-RFD database") - - def run(self, argv): - p = argparse.ArgumentParser(prog=self.key, description=self.description) - p.add_argument("--database-url", dest="database_url") - args = p.parse_args(argv) - - from trygvis.eda.cli import init - init.run(args) - - def main(): - cli.initialize() + initialize() commands = [ AddToDb(), - Init(), + InitCommand(), DbStats(), MakeBom(), - KicadBomToTtl(), - DigikeyDownloadForSchematic(), + KicadImportProjectCommand(), + DigikeyDownloadForProjectCommand(), DigikeyDownloadAttributeTypesForCategory(), DigikeyDownloadMetadata() ] diff --git a/trygvis/eda/cli/init.py b/trygvis/eda/cli/init.py index 1ea9079..fbc7d04 100755 --- a/trygvis/eda/cli/init.py +++ b/trygvis/eda/cli/init.py @@ -1,23 +1,79 @@ -from os.path import isfile, exists, isdir +import argparse +from os.path import isdir +from os import mkdir +from sys import exit +import glob +import configparser from trygvis.eda import cli -import configparser +from trygvis.eda.cli import CliException, CliCommand +from trygvis.eda.kicad.export import * + + +class InitCommand(CliCommand): + def __init__(self): + super().__init__("init", "Initialize a EDA-RFD database") + + def run(self, argv): + p = argparse.ArgumentParser(prog=self.key, description=self.description) + p.add_argument("--database-url", dest="database_url") + p.add_argument("--database-update-url", dest="database_update_url") + p.add_argument("--project-file", dest="project_file", required=False) + p.add_argument("--project-url", dest="project_url", required=False) + args = p.parse_args(argv) + + run(args) + + +def load_from_kicad_project(args, config: configparser.ConfigParser): + project_file = args.project_file + + if project_file is None: + for path in glob.glob('*.pro'): + if project_file is not None: + raise CliException("Found more than one project file in directly, use --project to specify which one " + "you want") + + project_file = path -import os -import sys + if project_file is None: + raise CliException("Could not find any KiCAD projects (.pro files) in the current directly.") + + project_url = args.project_url + + if project_url is None: + project_url = os.path.basename(project_file) + (project_url, _) = os.path.splitext(project_url) + + if not project_url.startswith('http'): + project_url = quote_plus(project_url) + project_url = kicad_rdf.KICAD_BOARD[project_url] + + cli.info("EDA-RDF project initialized with Project url: %s" % project_url) + + config['project']['file'] = project_file + config['project']['url'] = project_url def run(args): try: - os.mkdir('.eda-rdf') + mkdir('.eda-rdf') except FileExistsError: if isdir('.eda-rdf'): cli.info('Already initialized') else: cli.info('.eda-rdf exists, but is not a directory') - sys.exit(1) + exit(1) config = configparser.ConfigParser() + + # KiCAD is all that is supported for now + config['project'] = { + "type": "kicad" + } + + load_from_kicad_project(args, config) + config['db'] = { "type": "local" } @@ -25,5 +81,7 @@ def run(args): if args.database_url is not None: config["db"]["type"] = "sparql" config["db"]["url"] = args.database_url + if args.database_update_url is not None: + config["db"]["update-url"] = args.database_update_url cli.write_config(config) diff --git a/trygvis/eda/cli/kicad_import_project.py b/trygvis/eda/cli/kicad_import_project.py new file mode 100755 index 0000000..288b7fc --- /dev/null +++ b/trygvis/eda/cli/kicad_import_project.py @@ -0,0 +1,173 @@ +import os.path +from operator import attrgetter +import itertools + +import rdflib +from rdflib import Literal, URIRef +from rdflib.namespace import RDF, RDFS + +# from ..kicad import rdf as kicad_rdf +from . import * +from ..kicad.export import * + + +class KicadImportProjectCommand(CliCommand): + def __init__(self): + super().__init__("kicad-import-project", "Import a KiCAD project") + + def run(self, argv): + p = argparse.ArgumentParser(prog=self.key, description=self.description) + p.add_argument("-o", "--output-dir", dest="output_dir", required=False) + p.add_argument("-i", "--input", required=False) + args = p.parse_args(argv) + + run(args) + + +def run(args): + config = read_config() + + if args.input is "-": + src = sys.stdin + else: + if args.input is None: + (filename, _) = os.path.splitext(config['project']['file']) + filename += '.xml' + else: + filename = args.input + + if not isfile(filename): + raise CliException("No such file: %s. Did you export the BOM?" % filename) + + src = open(filename, 'r') + + project_url = config['project']['url'] + + with src: + project = export_to_graph(src, project_url) + + debug('Loaded %s tuples' % len(project)) + + if args.output_dir is not None: + parent = args.output_dir + if not os.path.exists(parent): + os.mkdir(parent) + output_file = os.path.join(parent, "project.ttl") + with open(output_file, 'wb') as dst: + project.serialize(destination=dst, encoding='utf-8', format='turtle') + + def import_project(g): + for idx, t in enumerate(project.triples((None, None, None))): + g.add(t) + + with_database(import_project) + + +def export_to_graph(src: object, project_url: str) -> rdflib.Graph: + export = Export.from_xml_file(src) + + # 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) + + # cli.info() + # cli.info('=== Summary ===') + # cli.info('Project URL: %s' % project_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 = create_graph(kicad=True) + + project = URIRef(project_url) + g.add((project, RDF.type, kicad_rdf.KICAD_TYPE.project)) + + # 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(project_url) + node = ns[c.ref] + g.add((project, 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 + + return g diff --git a/trygvis/eda/cli/make_bom.py b/trygvis/eda/cli/make_bom.py index 7805ead..a47e04a 100755 --- a/trygvis/eda/cli/make_bom.py +++ b/trygvis/eda/cli/make_bom.py @@ -22,11 +22,11 @@ class Component(object): self.fields[field_name] = field_value -def run(schematic_url): - cli.with_database(lambda g: work(schematic_url, g)) +def run(project_url): + cli.with_database(lambda g: work(project_url, g)) -def work(schematic_url, g): +def work(project_url, g): components = {} dk_parts = {} @@ -36,14 +36,14 @@ def work(schematic_url, g): SELECT ?ref ?value WHERE { - ?schematic a kicad-type:schematic ; + ?project a kicad-type:project ; kicad:component ?cmp . ?cmp a kicad-type:schematic_component ; kicad:value ?value ; rdfs:label ?ref . } ORDER BY ?ref -""", init_bindings={"schematic": rdflib.URIRef(schematic_url)}) +""", init_bindings={"project": rdflib.URIRef(project_url)}) for row in res: c = Component(row.ref, row.value) @@ -55,7 +55,7 @@ ORDER BY ?ref SELECT ?ref ?field ?field_name ?field_value WHERE { - ?schematic a kicad-type:schematic ; + ?project a kicad-type:project ; kicad:component ?cmp . ?cmp a kicad-type:schematic_component ; rdfs:label ?ref ; @@ -64,7 +64,7 @@ WHERE { ?field a kicad-type:field ; kicad:field_value ?field_value . } ORDER BY ?ref ?field_name -""", init_bindings={"schematic": rdflib.URIRef(schematic_url)}) +""", init_bindings={"project": rdflib.URIRef(project_url)}) for row in res: c = components[row.ref] @@ -72,12 +72,12 @@ ORDER BY ?ref ?field_name c.set_field(row.field_name, row.field_value) cli.info('%5s: %-20s %s' % (c.ref, row.field_name + ':', row.field_value)) - cli.info("Loading Digi-Key parts for schematic") + cli.info("Loading Digi-Key parts for project") res = cli.sparql(g, """ SELECT ?ref ?part_number ?type ?value ?attr_type WHERE { - ?schematic kicad:component ?cmp . + ?project kicad:component ?cmp . ?cmp a kicad-type:schematic_component ; rdfs:label ?ref ; kicad:field ?d . @@ -93,7 +93,7 @@ WHERE { dk:value ?attr_value . } ORDER BY ?ref ?attr_type ?attr_value -""", init_bindings={"schematic": rdflib.URIRef(schematic_url)}) +""", init_bindings={"project": rdflib.URIRef(project_url)}) for row in res: pn = row.part_number @@ -113,7 +113,7 @@ SELECT ?ref ?footprint ?part_number ?package_value ?case_value # * WHERE { - ?schematic kicad:component ?cmp . + ?project kicad:component ?cmp . ?cmp a kicad-type:schematic_component ; rdfs:label ?ref ; kicad:field ?dk_field . @@ -138,12 +138,12 @@ WHERE { # ?case_type a dk:attributeType ; dk:value ?case_url . # } . -# VALUES (?schematic_url) { (<https://trygvis/purl/kicad-board#SweetZpot+Sensor>) } . +# VALUES (?project_url) { (<https://trygvis/purl/kicad-board#SweetZpot+Sensor>) } . # VALUES (?package_type) { (dk-attr-type:pv16) } . # VALUES (?case_type) { (dk-attr-type:pv1291) } . } ORDER BY ?ref -""", init_bindings={"schematic": rdflib.URIRef(schematic_url), +""", init_bindings={"project": rdflib.URIRef(project_url), "package_type": digikey_rdf.DIGIKEY_ATTRIBUTE_TYPE["pv16"], "cast_type": digikey_rdf.DIGIKEY_ATTRIBUTE_TYPE["pv1291"]}) diff --git a/trygvis/eda/cli/kicad_bom_to_ttl.py b/trygvis/eda/kicad/export/__init__.py index 9cd3f8c..64b82d2 100755..100644 --- a/trygvis/eda/cli/kicad_bom_to_ttl.py +++ b/trygvis/eda/kicad/export/__init__.py @@ -1,19 +1,11 @@ +from typing import List import functools -import os.path import re -import sys -import xml.etree.ElementTree +import os.path +from trygvis.eda import EdaException +from trygvis.eda.kicad import rdf as kicad_rdf 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 +import xml.etree.ElementTree def _clean(s): @@ -58,11 +50,11 @@ class Value: valueRe = re.compile('([0-9]+\.?[0-9]*)(.*)') typeRe = re.compile('(.*)(H|Hz)$') - def __init__(self, text, value, factor, type): + def __init__(self, text, value, factor, typ): self.text = text self.value = value self.factor = factor - self.type = type + self.type = typ def __eq__(self, other): return self.text == other.text @@ -101,16 +93,16 @@ class Value: m = Value.typeRe.match(suffix) if m: suffix = m.group(1) - type = m.group(2) + typ = m.group(2) else: - type = None + 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, type) + return Value(text, value_int, factor, typ) return Value(text, None, None, None) @@ -217,7 +209,7 @@ class Export(object): self.components = components # type: List[Component] @staticmethod - def from_xml(doc): + def from_xml_document(doc) -> 'Export': cs = [] node = doc.find('design') @@ -231,6 +223,12 @@ class Export(object): 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: @@ -238,150 +236,15 @@ class Export(object): 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') + # 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] |