import logging import os.path from pathlib import Path from typing import Mapping from configclass import Config import ee.kicad import ee.kicad.pcb from ee.ds import DataSetManager logger = logging.getLogger(__name__) _config_template = Config({ "sch": None, "kicad_pcb": None, "gerber_dir": None, "gerber_zip": None, }) _config = None # type: Mapping[str, str] _dsm = None # type: DataSetManager _data_sets = {} def change_data_sets_for_task(task, callable): _data_sets[task] = callable(_data_sets[task]) def init(data_set_manager: DataSetManager, **kwargs): global _config _config = _config_template.make(kwargs) ee_logger = logging.getLogger("ee") formatter = logging.Formatter("%(levelname)s: %(message)s") ch = logging.StreamHandler() ch.setFormatter(formatter) ee_logger.addHandler(ch) ee_logger.setLevel(logging.DEBUG) global _dsm _dsm = data_set_manager def task_kicad_gerber(): kicad_pcb = _config["kicad_pcb"] gerber_dir = _config["gerber_dir"] gerber_zip = _config["gerber_zip"] gerber_zip = 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: raise Exception("Could not find export_gerber.py") # TODO: replace with python mkdir = "mkdir -p {}".format(gerber_dir) export_gerber = " ".join([ eg, "--pcb", kicad_pcb, "--output-directory", gerber_dir, "--protel-extensions", ]) def make_zip(): import zipfile from pathlib import Path with zipfile.ZipFile(gerber_zip, "w") as z: for p in Path(gerber_dir).iterdir(): if not p.is_file(): continue z.write(p, arcname=p.relative_to(gerber_dir)) return { "targets": [gerber_zip], "actions": [mkdir, export_gerber, make_zip], "file_dep": [kicad_pcb], } def task_kicad_sch_to_data_set(): out_data_set = "kicad-sch" in_data_sets = _data_sets[task_kicad_sch_to_data_set] sch = _config["sch"] def action(): from ee.kicad.model import ComponentField with _dsm.create_rw(out_data_set) as ds: schematics = ee.kicad.read_schematics(sch) for c in [c for c in schematics.components]: o = ds.create_object("kicad-schematic-component", c.timestamp) o.set("ref", c.ref) o.set("ref-type", c.ref_type) if c.has_ref_num: o.set("ref-num", str(c.ref_num)) o.set("value", c.value) if c.footprint: o.set("footprint", c.footprint) for f in c.fields: if f.value and f.name not in ComponentField.names: o.set(f.name, str(f.value)) return { "file_dep": [Path(sch)] + [_dsm.cookie_for_ds(ds) for ds in in_data_sets], "actions": [action], "targets": [_dsm.cookie_for_ds(out_data_set)], } _data_sets[task_kicad_sch_to_data_set] = [] def task_kicad_pcb_to_data_set(): kicad_pcb = _config["kicad_pcb"] out_data_set = "kicad-pcb" in_data_sets = _data_sets[task_kicad_pcb_to_data_set] def action(): from ee.kicad.pcb import KicadPcb, Module logger.debug("Parsing PCB {}".format(kicad_pcb)) 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.parse(kicad_pcb) # type: KicadPcb for _m in pcb.modules: m = _m # type: Module o = ds.create_object("kicad-pcb-component", m.tstamp) ref_text = next((t for t in m.fp_texts if t.kind == "reference"), None) o.set("ref", ref_text.value) x, y, rot = m.at o.set("placement-x", x) o.set("placement-y", y) o.set("placement-rotation", rot) o.set("layer", m.layer) return { "file_dep": [Path(kicad_pcb)] + [_dsm.cookie_for_ds(ds) for ds in in_data_sets], "actions": [action], "targets": [_dsm.cookie_for_ds(out_data_set)], } _data_sets[task_kicad_pcb_to_data_set] = [] def task_kicad_create_component_data_set(): out_data_set = "components" in_data_sets = _data_sets[task_kicad_create_component_data_set] def action(): logger.info("in_data_sets={}, out_data_set={}".format(in_data_sets, out_data_set)) with _dsm.create_ro(in_data_sets) as in_ds: # for o in in_ds.items(): # logger.info("item: {}/{}".format(o.object_type.name, o.key)) def map_footprint(footprint): for o in in_ds.items(): if not o.object_type.name == "kicad-footprint-mapping": continue common = o.get("common") if common: return common return footprint with _dsm.create_rw(out_data_set) as output: kicad_sch = [o for o in in_ds.items() if o.object_type.name == "kicad-schematic-component"] logger.info("processing {} kicad-sch".format(len(kicad_sch))) ignored_ref_types = {"#PWR", } for sch in kicad_sch: ref = sch.get("ref") ref_num = sch.get("ref-num") if not ref or not ref_num: logger.debug("Missing ref or ref-num") continue ref_type = sch.get("ref-type") if not ref_type or ref_type in ignored_ref_types: logger.debug("Missing ref-type or bad ref-type: ref={}, ref-type={}".format(ref, ref_type)) continue c = output.create_object("component", ref) c.set("ref", ref) c.set("ref-num", ref_num) c.set("ref-type", ref_type) fp = sch.get("footprint") if fp: fp = map_footprint(fp) c.set("footprint", fp) def pcb_match(o): return o.object_type.name == "kicad-pcb-component" and \ o.get("ref") == ref pcb = [o for o in in_ds.items() if pcb_match(o)] if not pcb: logger.info("Could not find PCB component for {}".format(ref)) # TODO: check that the SCH and PCB footprint are the same # c.set("footprint", pcb.) return { "file_dep": [_dsm.cookie_for_ds(ds) for ds in in_data_sets], "actions": [action], "targets": [_dsm.cookie_for_ds(out_data_set)], } _data_sets[task_kicad_create_component_data_set] = ["kicad-sch", "kicad-pcb"] __all__ = [ init.__name__, change_data_sets_for_task.__name__, task_kicad_create_component_data_set.__name__, task_kicad_gerber.__name__, task_kicad_pcb_to_data_set.__name__, task_kicad_sch_to_data_set.__name__, ]