aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTrygve Laugstøl <trygvis@inamo.no>2019-02-09 23:37:02 +0100
committerTrygve Laugstøl <trygvis@inamo.no>2019-02-09 23:37:02 +0100
commitc313e6de8c06017739402ea89f55ce3b36ac0f2b (patch)
tree3fa4ae2b36e5b3820c8677c82cafd676f77aaced
parent79b8525e776b27a1702a4eea6f3168bfd97a393a (diff)
downloadee-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__.py14
-rw-r--r--src/ee/__main__.py2
-rw-r--r--src/ee/_utils.py5
-rwxr-xr-xsrc/ee/kicad/export_gerber.py2
-rw-r--r--src/ee/kicad/model.py6
-rw-r--r--src/ee/kicad/read_schematic.py38
-rwxr-xr-xsrc/ee/tools/kicad_gerber.py81
-rw-r--r--src/ee/tools/kicad_make_bom.py8
-rw-r--r--src/ee/tools/kicad_mkdeps.py78
-rw-r--r--test/test_bom.py12
-rw-r--r--test/test_digikey.py2
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: