diff options
author | Trygve Laugstøl <trygvis@inamo.no> | 2019-02-09 23:37:02 +0100 |
---|---|---|
committer | Trygve Laugstøl <trygvis@inamo.no> | 2019-02-09 23:37:02 +0100 |
commit | c313e6de8c06017739402ea89f55ce3b36ac0f2b (patch) | |
tree | 3fa4ae2b36e5b3820c8677c82cafd676f77aaced | |
parent | 79b8525e776b27a1702a4eea6f3168bfd97a393a (diff) | |
download | ee-python-c313e6de8c06017739402ea89f55ce3b36ac0f2b.tar.gz ee-python-c313e6de8c06017739402ea89f55ce3b36ac0f2b.tar.bz2 ee-python-c313e6de8c06017739402ea89f55ce3b36ac0f2b.tar.xz ee-python-c313e6de8c06017739402ea89f55ce3b36ac0f2b.zip |
o kicad-mkdeps: new tool, new -M option for kicad-gerber. Both output a
Makefile-compatible dependencies file.
-rw-r--r-- | src/ee/__init__.py | 14 | ||||
-rw-r--r-- | src/ee/__main__.py | 2 | ||||
-rw-r--r-- | src/ee/_utils.py | 5 | ||||
-rwxr-xr-x | src/ee/kicad/export_gerber.py | 2 | ||||
-rw-r--r-- | src/ee/kicad/model.py | 6 | ||||
-rw-r--r-- | src/ee/kicad/read_schematic.py | 38 | ||||
-rwxr-xr-x | src/ee/tools/kicad_gerber.py | 81 | ||||
-rw-r--r-- | src/ee/tools/kicad_make_bom.py | 8 | ||||
-rw-r--r-- | src/ee/tools/kicad_mkdeps.py | 78 | ||||
-rw-r--r-- | test/test_bom.py | 12 | ||||
-rw-r--r-- | test/test_digikey.py | 2 |
11 files changed, 186 insertions, 62 deletions
diff --git a/src/ee/__init__.py b/src/ee/__init__.py index aa2c109..c7674c4 100644 --- a/src/ee/__init__.py +++ b/src/ee/__init__.py @@ -19,7 +19,7 @@ class EeException(Exception): class EeVal(object): from decimal import Decimal units = ['F', - 'Ohm', '\u2126', # Ohm symbol + 'Ohm', '\u2126', # Ohm symbol 'H'] exponents = { 'f': -15, @@ -63,7 +63,7 @@ class EeVal(object): e = math.ceil(math.log10(value)) exp = exp + e value = value * math.pow(10, -e) - return EeVal(None, value = float(value), exp = exp, unit = unit if len(unit) > 0 else None) + return EeVal(None, value=float(value), exp=exp, unit=unit if len(unit) > 0 else None) @property def value(self): @@ -75,9 +75,9 @@ class EeVal(object): def set(self, value=None, exp=None, unit=None): return EeVal(None, - value=value if value else self._value, - exp=exp if exp else self._exp, - unit=unit if unit else self._unit) + value=value if value else self._value, + exp=exp if exp else self._exp, + unit=unit if unit else self._unit) def __hash__(self): return hash((self.__float__(), self._unit)) @@ -86,7 +86,7 @@ class EeVal(object): return math.isclose(self.__float__(), other.__float__()) and self._unit == other._unit def __lt__(self, other): -# return ((self.__float__(), self._unit) < (other.__float__(), other._unit)) + # return ((self.__float__(), self._unit) < (other.__float__(), other._unit)) x = self.__float__() < other.__float__() if x != 0: @@ -120,7 +120,7 @@ class LtSpiceRaw(object): if expression is not None: v = [v for v in self.variables if v.expression == expression] if len(v) != 1: - raise Exception('Unknown variable: ' + str(variable)) + raise Exception('Unknown variable: ' + str(v)) return v[0] raise Exception('idx or expression must be given') diff --git a/src/ee/__main__.py b/src/ee/__main__.py index acb8cbf..6a7517f 100644 --- a/src/ee/__main__.py +++ b/src/ee/__main__.py @@ -45,7 +45,7 @@ def main(): if len(sys.argv) <= 1: eprint("Available tools:") for t in tools: - print(t.name) + print(" {}".format(t.name)) exit(1) name = sys.argv[1] del sys.argv[1] diff --git a/src/ee/_utils.py b/src/ee/_utils.py index 1548b89..29b039a 100644 --- a/src/ee/_utils.py +++ b/src/ee/_utils.py @@ -1,9 +1,10 @@ from typing import List -import pandas as pd +def ensure_has_columns(df: "pandas.DataFrame", columns: List[str]): + # We don't want to import pandas too soon + import pandas as pd -def ensure_has_columns(df: pd.DataFrame, columns: List[str]): all_columns = columns # print("all_columns={}".format(all_columns)) # print("df={}".format(df.columns.tolist())) diff --git a/src/ee/kicad/export_gerber.py b/src/ee/kicad/export_gerber.py index c4c72fd..fb3247f 100755 --- a/src/ee/kicad/export_gerber.py +++ b/src/ee/kicad/export_gerber.py @@ -69,6 +69,7 @@ if args.layer_extensions is not None: board = LoadBoard(args.pcb) + class Plan: def __init__(self, layerNum, layerName, description): self.layerNum = layerNum @@ -88,6 +89,7 @@ class Plan: description = "Copper Layer " + layerName return Plan(layerNum, layerName, description) + plot_plan = [Plan.standard(layerNum, description) for (layerNum, description) in [ (F_SilkS, "Silk front"), (F_Mask, "Mask front"), diff --git a/src/ee/kicad/model.py b/src/ee/kicad/model.py index 8a514cd..f6bd8c1 100644 --- a/src/ee/kicad/model.py +++ b/src/ee/kicad/model.py @@ -156,7 +156,8 @@ class Library(object): class Schematic(object): - def __init__(self): + def __init__(self, path): + self.path = path self._libraries = set() self._sheets = [] self._components = [] @@ -183,7 +184,7 @@ class Schematic(object): self._components.append(component) def get_component(self, ref, unit=1): - c = find_component(self, ref, unit) + c = self.find_component(ref, unit) if c: return c @@ -195,6 +196,7 @@ class Schematic(object): if c.ref == ref and unit == unit: return c + class Schematics(object): def __init__(self, schematics): self._schematics = schematics diff --git a/src/ee/kicad/read_schematic.py b/src/ee/kicad/read_schematic.py index 56cdca8..4a9bbdc 100644 --- a/src/ee/kicad/read_schematic.py +++ b/src/ee/kicad/read_schematic.py @@ -4,28 +4,32 @@ from ee.kicad.model import * import os.path -def read_schematics(path): - def read(path): - schematic = read_schematic(path) +# Reads all .sch files referenced from the given .sch file. +def read_schematics(path) -> Schematics: + def read(schematic_path): + schematic = read_schematic(schematic_path) schematics = [schematic] for sheet in schematic.sheets: - p = os.path.join(os.path.dirname(path), sheet.path) + p = os.path.join(os.path.dirname(schematic_path), sheet.path) children = read(p) schematics.extend(children) return schematics return Schematics(read(path)) -def read_schematic(path): + +# Reads a single .sch file. All references to other sheets are read and available as schematic.sheets +def read_schematic(path: str) -> Schematic: path_basename = os.path.basename(path) - schematic = Schematic() + schematic = Schematic(path) def descr_section(lines): # print("descr_section: len={}".format(len(lines))) pass def sheet_section(lines): - + name = None + sheet_path = None for line in lines: parts = shlex.split(line) if len(parts) < 2: @@ -33,9 +37,9 @@ def read_schematic(path): if parts[0] == "F0": name = parts[1] elif parts[0] == "F1": - path = parts[1] + sheet_path = parts[1] - schematic.add_sheet(Sheet(name, path)) + schematic.add_sheet(Sheet(name, sheet_path)) def comp_section(lines): # print("comp_section: len={}".format(len(lines))) @@ -95,7 +99,7 @@ def read_schematic(path): schematic.add_component(Component(position, timestamp, library, name, unit, ref, fields)) - def load(path, f): + def load(f): header = f.readline() line_number = 1 if "EESchema Schematic File Version" not in header: @@ -140,12 +144,12 @@ def read_schematic(path): # print("SECTION: {}".format(section_name)) section = [] elif parts[0:3] == ["Entry", "Wire", "Line"] or \ - parts[0:2] == ["Text", "Label"] or \ - parts[0:2] == ["Text", "Notes"] or \ - parts[0:2] == ["Text", "HLabel"] or \ - parts[0:3] == ["Wire", "Notes", "Line"] or \ - parts[0:3] == ["Wire", "Wire", "Line"] or \ - parts[0:3] == ["Wire", "Bus", "Line"]: + parts[0:2] == ["Text", "Label"] or \ + parts[0:2] == ["Text", "Notes"] or \ + parts[0:2] == ["Text", "HLabel"] or \ + parts[0:3] == ["Wire", "Notes", "Line"] or \ + parts[0:3] == ["Wire", "Wire", "Line"] or \ + parts[0:3] == ["Wire", "Bus", "Line"]: f.readline() # ignore the next line for now line_number = line_number + 1 elif line.startswith("NoConn "): @@ -159,4 +163,4 @@ def read_schematic(path): return schematic with open(path) as file: - return load(path, file) + return load(file) diff --git a/src/ee/tools/kicad_gerber.py b/src/ee/tools/kicad_gerber.py index be0e34d..7f15154 100755 --- a/src/ee/tools/kicad_gerber.py +++ b/src/ee/tools/kicad_gerber.py @@ -1,8 +1,29 @@ -#!/usr/bin/env python +from __future__ import print_function import sys import os import argparse -from pcbnew import * + +try: + from pcbnew import * +except ImportError: + + ee_hack = os.environ.get("EE_HACK", "0") + print("ee_hack={}".format(ee_hack), file=sys.stderr) + try_p2 = sys.version_info.major == 2 and ee_hack != "1" + print("Could not import 'pcbnew' module. Make sure you run this command with KiCAD's python.", file=sys.stderr) + + argv = sys.argv[1:] + cmd = "python2 {} {}".format(__file__, " ".join(argv)) + print(cmd, file=sys.stderr) + + import subprocess + + subprocess.call("sleep 1", shell=True) + env = dict(os.environ) + env["EE_HACK"] = "1" + ret = subprocess.call(cmd, shell=True, env=env) + print("ret={}".format(ret), file=sys.stderr) + sys.exit(ret) def layer_name_parser(s): @@ -32,6 +53,11 @@ parser.add_argument('--detect-files-only', action='store_true', help='Don\'t create the GERBER files, just list the files to be created') +parser.add_argument('-M', + dest='mkdep', + action='store_true', + help='Don\'t create the GERBER files, output a Makefile-compatible file') + parser.add_argument('--create-drill-map-file', dest='create_drill_map_file', action='store_true', @@ -42,6 +68,11 @@ parser.add_argument('--protel-extensions', action='store_true', help='Use Protel filename extensions instead of .gbr') +parser.add_argument('--extended-gerber-attributes', + dest='extended_gerber_attributes', + action='store_true', + help='Use extended Gerber attributes') + parser.add_argument('--uppercase-extensions', action='store_true', help='Uppercase all extensions') @@ -101,7 +132,16 @@ for layerNum in layers.CuStack(): pctl = PLOT_CONTROLLER(board) popt = pctl.GetPlotOptions() -popt.SetOutputDirectory(args.output_directory) + +output_directory = args.output_directory +popt.SetOutputDirectory(output_directory) + +if not os.path.isdir(output_directory): + try: + os.makedirs(output_directory) + except: + print("Could not make output directory", file=sys.stderr) + sys.exit(1) # A nasty hack to get the base filename pctl.SetLayer(F_Cu) @@ -113,12 +153,12 @@ except: pass pctl.ClosePlot() -if args.protel_extensions: - popt.SetUseGerberProtelExtensions(True) +# "Use protel filename extensions", default=False +popt.SetUseGerberProtelExtensions(args.protel_extensions) basename = os.path.splitext(filename)[0] -drlFileOut = drlFile = basename + ".drl" +drlFileOut = drlFile = basename + "-PTH.drl" drlNpthFileOut = drlNpthFile = basename + "-NPTH.drl" if args.protel_extensions: @@ -162,16 +202,21 @@ for plan in plot_plan: # print "filename = " + plan.filename + ", postfix=" + plan.postfix # print "filename: " + plan.filename -if args.detect_files_only: - for plan in plot_plan: - print - plan.filename +if args.detect_files_only or args.mkdep: + if not args.mkdep: + for plan in plot_plan: + print(plan.filename) + + print(drlFileOut) + print(drlNpthFileOut) + else: + print("GERBERS = ") + for plan in plot_plan: + print("GERBERS += {}".format(plan.filename)) + print("GERBERS += {}".format(drlFileOut, args.pcb)) + print("GERBERS += {}".format(drlNpthFileOut, args.pcb)) + print("$(GERBERS): {}".format(args.pcb)) - if args.protel_extensions: - print - drlFileOut - print - drlNpthFileOut sys.exit(0) # Set some important plot options: @@ -181,7 +226,7 @@ popt.SetLineWidth(FromMM(0.35)) popt.SetAutoScale(False) popt.SetScale(1) popt.SetMirror(False) -popt.SetUseGerberAttributes(True) +popt.SetUseGerberAttributes(args.extended_gerber_attributes) popt.SetScale(1) popt.SetUseAuxOrigin(True) @@ -226,8 +271,6 @@ drlwriter.CreateDrillandMapFilesSet(pctl.GetPlotDirName(), genDrl, genMap) if drlFile != drlFileOut: os.rename(drlFile, drlFileOut) - pass -if drlFile != drlNpthFileOut: +if drlNpthFile != drlNpthFileOut: os.rename(drlNpthFile, drlNpthFileOut) - pass diff --git a/src/ee/tools/kicad_make_bom.py b/src/ee/tools/kicad_make_bom.py index 9f67899..2600968 100644 --- a/src/ee/tools/kicad_make_bom.py +++ b/src/ee/tools/kicad_make_bom.py @@ -4,6 +4,8 @@ from ee.tools import mk_parents from xml.etree import ElementTree from xml.dom import minidom +pretty = True # we always pretty print the XML + parser = argparse.ArgumentParser(description="Create a bom XML file from a KiCAD schematic") parser.add_argument("--sch", @@ -15,10 +17,6 @@ parser.add_argument("--out", metavar="FILE", help="The output file") -parser.add_argument("--pretty", - action='store_true', - help="Pretty print the XML") - args = parser.parse_args() sch = kicad.read_schematics(args.sch) @@ -26,7 +24,7 @@ sch = kicad.read_schematics(args.sch) bom = kicad.to_bom_xml(sch) xml = ElementTree.tostring(bom, encoding='unicode') -if args.pretty: +if pretty: xml = minidom.parseString(xml).toprettyxml(indent=" ") if args.out: diff --git a/src/ee/tools/kicad_mkdeps.py b/src/ee/tools/kicad_mkdeps.py new file mode 100644 index 0000000..10c7e97 --- /dev/null +++ b/src/ee/tools/kicad_mkdeps.py @@ -0,0 +1,78 @@ +from __future__ import print_function +import sys +import argparse +import ee.kicad as kicad + +out_file = sys.stdout + + +def p(msg): + global out_file + print(msg, file=out_file) + + +def work(args): + sch = kicad.read_schematics(args.sch) + + p("# This file is generated") + p("ifeq ($(EE),)") + p(" $(error EE must be set)") + p("endif") + p("") + p("SCH_FILES =") + for s in sch.schematics: + p("SCH_FILES += {}".format(s.path)) + + if args.gerber: + p("# Gerber rules") + p("ifeq ($(GERBER_ZIP),)") + p(" $(error GERBER_ZIP must be set)") + p("endif") + p("gerbers: $(GERBER_ZIP)") + p("$(GERBER_ZIP): $(SCH_FILES)") + p("\t@echo GERBER") + p("\t$(EE) kicad-gerber \\") + p("\t\t--output-dir $(GERBER_DIR) \\") + p("\t\t--pcb $(PROJECT).kicad_pcb \\") + p("\t\t$(GERBER_ARGS)") + p("\tmkdir -p $(dir $@)") + p("\t(cd $(GERBER_DIR); zip tmp.zip $(foreach GBR,$(GERBERS),$(notdir $(GBR))))") + p("\tmv $(GERBER_DIR)/tmp.zip $@") + p("EE_OUTPUTS += $(GERBER_ZIP)") + p("") + + p("") + p("# Utility targets") + p(".PHONY: schematic pcb gerber") + p("schematic: $(SCH_FILES)") + p("pcb: {}".format(args.pcb)) + + +parser = argparse.ArgumentParser(description="Create a Makefile with all dependencies") + +parser.add_argument("--sch", + required=True, + metavar="SCH", + help="Schematic file") + +parser.add_argument("--pcb", + required=True, + metavar="PCB", + help="PCB file") + +parser.add_argument("--out", + metavar="OUT", + help="Output file") + +parser.add_argument("--gerber", + action="store_true", + help="Enable gerber.zip target") + +args = parser.parse_args() + +if args.out: + with open(args.out, "w") as f: + out_file = f + work(args) +else: + work(args) diff --git a/test/test_bom.py b/test/test_bom.py index fd8c6c6..50cae9f 100644 --- a/test/test_bom.py +++ b/test/test_bom.py @@ -37,11 +37,10 @@ def test_read_bom_2(): assert len(r5.fields) == 4 assert r5.fields["Part Number"] == "CRCW0402100RFKED" assert r5["Part Number"] == "CRCW0402100RFKED" - assert set( - ['ref', 'value', 'Capacitance', 'Color', 'Description', 'Frequency', 'Impedance', 'Inductance', 'Manufacturer', - 'Part Number', 'Resistance']) == bom.all_field_names() + assert {'ref', 'value', 'Capacitance', 'Color', 'Description', 'Frequency', 'Impedance', 'Inductance', + 'Manufacturer', 'Part Number', 'Resistance'} == bom.all_field_names() - assert not "foo" in r5 + assert "foo" not in r5 with pytest.raises(KeyError): r5["foo"] @@ -50,10 +49,7 @@ def test_read_bom_2_pandas(): bom = read_bom(basedir + '/../demo/kicad/bom/gw.xml').to_pandas() assert len(bom) == 165 - print("bom") - print(str(bom)) r5 = bom.loc["R5"] - print(str(r5.index)) # assert r5.index == "R5" assert r5["ref"] == "R5" assert r5["value"] == "R0402_100R" @@ -65,6 +61,6 @@ def test_read_bom_2_pandas(): assert r5["Part Number"] == "CRCW0402100RFKED" # assert set(['ref', 'value', 'Capacitance', 'Color', 'Description', 'Frequency', 'Impedance', 'Inductance', 'Manufacturer', 'Part Number', 'Resistance']) == bom.all_field_names() - assert not "foo" in r5 + assert "foo" not in r5 with pytest.raises(KeyError): r5["foo"] diff --git a/test/test_digikey.py b/test/test_digikey.py index 2b3cd12..97cd943 100644 --- a/test/test_digikey.py +++ b/test/test_digikey.py @@ -4,7 +4,6 @@ from pathlib import Path import pytest from selenium import webdriver -from selenium.webdriver.common.keys import Keys import ee.digikey as dk @@ -110,6 +109,7 @@ def cache_file(url, keyword): return content + try: driver.close() except Exception: |