aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2017-01-08 00:23:24 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2017-01-08 00:23:24 +0100
commitd8c8bb05d9a5c1ab759e8155d10dba3a64139714 (patch)
tree38b89a86e54a916f38f950e576947c2394708538
parent0958273a71dd19c2a90471a182ccc5b90b14e5b4 (diff)
downloadeda-rdf-d8c8bb05d9a5c1ab759e8155d10dba3a64139714.tar.gz
eda-rdf-d8c8bb05d9a5c1ab759e8155d10dba3a64139714.tar.bz2
eda-rdf-d8c8bb05d9a5c1ab759e8155d10dba3a64139714.tar.xz
eda-rdf-d8c8bb05d9a5c1ab759e8155d10dba3a64139714.zip
Starting on a KiCAD ontology.
o Supporting many version of a single project.
-rw-r--r--owl/kicad.owl76
-rw-r--r--trygvis/eda/cli/__init__.py7
-rwxr-xr-xtrygvis/eda/cli/db_stats.py2
-rwxr-xr-xtrygvis/eda/cli/digikey_download_for_project.py15
-rw-r--r--trygvis/eda/cli/eda_rdf.py16
-rwxr-xr-xtrygvis/eda/cli/kicad_import_project.py92
-rwxr-xr-xtrygvis/eda/cli/make_bom.py84
-rw-r--r--trygvis/eda/kicad/export/__init__.py2
-rw-r--r--trygvis/eda/kicad/rdf.py13
9 files changed, 228 insertions, 79 deletions
diff --git a/owl/kicad.owl b/owl/kicad.owl
new file mode 100644
index 0000000..ae9c763
--- /dev/null
+++ b/owl/kicad.owl
@@ -0,0 +1,76 @@
+Prefix(:=<https://trygvis/purl/kicad#>)
+Prefix(owl:=<http://www.w3.org/2002/07/owl#>)
+Prefix(rdf:=<http://www.w3.org/1999/02/22-rdf-syntax-ns#>)
+Prefix(xml:=<http://www.w3.org/XML/1998/namespace>)
+Prefix(xsd:=<http://www.w3.org/2001/XMLSchema#>)
+Prefix(rdfs:=<http://www.w3.org/2000/01/rdf-schema#>)
+Prefix(kicad-random:=<https://trygvis/purl/kicad-random/>)
+
+
+Ontology(<https://trygvis/purl/kicad>
+
+Declaration(Class(:component))
+Declaration(Class(:field))
+Declaration(Class(:footprint))
+Declaration(Class(:project))
+Declaration(Class(:project-version))
+Declaration(Class(:sheet))
+Declaration(ObjectProperty(:hasComponent))
+Declaration(ObjectProperty(:hasProjectVersion))
+Declaration(ObjectProperty(:hasTimestamp))
+Declaration(DataProperty(:field-name))
+Declaration(DataProperty(:field-value))
+Declaration(DataProperty(:timestamp))
+
+############################
+# Object Properties
+############################
+
+# Object Property: owl:topObjectProperty (owl:topObjectProperty)
+
+ObjectPropertyDomain(owl:topObjectProperty :project)
+
+# Object Property: :hasComponent (:hasComponent)
+
+InverseObjectProperties(:hasComponent :hasProjectVersion)
+ObjectPropertyDomain(:hasComponent :project-version)
+ObjectPropertyRange(:hasComponent :component)
+
+# Object Property: :hasProjectVersion (:hasProjectVersion)
+
+ObjectPropertyDomain(:hasProjectVersion :component)
+ObjectPropertyRange(:hasProjectVersion :project-version)
+
+# Object Property: :hasTimestamp (:hasTimestamp)
+
+ObjectPropertyDomain(:hasTimestamp :project-version)
+
+
+############################
+# Data Properties
+############################
+
+# Data Property: :field-name (:field-name)
+
+DataPropertyRange(:field-name xsd:string)
+
+# Data Property: :field-value (:field-value)
+
+DataPropertyRange(:field-value xsd:string)
+
+# Data Property: :timestamp (:timestamp)
+
+DataPropertyRange(:timestamp xsd:dateTime)
+
+
+
+############################
+# Classes
+############################
+
+# Class: :project (:project)
+
+AnnotationAssertion(rdfs:comment :project "KiCAD Project"@en)
+
+
+) \ No newline at end of file
diff --git a/trygvis/eda/cli/__init__.py b/trygvis/eda/cli/__init__.py
index aa61021..422cf48 100644
--- a/trygvis/eda/cli/__init__.py
+++ b/trygvis/eda/cli/__init__.py
@@ -3,6 +3,7 @@ import logging
from genericpath import isfile
from os import mkdir
from os.path import dirname, isdir
+from typing import Callable
from rdflib import store, ConjunctiveGraph, Graph, RDF, RDFS
from rdflib.plugins.sparql import prepareQuery
@@ -92,11 +93,11 @@ def create_graph(digikey=False, kicad=False) -> Graph:
if kicad:
g.bind("kicad", kicad_rdf.KICAD)
- g.bind("kicad-type", kicad_rdf.KICAD_TYPE)
+ g.bind("kicad-random", kicad_rdf.KICAD_RANDOM)
return g
-def write_graph(gen_g: Graph, filename: str = None, force_write: bool = False):
+def write_graph(gen_g: Callable[[], Graph], filename: str = None, force_write: bool = False):
if filename is not None:
if force_write or not isfile(filename):
parent = dirname(filename)
@@ -129,7 +130,7 @@ _initNs = {
"dk-part": digikey_rdf.DIGIKEY_PART,
"dk-p-c": digikey_rdf.DIGIKEY_PRODUCT_CATEGORY,
"kicad": kicad_rdf.KICAD,
- "kicad-type": kicad_rdf.KICAD_TYPE}
+ "kicad-random": kicad_rdf.KICAD_RANDOM}
def sparql(g: Graph, query: str, init_bindings=None):
diff --git a/trygvis/eda/cli/db_stats.py b/trygvis/eda/cli/db_stats.py
index babc53b..430c1f3 100755
--- a/trygvis/eda/cli/db_stats.py
+++ b/trygvis/eda/cli/db_stats.py
@@ -6,7 +6,7 @@ def run(args: object):
res = cli.sparql(g, """
SELECT ?project ?label
WHERE {
- ?project a kicad-type:project
+ ?project a kicad:project
OPTIONAL {
?project rdfs:label ?label
}
diff --git a/trygvis/eda/cli/digikey_download_for_project.py b/trygvis/eda/cli/digikey_download_for_project.py
index 02e3cb1..a7d93e7 100755
--- a/trygvis/eda/cli/digikey_download_for_project.py
+++ b/trygvis/eda/cli/digikey_download_for_project.py
@@ -24,20 +24,7 @@ def work(project_url, force, g):
db = DigikeyDatabase()
download_category_tree(db, client)
- # Dump project:
- # SELECT ?project
- # WHERE {
- # ?project a kicad-type:project .
- # b:rateboard-2_a ? ?.
- # ?s ?predicate ?object .
- # }
-
- # 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={'project_url': project_url}):
- # print("ref=%s, value=%s" % (row.ref, row.value))
-
- res = cli.sparql(g, "SELECT ?project WHERE {?project a kicad-type:project}")
+ res = cli.sparql(g, "SELECT ?project WHERE {?project a kicad:project}")
print("Found %d projects in database" % len(res))
for row in res:
print("project: %s" % row.project)
diff --git a/trygvis/eda/cli/eda_rdf.py b/trygvis/eda/cli/eda_rdf.py
index 0df66ac..ff11fda 100644
--- a/trygvis/eda/cli/eda_rdf.py
+++ b/trygvis/eda/cli/eda_rdf.py
@@ -1,6 +1,7 @@
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 trygvis.eda.cli.make_bom import MakeBomCommand
from . import *
@@ -61,19 +62,6 @@ class DigikeyDownloadAttributeTypesForCategory(CliCommand):
digikey_download_attribute_types_for_category.run(args.category, args.sub_category, args.output, args)
-class MakeBom(CliCommand):
- def __init__(self):
- super().__init__("make-bom", "Create a BOM for a project with all info for each part.")
-
- def run(self, argv):
- p = argparse.ArgumentParser(prog=self.key, description=self.description)
- p.add_argument("--schematic", required=True)
- args = p.parse_args(argv)
-
- from trygvis.eda.cli import make_bom
- make_bom.run(args.schematic)
-
-
def main():
initialize()
@@ -81,7 +69,7 @@ def main():
AddToDb(),
InitCommand(),
DbStats(),
- MakeBom(),
+ MakeBomCommand(),
KicadImportProjectCommand(),
DigikeyDownloadForProjectCommand(),
DigikeyDownloadAttributeTypesForCategory(),
diff --git a/trygvis/eda/cli/kicad_import_project.py b/trygvis/eda/cli/kicad_import_project.py
index 288b7fc..422f24a 100755
--- a/trygvis/eda/cli/kicad_import_project.py
+++ b/trygvis/eda/cli/kicad_import_project.py
@@ -1,10 +1,11 @@
import os.path
from operator import attrgetter
import itertools
+from uuid import uuid4, UUID
+import datetime
import rdflib
from rdflib import Literal, URIRef
-from rdflib.namespace import RDF, RDFS
# from ..kicad import rdf as kicad_rdf
from . import *
@@ -27,6 +28,9 @@ class KicadImportProjectCommand(CliCommand):
def run(args):
config = read_config()
+ version = None
+ timestamp = None # type: datetime.datetime
+
if args.input is "-":
src = sys.stdin
else:
@@ -40,30 +44,49 @@ def run(args):
raise CliException("No such file: %s. Did you export the BOM?" % filename)
src = open(filename, 'r')
+ s = os.stat(filename)
+ timestamp = datetime.datetime.fromtimestamp(s.st_mtime)
project_url = config['project']['url']
+ if version is None:
+ if timestamp is None:
+ version = uuid4()
+ timestamp = datetime.datetime.now()
+ else:
+ from random import Random
+ r = Random(timestamp.year + timestamp.month + timestamp.day + timestamp.hour + timestamp.minute +
+ timestamp.second + timestamp.microsecond)
+ s = ["0123456789abcdef"[int(r.random() * 16)] for _ in range(0, 32)]
+ version = UUID(''.join(s))
+
+ info("Version: %s, timestamp=%s" % (version, timestamp))
+
with src:
- project = export_to_graph(src, project_url)
+ (version, project_g) = export_to_graph(src, project_url, version, timestamp)
- debug('Loaded %s tuples' % len(project))
+ debug('Loaded %s tuples' % len(project_g))
+
+ project_g.add((URIRef(project_url), RDF.type, kicad_rdf.KICAD['project']))
+ project_g.add((URIRef(project_url), kicad_rdf.KICAD['version'], version))
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')
+
+ v = timestamp.strftime("%Y-%m-%d %H:%M:%S")
+ output_file = os.path.join(parent, "project-%s.ttl" % v)
+ write_graph(lambda: project_g, filename=output_file, force_write=True)
def import_project(g):
- for idx, t in enumerate(project.triples((None, None, None))):
+ for idx, t in enumerate(project_g.triples((None, None, None))):
g.add(t)
with_database(import_project)
-def export_to_graph(src: object, project_url: str) -> rdflib.Graph:
+def export_to_graph(src: object, project_url: str, version: str, timestamp: datetime.datetime) -> (str, rdflib.Graph):
export = Export.from_xml_file(src)
# print('components:')
@@ -135,8 +158,11 @@ def export_to_graph(src: object, project_url: str) -> rdflib.Graph:
g = create_graph(kicad=True)
- project = URIRef(project_url)
- g.add((project, RDF.type, kicad_rdf.KICAD_TYPE.project))
+ version_uri = URIRef('urn:uuid:%s#' % version)
+ g.add((version_uri, RDF.type, kicad_rdf.KICAD["project-version"]))
+ g.add((version_uri, RDFS.label, Literal('Version: %s' % timestamp.strftime("%Y-%m-%d %H:%M:%S"))))
+ g.add((version_uri, kicad_rdf.KICAD['version-of'], URIRef(project_url)))
+ g.add((version_uri, kicad_rdf.KICAD['timestamp'], Literal(timestamp)))
# 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.
@@ -145,29 +171,43 @@ def export_to_graph(src: object, project_url: str) -> rdflib.Graph:
# add_prefix(hash) => 'https://trygvis.io/purl/kicad/generated#123456'
footprints = set()
for c in export.components:
- ns = rdflib.Namespace(project_url)
+ ns = rdflib.Namespace(str(version_uri))
node = ns[c.ref]
- g.add((project, kicad_rdf.KICAD.component, node))
+ g.add((version_uri, kicad_rdf.KICAD['component'], node))
- g.add((node, RDF.type, kicad_rdf.KICAD_TYPE.schematic_component))
+ g.add((node, RDF.type, kicad_rdf.KICAD['component']))
g.add((node, RDFS.label, Literal(c.ref)))
- g.add((node, kicad_rdf.KICAD.value, Literal(c.value.text)))
+ 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))
+ if footprint_uri not in footprints:
+ g.add((footprint_uri, RDF.type, kicad_rdf.KICAD['footprint']))
g.add((footprint_uri, RDFS.label, Literal(c.footprint)))
footprints.add(footprint_uri)
- g.add((node, kicad_rdf.KICAD.footprint, 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
+ # f = ns['%s-%s' % (c.ref, name)]
+ f = kicad_rdf.gen_random('%s-%s' % (c.ref, name))
+ g.add((node, kicad_rdf.KICAD['field'], f))
+ g.add((f, RDF.type, kicad_rdf.KICAD['field']))
+ g.add((f, kicad_rdf.KICAD['field-name'], Literal(name)))
+ g.add((f, kicad_rdf.KICAD['field-value'], Literal(value)))
+
+ d = export.design
+ if d is not None:
+ g.add((version_uri, kicad_rdf.KICAD['design-date'], Literal(d.date)))
+
+ for s in d.sheets:
+ sheet_uri = kicad_rdf.gen_random(s.number)
+ g.add((sheet_uri, RDF.type, kicad_rdf.KICAD['sheet']))
+ label = s.title_block.title if s.title_block.title is not None and len(s.title_block.title) > 0 else s.name
+ g.add((sheet_uri, RDFS.label, Literal(label)))
+ g.add((sheet_uri, kicad_rdf.KICAD['sheet-name'], Literal(s.name)))
+ g.add((sheet_uri, kicad_rdf.KICAD['sheet-number'], Literal(int(s.number))))
+ g.add((sheet_uri, kicad_rdf.KICAD['title'], Literal(s.title_block.title)))
+ g.add((sheet_uri, kicad_rdf.KICAD['rev'], Literal(s.title_block.rev)))
+ g.add((sheet_uri, kicad_rdf.KICAD['date'], Literal(s.title_block.date)))
+
+ return version_uri, g
diff --git a/trygvis/eda/cli/make_bom.py b/trygvis/eda/cli/make_bom.py
index a47e04a..1023c11 100755
--- a/trygvis/eda/cli/make_bom.py
+++ b/trygvis/eda/cli/make_bom.py
@@ -1,7 +1,22 @@
-from trygvis.eda import cli
-import trygvis.eda.digikey.rdf as digikey_rdf
+import argparse
import rdflib
+import trygvis.eda.digikey.rdf as digikey_rdf
+from trygvis.eda import cli, EdaException
+
+
+class MakeBomCommand(cli.CliCommand):
+ def __init__(self):
+ super().__init__("make-bom", "Create a BOM for a project with all info for each part.")
+
+ def run(self, argv):
+ p = argparse.ArgumentParser(prog=self.key, description=self.description)
+ p.add_argument("--project")
+ p.add_argument("--version")
+ args = p.parse_args(argv)
+
+ run(args)
+
class DigiKeyPart(object):
def __init__(self, part_number):
@@ -22,49 +37,80 @@ class Component(object):
self.fields[field_name] = field_value
-def run(project_url):
- cli.with_database(lambda g: work(project_url, g))
+def run(args):
+ cli.with_database(lambda g: work(args, g))
-def work(project_url, g):
+def work(args, g):
+ config = cli.read_config()
+ project_url = args.project if args.project is not None else config['project']['url']
+
components = {}
dk_parts = {}
- cli.info("Loading components")
+ version = None
+ if args.version is not None:
+ version = args.version
+ else:
+ cli.info("Finding latest version")
+ res = cli.sparql(g, """
+SELECT
+?project ?version ?timestamp
+WHERE {
+ ?project a kicad:project .
+ ?version a kicad:project-version;
+ kicad:timestamp ?timestamp;
+ kicad:version-of ?project
+ .
+}
+ORDER BY DESC(?timestamp)
+LIMIT 1
+""", init_bindings={"project": rdflib.URIRef(project_url)})
+
+ for row in res:
+ version = row.version
+ break
+ else:
+ raise EdaException("Could not find any version of project %s" % project_url)
+
+ cli.info("Loading components for version %s" % version)
res = cli.sparql(g, """
SELECT
?ref ?value
WHERE {
- ?project a kicad-type:project ;
+ ?version a kicad:project-version ;
kicad:component ?cmp .
- ?cmp a kicad-type:schematic_component ;
- kicad:value ?value ;
- rdfs:label ?ref .
+ ?cmp a kicad:component ;
+ kicad:value ?value ;
+ rdfs:label ?ref .
}
ORDER BY ?ref
-""", init_bindings={"project": rdflib.URIRef(project_url)})
+""", init_bindings={"version": rdflib.URIRef(version)})
for row in res:
c = Component(row.ref, row.value)
components[row.ref] = c
cli.info('ref=%s, value=%s' % (c.ref, c.value))
+ if len(components) == 0:
+ raise EdaException("Could not find any components")
+
cli.info("Loading custom component attributes")
res = cli.sparql(g, """
SELECT
?ref ?field ?field_name ?field_value
WHERE {
- ?project a kicad-type:project ;
+ ?version a kicad:project-version ;
kicad:component ?cmp .
- ?cmp a kicad-type:schematic_component ;
+ ?cmp a kicad:component ;
rdfs:label ?ref ;
kicad:field ?field .
- ?field a kicad-type:field ; kicad:field_name ?field_name .
- ?field a kicad-type:field ; kicad:field_value ?field_value .
+ ?field a kicad:field ; kicad:field_name ?field_name .
+ ?field a kicad:field ; kicad:field_value ?field_value .
}
ORDER BY ?ref ?field_name
-""", init_bindings={"project": rdflib.URIRef(project_url)})
+""", init_bindings={"version": rdflib.URIRef(version)})
for row in res:
c = components[row.ref]
@@ -78,7 +124,7 @@ SELECT
?ref ?part_number ?type ?value ?attr_type
WHERE {
?project kicad:component ?cmp .
- ?cmp a kicad-type:schematic_component ;
+ ?cmp a kicad:component ;
rdfs:label ?ref ;
kicad:field ?d .
?d kicad:field_name "digikey" ;
@@ -114,7 +160,7 @@ SELECT
# *
WHERE {
?project kicad:component ?cmp .
- ?cmp a kicad-type:schematic_component ;
+ ?cmp a kicad:component ;
rdfs:label ?ref ;
kicad:field ?dk_field .
optional {
@@ -138,7 +184,7 @@ WHERE {
# ?case_type a dk:attributeType ; dk:value ?case_url .
# } .
-# VALUES (?project_url) { (<https://trygvis/purl/kicad-board#SweetZpot+Sensor>) } .
+# VALUES (?project_url) { (...) } .
# VALUES (?package_type) { (dk-attr-type:pv16) } .
# VALUES (?case_type) { (dk-attr-type:pv1291) } .
}
diff --git a/trygvis/eda/kicad/export/__init__.py b/trygvis/eda/kicad/export/__init__.py
index 64b82d2..9f3e96e 100644
--- a/trygvis/eda/kicad/export/__init__.py
+++ b/trygvis/eda/kicad/export/__init__.py
@@ -174,7 +174,7 @@ class Sheet(object):
def __init__(self, number, name, title_block):
self.number = number
self.name = name
- self.title_block = title_block
+ self.title_block = title_block # type: TitleBlock
@staticmethod
def from_xml(s):
diff --git a/trygvis/eda/kicad/rdf.py b/trygvis/eda/kicad/rdf.py
index 34b73e1..2984fcd 100644
--- a/trygvis/eda/kicad/rdf.py
+++ b/trygvis/eda/kicad/rdf.py
@@ -1,8 +1,19 @@
import rdflib
+from trygvis.eda import EdaException
KICAD = rdflib.Namespace("https://trygvis/purl/kicad#")
-KICAD_TYPE = rdflib.Namespace("https://trygvis/purl/kicad-type#")
+KICAD_RANDOM = rdflib.Namespace("https://trygvis/purl/kicad-random/")
KICAD_FOOTPRINT = rdflib.Namespace("https://trygvis/purl/kicad-footprints#")
# Namespace for all unknown kicad boards
KICAD_BOARD = rdflib.Namespace("https://trygvis/purl/kicad-board#")
+
+
+def gen_random(key: object):
+ s = str(key)
+
+ if len(s) == 0:
+ raise EdaException("Can't generate a random URI for empty strings.")
+
+ s = str(abs(hash(s)))
+ return KICAD_RANDOM.term(s)