From a30b71772e7eb831e8d87759172a02e79f9673c4 Mon Sep 17 00:00:00 2001 From: Trygve Laugstøl Date: Tue, 17 Jul 2018 12:23:58 +0200 Subject: wip. pcb. --- src/ee/fact/__init__.py | 50 +++++++++++------------ src/ee/kicad/doit.py | 97 ++++++++++++++++++++++++++++++++------------ src/ee/kicad/pcb/__init__.py | 40 ++++++++++-------- 3 files changed, 120 insertions(+), 67 deletions(-) (limited to 'src') diff --git a/src/ee/fact/__init__.py b/src/ee/fact/__init__.py index 959e755..1bdef0d 100644 --- a/src/ee/fact/__init__.py +++ b/src/ee/fact/__init__.py @@ -1,12 +1,13 @@ -from typing import Optional, Mapping, List import configparser +import logging import os -from pathlib import Path from functools import total_ordering -import logging +from pathlib import Path +from typing import MutableMapping, Optional, Mapping, List logger = logging.getLogger(__name__) + @total_ordering class ObjectType(object): def __init__(self, name: str): @@ -14,28 +15,20 @@ class ObjectType(object): self._fields = [] self._objects = {} - def __eq__(self, o: object) -> bool: - other = o # type ObjectType + def __eq__(self, o) -> bool: + other = o # type: ObjectType return isinstance(o, ObjectType) and self._name == other._name def __lt__(self, o: object) -> bool: if not isinstance(o, ObjectType): return True - other = o # type ObjectType - return (self._name) < (self._name) + other = o # type: ObjectType + return self._name < other._name def __hash__(self) -> int: return self._name.__hash__() - def by_key(self, key: str) -> "Object": - try: - return self._objects[key] - except ValueError: - o = Object(self, key, {}, {}) - self._objects[key] = o - return o - @property def name(self): return self._name @@ -54,6 +47,7 @@ class ObjectType(object): self._fields.append(field) return len(self._fields) - 1 + class Object(object): def __init__(self, ds: "DataSet", ot: ObjectType, key: str): self._ds = ds @@ -81,13 +75,14 @@ class Object(object): def get(self, key: str) -> Optional[str]: idx = self._ot.index_of(key) - return self._data[idx] + return self._data[idx] if idx < len(self._data) else None + class DataSet(object): def __init__(self, name): self._name = name self._object_types = {} - self._objects_by_type = {} # type: Mapping[str, Mapping[str, Object]] + self._objects_by_type = {} # type: MutableMapping[str, Mapping[str, Object]] self._frozen = False self._changed = False @@ -146,6 +141,7 @@ class DataSet(object): return ds + class DataSetManager(object): def __init__(self, basedir: Path): self._basedir = Path(basedir) @@ -181,7 +177,7 @@ class DataSetManager(object): ini = self._load_ini(o_path) o = ds.get_object(ot, key) for k, v in ini.items("values"): - o.set(k, v) + o.set(k, v) if freeze: ds.freeze() @@ -212,11 +208,13 @@ class DataSetManager(object): ini.add_section("values") for k in ot.fields: v = o.get(k) - ini.set("values", k, v) + if v: + ini.set("values", k, str(v)) self._store_ini(ini, ot_dir / "{}.ini".format(key)) - def _blank_ini(self): - return configparser.ConfigParser(interpolation = None) + @staticmethod + def _blank_ini(): + return configparser.ConfigParser(interpolation=None) def _load_ini(self, path: Path): ini = self._blank_ini() @@ -224,10 +222,12 @@ class DataSetManager(object): raise IOError("Could not load ini file: {}".format(path)) return ini - def _store_ini(self, ini, path): + @staticmethod + def _store_ini(ini, path): with open(path, "w") as f: ini.write(f) + class LazyDataSet(object): def __init__(self, dsm: DataSetManager, name, inputs): self._dsm = dsm @@ -235,7 +235,7 @@ class LazyDataSet(object): self._inputs = inputs def __enter__(self): -# logger.info("enter: name={}, inputs={}".format(self._name, self._inputs)) + # logger.info("enter: name={}, inputs={}".format(self._name, self._inputs)) ds = DataSet(self._name) for name in self._inputs: ds = ds.merge(self._dsm.load(name, freeze=True)) @@ -243,7 +243,7 @@ class LazyDataSet(object): return self._ds def __exit__(self, *args): -# logger.info("exit: name={}, inputs={}".format(self._name, self._inputs)) -# logger.info("ds.size={}".format(len(self._ds.items()))) + # logger.info("exit: name={}, inputs={}".format(self._name, self._inputs)) + # logger.info("ds.size={}".format(len(self._ds.items()))) self._dsm.store(self._ds) return False diff --git a/src/ee/kicad/doit.py b/src/ee/kicad/doit.py index 10de885..fcbcaef 100644 --- a/src/ee/kicad/doit.py +++ b/src/ee/kicad/doit.py @@ -1,13 +1,14 @@ +import logging import os.path -from doit.exceptions import TaskFailed -from doit.tools import check_timestamp_unchanged -from configclass import Config from typing import List + +from configclass import Config + from ee.fact import DataSetManager -import logging logger = logging.getLogger(__name__) + class KicadDoitTasks(object): config = Config({ "sch": None, @@ -15,7 +16,7 @@ class KicadDoitTasks(object): "gerber_dir": None, "gerber_zip": None, "data_set_dir": None, - }) + }) def __init__(self, *args, **kwargs): self.config = self.config.make(kwargs) @@ -24,7 +25,6 @@ class KicadDoitTasks(object): logger.info("_task: args={}, kwars={}".format(args, kwargs)) def tasks(self, *args, **kwargs): - import ee.kicad kicad_pcb = self.config["kicad_pcb"] sch = self.config["sch"] tasks = [] @@ -32,24 +32,32 @@ class KicadDoitTasks(object): dsm = DataSetManager(self.config["data_set_dir"]) gerber_dir = self.config["gerber_dir"] - if gerber_dir: - tasks.append(task_kicad_gerber()) + gerber_zip = self.config["gerber_zip"] + if kicad_pcb and gerber_dir: + tasks.append(task_kicad_gerber(kicad_pcb, gerber_dir, gerber_zip)) - sch_ds = task_kicad_sch_to_data_set(dsm, sch, \ - in_data_sets=[], \ - out_data_set="kicad-sch-data-set") \ - if sch else None + sch_ds = task_kicad_sch_to_data_set(dsm, sch, + in_data_sets=[], + out_data_set="kicad-sch-data-set") \ + if sch else None - component_ds = task_kicad_create_component_data_set(dsm, \ - in_data_sets=sch_ds["targets"], \ - out_data_set="raw-component") \ - if sch_ds else None + component_ds = task_kicad_create_component_data_set(dsm, + in_data_sets=sch_ds["targets"], + out_data_set="raw-component") \ + if sch_ds else None - return (t for t in [sch_ds, component_ds] if t is not None) + pcb_ds = task_kicad_pcb_to_data_set(dsm, kicad_pcb, in_data_sets=[], out_data_set="kicad-pcb") \ + if kicad_pcb else None -def task_kicad_gerber(name="kicad-gerber"): - gerber_zip = self.config["gerber_zip"] or "{}.zip".format(gerber_dir) - #logger.info("gerber_zip={}".format(gerber_zip)) + tasks = [sch_ds, component_ds, pcb_ds] + return (t for t in tasks if t) + + +def task_kicad_gerber(kicad_pcb: str, gerber_dir: str, gerber_zip: str, name="kicad-gerber"): + import ee.kicad + + gerber_zip = len(gerber_zip) or "{}.zip".format(gerber_dir) + # logger.info("gerber_zip={}".format(gerber_zip)) eg = next((p for p in (os.path.join(p, "export_gerber.py") for p in ee.kicad.__path__) if os.path.isfile(p)), None) if not eg: @@ -63,6 +71,7 @@ def task_kicad_gerber(name="kicad-gerber"): "--output-directory", gerber_dir, "--protel-extensions", ]) + def make_zip(): import zipfile from pathlib import Path @@ -79,12 +88,12 @@ def task_kicad_gerber(name="kicad-gerber"): "file_dep": [kicad_pcb], } -def task_kicad_sch_to_data_set(dsm: DataSetManager, sch, in_data_sets: List[str], out_data_set, name="kicad-sch-to-data-set"): + +def task_kicad_sch_to_data_set(dsm: DataSetManager, sch, in_data_sets: List[str], out_data_set, + name="kicad-sch-to-data-set"): def action(): import ee.kicad - from ee.kicad.model import Component, ComponentField - import csv - import configparser + from ee.kicad.model import ComponentField with dsm.create_rw(out_data_set, inputs=in_data_sets) as ds: schematics = ee.kicad.read_schematics(sch) @@ -109,10 +118,46 @@ def task_kicad_sch_to_data_set(dsm: DataSetManager, sch, in_data_sets: List[str] "targets": [dsm.metafile_for_ds(out_data_set)], } -def task_kicad_create_component_data_set(dsm: DataSetManager, in_data_sets: List[str], out_data_set, name="kicad-create-component-data-set"): + +def task_kicad_pcb_to_data_set(dsm: DataSetManager, pcb_path, in_data_sets: List[str], out_data_set, + name="kicad-pcb-to-data-set"): + def action(): + import ee.kicad.pcb + from ee.kicad.pcb import FpText + + with dsm.create_rw(out_data_set, inputs=in_data_sets) as ds: + # [ds.delete(o) for o in ds.items(object_type="kicad-pcb-component")] + + pcb: ee.kicad.pcb.KicadPcb = ee.kicad.pcb.parse(pcb_path) + for _m in pcb.modules: + m: ee.kicad.pcb.Module = _m + logger.info("attrs") + for s in dir(m): + logger.info(s) + + o = ds.get_object("kicad-pcb-component", m.tstamp) + + ref_text: FpText = next((t for t in m.fp_texts if t.kind == "reference"), None) + o.set("reference", ref_text.value) + + x, y, rot = m.at + o.set("at-x", x) + o.set("at-y", y) + o.set("at-rot", rot) + o.set("layer", m.layer) + + return { + "name": name, + "file_dep": [pcb_path] + [dsm.metafile_for_ds(ds) for ds in in_data_sets], + "actions": [action], + "targets": [dsm.metafile_for_ds(out_data_set)], + } + + +def task_kicad_create_component_data_set(dsm: DataSetManager, in_data_sets: List[str], out_data_set, + name="kicad-create-component-data-set"): def action(*args, **kwargs): logger.info("args={}, kwargs={}".format(args, kwargs)) - import ee.fact as fact with dsm.create_rw(out_data_set, inputs=in_data_sets) as ds: items = ds.items() diff --git a/src/ee/kicad/pcb/__init__.py b/src/ee/kicad/pcb/__init__.py index 79987e6..25689ef 100644 --- a/src/ee/kicad/pcb/__init__.py +++ b/src/ee/kicad/pcb/__init__.py @@ -49,18 +49,21 @@ class Pad(object): @auto_str class FpText(object): def __init__(self, **kwargs): + self.kind = None # type: str + self.value = None # type: str for k, v in kwargs.items(): setattr(self, k, v) -def parse(path): + +def parse(path) -> KicadPcb: count = 0 p = sexpr.parse(path) #p = sexpr.logging_parser(p) - (event, token) = next(p) - assert event == sexpr.EVENT_LPAREN + (e, t) = next(p) + assert e == sexpr.EVENT_LPAREN - (event, token) = next(p) - assert event == sexpr.EVENT_TEXT and token == "kicad_pcb" + (e, t) = next(p) + assert e == sexpr.EVENT_TEXT and t == "kicad_pcb" idx = 0 def _consume(): @@ -134,10 +137,14 @@ def parse(path): pads = [] fp_texts = [] - args = {} - args["footprint"] = _parse_text() - args["pads"] = pads - args["fp_texts"] = fp_texts + args = { + "footprint": _parse_text(), + "pads": pads, + "fp_texts": fp_texts, + "layer": None, + "tedit": None, + "tstamp": None, + } (event, token) = next(p) while event == sexpr.EVENT_TEXT: @@ -145,12 +152,12 @@ def parse(path): while event == sexpr.EVENT_LPAREN: (event, token) = next(p) - if token == "layer": - args[token] = _parse_text(rparen = True) + if token in ["layer", "tedit", "tstamp"]: + args[token] = _parse_text(rparen=True) elif token == "at": args[token] = _parse_at() elif token == "attr": - args[token] = [_parse_text(rparen = True)] + args[token] = [_parse_text(rparen=True)] elif token == "pad": pads.append(_parse_pad()) elif token == "fp_text": @@ -163,8 +170,9 @@ def parse(path): def _parse_pad(): texts = [] - args = {} - args["footprint"] = _parse_text() + args = { + "footprint": _parse_text() + } (event, token) = next(p) while event == sexpr.EVENT_TEXT: @@ -182,7 +190,7 @@ def parse(path): return Pad(**args) - def _parse_fp_text(): + def _parse_fp_text() -> FpText: args = { "kind": _parse_text(), "value": _parse_text(), @@ -202,7 +210,7 @@ def parse(path): x = _parse_text(to=float) y = _parse_text(to=float) rot = _parse_text(to=float, optional = True, rparen = True) - return (x, y, rot or 0) + return x, y, rot or 0 kicad_pcb = _parse_kicad_pcb() assert next(p, None) == None -- cgit v1.2.3