diff options
-rw-r--r-- | src/ee/__init__.py | 261 | ||||
-rw-r--r-- | src/ee/__main__.py | 69 | ||||
-rw-r--r-- | src/ee/formatting/__init__.py | 132 | ||||
-rw-r--r-- | src/ee/kicad/bom/__init__.py | 221 | ||||
-rw-r--r-- | src/ee/kicad/bom/io.py | 81 | ||||
-rw-r--r-- | src/ee/kicad/bom_tool/__init__.py | 185 | ||||
-rw-r--r-- | src/ee/kicad/bom_tool/predef.py | 5 | ||||
-rwxr-xr-x | src/ee/tools/kicad_gerber.py | 9 | ||||
-rw-r--r-- | src/ee/tools/read_ltspice_raw.py | 16 | ||||
-rw-r--r-- | test/test_bom.py | 82 | ||||
-rw-r--r-- | test/test_digikey.py | 26 | ||||
-rw-r--r-- | test/test_formatting.py | 24 |
12 files changed, 578 insertions, 533 deletions
diff --git a/src/ee/__init__.py b/src/ee/__init__.py index a22ecc6..e5519f8 100644 --- a/src/ee/__init__.py +++ b/src/ee/__init__.py @@ -1,145 +1,148 @@ import numpy as np __all__ = [ - 'EeError', - 'read_ltspice_raw' + 'EeError', + 'read_ltspice_raw' ] + class EeError(Exception): pass + class LtSpiceRaw(object): - def __init__(self, variables, values_first, values_rest): - self.variables = variables - self.values_first = values_first - self.values_rest = values_rest - - def get_values(self, variable): - return self.values[variable.idx] - - def get_variable(self, idx=None, expression=None): - if idx is not None: - return self.variables[idx] - 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)) - return v[0] - raise Exception('idx or expression must be given') - - def to_pandas(self): - import pandas - data = [] - for (i, v) in enumerate(self.values_first): - data.append(pandas.Series(v, dtype='float64')) - - data = {} - for (i, v) in enumerate(self.values_first): - data[self.variables[i].expression] = v - - return pandas.DataFrame(data = data) + def __init__(self, variables, values_first, values_rest): + self.variables = variables + self.values_first = values_first + self.values_rest = values_rest + + def get_values(self, variable): + return self.values[variable.idx] + + def get_variable(self, idx=None, expression=None): + if idx is not None: + return self.variables[idx] + 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)) + return v[0] + raise Exception('idx or expression must be given') + + def to_pandas(self): + import pandas + data = [] + for (i, v) in enumerate(self.values_first): + data.append(pandas.Series(v, dtype='float64')) + + data = {} + for (i, v) in enumerate(self.values_first): + data[self.variables[i].expression] = v + + return pandas.DataFrame(data=data) + class LtSpiceVariable(object): - def __init__(self, idx, expression, kind): - self.idx = idx - self.expression = expression - self.kind = kind + def __init__(self, idx, expression, kind): + self.idx = idx + self.expression = expression + self.kind = kind + class LtSpiceReader(object): + def __init__(self, f): + self.f = f + self.mode = 'header' + self.headers = {} + self.variables = [] + self.values = [] + + def read_header(self, line): + sep = line.find(':') + key = line[0:sep] + value = line[sep + 1:].strip() + + # print("key='{}', value='{}'".format(key, str(value))) + + if key == 'Binary': + self.set_binary_mode() + elif key == 'Variables': + self.no_variables = int(self.headers['No. Variables']) + self.mode = 'variables' + else: + self.headers[key] = value + + def read_variable(self, line): + parts = line.split('\t') + idx = int(parts[1]) + expression = parts[2] + kind = parts[3] + self.variables.append(LtSpiceVariable(idx, expression, kind)) + + if len(self.variables) == self.no_variables: + self.mode = 'header' + + def set_binary_mode(self): + self.mode = 'binary' + + def read_binary(self): + pos = self.f.tell() + no_points = int(self.headers['No. Points']) + no_variables = int(self.headers['No. Variables']) + + # print("no_points={}, no_variables={}".format(no_points, no_variables)) + + if True: + self.values = np.zeros((no_variables, no_points), dtype='float32') + + for p in range(no_points): + self.values[0][p] = np.fromfile(self.f, count=1, dtype='float64') + row = np.fromfile(self.f, count=no_variables - 1, dtype='float32'); + for v in range(0, len(row)): + self.values[v + 1][p] = row[v] + else: + self.values = np.zeros((no_points, no_variables), dtype='float64') + + for p in range(no_points): + self.values[p][0] = np.fromfile(self.f, count=1, dtype='float64') + row = np.fromfile(self.f, count=no_variables - 1, dtype='float32'); + for v in range(0, len(row)): + self.values[p][v + 1] = row[v] + + def read(self): + while True: + line = self.f.readline(); + + if len(line) == 0: + break + + self.f.read(1) # The data is utf16 encoded so read the extra null byte + + # if this wasn't the last line .readline() includes the newline + if line[-1] == '\n' or line[-1] == 0x0a: + line = line[:-1] + + # print("len(line)={}, line={}".format(len(line), str(line))) + line = line.decode(encoding="utf-16") + + if self.mode == 'header': + self.read_header(line) + elif self.mode == 'variables': + self.read_variable(line) + + # The binary data can't be read with readline() + if self.mode == 'binary': + # self.f.read() + self.read_binary() + break + + if len(self.variables) == 0: + raise Exception("Something didn't quite work when parsing file, no variables found") + + return LtSpiceRaw(self.variables, self.values, []) - def __init__(self, f): - self.f = f - self.mode = 'header' - self.headers = {} - self.variables = [] - self.values = [] - - def read_header(self, line): - sep = line.find(':') - key = line[0:sep] - value = line[sep + 1:].strip() - -# print("key='{}', value='{}'".format(key, str(value))) - - if key == 'Binary': - self.set_binary_mode() - elif key == 'Variables': - self.no_variables = int(self.headers['No. Variables']) - self.mode = 'variables' - else: - self.headers[key] = value - - def read_variable(self, line): - parts = line.split('\t') - idx = int(parts[1]) - expression = parts[2] - kind = parts[3] - self.variables.append(LtSpiceVariable(idx, expression, kind)) - - if len(self.variables) == self.no_variables: - self.mode = 'header' - - def set_binary_mode(self): - self.mode = 'binary' - - def read_binary(self): - pos = self.f.tell() - no_points = int(self.headers['No. Points']) - no_variables = int(self.headers['No. Variables']) - -# print("no_points={}, no_variables={}".format(no_points, no_variables)) - - if True: - self.values = np.zeros((no_variables, no_points), dtype='float32') - - for p in range(no_points): - self.values[0][p] = np.fromfile(self.f, count=1, dtype='float64') - row = np.fromfile(self.f, count=no_variables - 1, dtype='float32'); - for v in range(0, len(row)): - self.values[v + 1][p] = row[v] - else: - self.values = np.zeros((no_points, no_variables), dtype='float64') - - for p in range(no_points): - self.values[p][0] = np.fromfile(self.f, count=1, dtype='float64') - row = np.fromfile(self.f, count=no_variables - 1, dtype='float32'); - for v in range(0, len(row)): - self.values[p][v + 1] = row[v] - - def read(self): - while True: - line = self.f.readline(); - - if len(line) == 0: - break - - self.f.read(1) # The data is utf16 encoded so read the extra null byte - - # if this wasn't the last line .readline() includes the newline - if line[-1] == '\n' or line[-1] == 0x0a: - line = line[:-1] - -# print("len(line)={}, line={}".format(len(line), str(line))) - line = line.decode(encoding="utf-16") - - if self.mode == 'header': - self.read_header(line) - elif self.mode == 'variables': - self.read_variable(line) - - # The binary data can't be read with readline() - if self.mode == 'binary': -# self.f.read() - self.read_binary() - break - - if len(self.variables) == 0: - raise Exception("Something didn't quite work when parsing file, no variables found") - - return LtSpiceRaw(self.variables, self.values, []) def read_ltspice_raw(filename): - - with open(filename, mode="rb") as f: - r = LtSpiceReader(f) - return r.read() + with open(filename, mode="rb") as f: + r = LtSpiceReader(f) + return r.read() diff --git a/src/ee/__main__.py b/src/ee/__main__.py index 6bb40b0..ecc0a98 100644 --- a/src/ee/__main__.py +++ b/src/ee/__main__.py @@ -5,51 +5,56 @@ import logging import pkgutil import sys + def eprint(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) + print(*args, file=sys.stderr, **kwargs) + @total_ordering class Tool(object): - def __init__(self, module_name, name): - self.module_name = module_name - self.name = name + def __init__(self, module_name, name): + self.module_name = module_name + self.name = name + + def __eq__(self, other): + return self.name == other.name - def __eq__(self, other): - return self.name == other.name + def __lt__(self, other): + return self.name < other.name - def __lt__(self, other): - return self.name < other.name def find_tools(): - prefix = ee.tools.__name__ + '.' - ps = pkgutil.walk_packages(ee.tools.__path__, prefix) - tools = [] - for (module_loader, module_name, ispkg) in ps: - name = module_name.replace(prefix, '').replace('_', '-') - tools.append(Tool(module_name, name)) - return sorted(tools) + prefix = ee.tools.__name__ + '.' + ps = pkgutil.walk_packages(ee.tools.__path__, prefix) + tools = [] + for (module_loader, module_name, ispkg) in ps: + name = module_name.replace(prefix, '').replace('_', '-') + tools.append(Tool(module_name, name)) + return sorted(tools) + def main(): - logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests - logging.getLogger().setLevel(logging.DEBUG) - requests_log = logging.getLogger("requests.packages.urllib3") - requests_log.setLevel(logging.DEBUG) - requests_log.propagate = True + logging.basicConfig() # you need to initialize logging, otherwise you will not see anything from requests + logging.getLogger().setLevel(logging.DEBUG) + requests_log = logging.getLogger("requests.packages.urllib3") + requests_log.setLevel(logging.DEBUG) + requests_log.propagate = True + + tools = find_tools() - tools = find_tools() + name = sys.argv[1] + del sys.argv[1] - name = sys.argv[1] - del sys.argv[1] + for t in tools: + if t.name != name: + continue + sys.argv[0] = t.name + importlib.import_module(t.module_name) + exit(0) - for t in tools: - if t.name != name: - continue - sys.argv[0] = t.name - importlib.import_module(t.module_name) - exit(0) + eprint("No such tool: {}".format(name)) + exit(1) - eprint("No such tool: {}".format(name)) - exit(1) if __name__ == "__main__": - main() + main() diff --git a/src/ee/formatting/__init__.py b/src/ee/formatting/__init__.py index 42bf48d..32eaf64 100644 --- a/src/ee/formatting/__init__.py +++ b/src/ee/formatting/__init__.py @@ -6,42 +6,45 @@ __all__ = [ 'eng_str', ] + class ESeries(object): - def __init__(self, series): - self.series = series + def __init__(self, series): + self.series = series - def closest(self, value): - e = math.floor(math.log10(value)) - value = float(value/(10**e)) + def closest(self, value): + e = math.floor(math.log10(value)) + value = float(value / (10 ** e)) + + return min(self.series, key=lambda v: abs(v - value)) * 10 ** e - return min(self.series, key=lambda v: abs(v - value)) * 10**e # https://en.wikipedia.org/wiki/E-series_of_preferred_numbers -_e_series_24 = [1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0, 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1] +_e_series_24 = [1.0, 1.1, 1.2, 1.3, 1.5, 1.6, 1.8, 2.0, 2.2, 2.4, 2.7, 3.0, + 3.3, 3.6, 3.9, 4.3, 4.7, 5.1, 5.6, 6.2, 6.8, 7.5, 8.2, 9.1] _e_series_12 = [v for i, v in enumerate(_e_series_24) if i % 2 == 0] _e_series_6 = [v for i, v in enumerate(_e_series_24) if i % 4 == 0] _e_series_192 = [ - 1.00, 1.01, 1.02, 1.04, 1.05, 1.06, 1.07, 1.09, 1.10, 1.11, - 1.13, 1.14, 1.15, 1.17, 1.18, 1.20, 1.21, 1.23, 1.24, 1.26, - 1.27, 1.29, 1.30, 1.32, 1.33, 1.35, 1.37, 1.38, 1.40, 1.42, - 1.43, 1.45, 1.47, 1.49, 1.50, 1.52, 1.54, 1.56, 1.58, 1.60, - 1.62, 1.64, 1.65, 1.67, 1.69, 1.72, 1.74, 1.76, 1.78, 1.80, - 1.82, 1.84, 1.87, 1.89, 1.91, 1.93, 1.96, 1.98, 2.00, 2.03, - 2.05, 2.08, 2.10, 2.13, 2.15, 2.18, 2.21, 2.23, 2.26, 2.29, - 2.32, 2.34, 2.37, 2.40, 2.43, 2.46, 2.49, 2.52, 2.55, 2.58, - 2.61, 2.64, 2.67, 2.71, 2.74, 2.77, 2.80, 2.84, 2.87, 2.91, - 2.94, 2.98, 3.01, 3.05, 3.09, 3.12, 3.16, 3.20, 3.24, 3.28, - 3.32, 3.36, 3.40, 3.44, 3.48, 3.52, 3.57, 3.61, 3.65, 3.70, - 3.74, 3.79, 3.83, 3.88, 3.92, 3.97, 4.02, 4.07, 4.12, 4.17, - 4.22, 4.27, 4.32, 4.37, 4.42, 4.48, 4.53, 4.59, 4.64, 4.70, - 4.75, 4.81, 4.87, 4.93, 4.99, 5.05, 5.11, 5.17, 5.23, 5.30, - 5.36, 5.42, 5.49, 5.56, 5.62, 5.69, 5.76, 5.83, 5.90, 5.97, - 6.04, 6.12, 6.19, 6.26, 6.34, 6.42, 6.49, 6.57, 6.65, 6.73, - 6.81, 6.90, 6.98, 7.06, 7.15, 7.23, 7.32, 7.41, 7.50, 7.59, - 7.68, 7.77, 7.87, 7.96, 8.06, 8.16, 8.25, 8.35, 8.45, 8.56, - 8.66, 8.76, 8.87, 8.98, 9.09, 9.20, 9.31, 9.42, 9.53, 9.65, - 9.76, 9.88, + 1.00, 1.01, 1.02, 1.04, 1.05, 1.06, 1.07, 1.09, 1.10, 1.11, + 1.13, 1.14, 1.15, 1.17, 1.18, 1.20, 1.21, 1.23, 1.24, 1.26, + 1.27, 1.29, 1.30, 1.32, 1.33, 1.35, 1.37, 1.38, 1.40, 1.42, + 1.43, 1.45, 1.47, 1.49, 1.50, 1.52, 1.54, 1.56, 1.58, 1.60, + 1.62, 1.64, 1.65, 1.67, 1.69, 1.72, 1.74, 1.76, 1.78, 1.80, + 1.82, 1.84, 1.87, 1.89, 1.91, 1.93, 1.96, 1.98, 2.00, 2.03, + 2.05, 2.08, 2.10, 2.13, 2.15, 2.18, 2.21, 2.23, 2.26, 2.29, + 2.32, 2.34, 2.37, 2.40, 2.43, 2.46, 2.49, 2.52, 2.55, 2.58, + 2.61, 2.64, 2.67, 2.71, 2.74, 2.77, 2.80, 2.84, 2.87, 2.91, + 2.94, 2.98, 3.01, 3.05, 3.09, 3.12, 3.16, 3.20, 3.24, 3.28, + 3.32, 3.36, 3.40, 3.44, 3.48, 3.52, 3.57, 3.61, 3.65, 3.70, + 3.74, 3.79, 3.83, 3.88, 3.92, 3.97, 4.02, 4.07, 4.12, 4.17, + 4.22, 4.27, 4.32, 4.37, 4.42, 4.48, 4.53, 4.59, 4.64, 4.70, + 4.75, 4.81, 4.87, 4.93, 4.99, 5.05, 5.11, 5.17, 5.23, 5.30, + 5.36, 5.42, 5.49, 5.56, 5.62, 5.69, 5.76, 5.83, 5.90, 5.97, + 6.04, 6.12, 6.19, 6.26, 6.34, 6.42, 6.49, 6.57, 6.65, 6.73, + 6.81, 6.90, 6.98, 7.06, 7.15, 7.23, 7.32, 7.41, 7.50, 7.59, + 7.68, 7.77, 7.87, 7.96, 8.06, 8.16, 8.25, 8.35, 8.45, 8.56, + 8.66, 8.76, 8.87, 8.98, 9.09, 9.20, 9.31, 9.42, 9.53, 9.65, + 9.76, 9.88, ] _e_series_96 = [v for i, v in enumerate(_e_series_192) if i % 2 == 0] _e_series_48 = [v for i, v in enumerate(_e_series_192) if i % 4 == 0] @@ -53,46 +56,49 @@ e48 = ESeries(_e_series_48) e96 = ESeries(_e_series_96) e192 = ESeries(_e_series_192) -def e_series_find_closest(value): - e = math.floor(math.log10(value)) - value = float(value/(10**e)) - - return min(series, key=lambda v: abs(v - value)) * 10**e - -import numpy -def eng_str(value, unit = None): - if value == 0: - s = '0' + (' ' + unit if unit is not None else '') - else: - big = ['k', 'M', 'G', 'T'] - small = ['m', 'u', 'p'] +def e_series_find_closest(value): + e = math.floor(math.log10(value)) + value = float(value / (10 ** e)) - if unit is not None: - big = [' ' + unit] + [' ' + x + unit for x in big] - small = [' ' + unit] + [' ' + x + unit for x in small] - else: - big = [''] + [' ' + x for x in big] - small = [''] + [' ' + x for x in small] + return min(series, key=lambda v: abs(v - value)) * 10 ** e - e_floor = math.floor(math.log10(value)) - div3 = math.floor(e_floor/3) - if div3 > 0: - suffixes = big - idx = int(div3) - else: - suffixes = small - idx = int(-div3) +import numpy - suffix = suffixes[idx] - scale = 10**(div3*3) - scaled = value/scale - if (scaled - math.floor(scaled)) < 0.0001: -# if scaled == math.floor(scaled): - s = "{:1.0f}{}".format(scaled, suffix) +def eng_str(value, unit=None): + if value == 0: + s = '0' + (' ' + unit if unit is not None else '') else: - s = "{:1.1f}{}".format(scaled, suffix) - - return s + big = ['k', 'M', 'G', 'T'] + small = ['m', 'u', 'p'] + + if unit is not None: + big = [' ' + unit] + [' ' + x + unit for x in big] + small = [' ' + unit] + [' ' + x + unit for x in small] + else: + big = [''] + [' ' + x for x in big] + small = [''] + [' ' + x for x in small] + + e_floor = math.floor(math.log10(value)) + + div3 = math.floor(e_floor / 3) + if div3 > 0: + suffixes = big + idx = int(div3) + else: + suffixes = small + idx = int(-div3) + + suffix = suffixes[idx] + scale = 10 ** (div3 * 3) + scaled = value / scale + + if (scaled - math.floor(scaled)) < 0.0001: + # if scaled == math.floor(scaled): + s = "{:1.0f}{}".format(scaled, suffix) + else: + s = "{:1.1f}{}".format(scaled, suffix) + + return s diff --git a/src/ee/kicad/bom/__init__.py b/src/ee/kicad/bom/__init__.py index d0798a4..2a32521 100644 --- a/src/ee/kicad/bom/__init__.py +++ b/src/ee/kicad/bom/__init__.py @@ -2,124 +2,131 @@ import re import sys __all__ = [ - 'Part', - 'Library', - 'Bom', - 'Comp', - 'split_ref', + 'Part', + 'Library', + 'Bom', + 'Comp', + 'split_ref', ] + def split_ref(ref): - """Split "C12" into a tuple that's useful for sorting by component reference. - - For example: "C12" => ("C", 12, None). "Cfoo" => ("C", sys.maxsize, ""). - """ - m = split_ref.r.match(ref) - if not m: - return (ref, sys.maxsize, "") - groups = m.groups() - ref = groups[0] - val = groups[1] - rest = groups[2] - try: - return (ref, int(val), rest) - except ValueError: - pass - - return (ref, val, rest) -split_ref.r = re.compile("([A-Za-z]+)([0-9]+)(.*)") + """Split "C12" into a tuple that's useful for sorting by component reference. + + For example: "C12" => ("C", 12, None). "Cfoo" => ("C", sys.maxsize, ""). + """ + m = split_ref.r.match(ref) + if not m: + return (ref, sys.maxsize, "") + groups = m.groups() + ref = groups[0] + val = groups[1] + rest = groups[2] + try: + return (ref, int(val), rest) + except ValueError: + pass -class Part: - def __init__(self, name): - self.name = name + return (ref, val, rest) -class Library: - def __init__(self, name): - self.name = name - self.parts = {} - def add_part(self, part): - try: - return self.parts[part] - except KeyError: - p = Part(part) - self.parts[part] = p - return p +split_ref.r = re.compile("([A-Za-z]+)([0-9]+)(.*)") -class Bom(object): - def __init__(self): - self.libraries = {} - self._components = {} - def add_component(self, component): - self._components[component.ref] = component +class Part: + def __init__(self, name): + self.name = name - def get_component(self, name): - return self._components[name] - def get_components(self): - return self._components +class Library: + def __init__(self, name): + self.name = name + self.parts = {} - def all_field_names(self): - fields = set(['ref', 'value']) - for c in self._components.values(): - for f in c.fields: - fields.add(f) - return fields + def add_part(self, part): + try: + return self.parts[part] + except KeyError: + p = Part(part) + self.parts[part] = p + return p + + +class Bom(object): + def __init__(self): + self.libraries = {} + self._components = {} + + def add_component(self, component): + self._components[component.ref] = component + + def get_component(self, name): + return self._components[name] + + def get_components(self): + return self._components + + def all_field_names(self): + fields = set(['ref', 'value']) + for c in self._components.values(): + for f in c.fields: + fields.add(f) + return fields + + def find_library(self, name): + try: + return self.libraries[name] + except KeyError: + lib = Library(name) + self.libraries[name] = lib + return lib + + def to_pandas(self, ref_field_name=None, value_field_name=None): + import pandas + + ref_field_name = ref_field_name or "ref" + value_field_name = value_field_name or "value" + + fields = self.all_field_names() + data = {k: [] for k in fields} + refs = [] + values = [] + for ref, c in self.get_components().items(): + refs.append(c.ref) + values.append(c.value) + for field in fields: + data[field].append(c[field] if field in c else None) + + # del data[ref_field_name] + data[ref_field_name] = refs + data[value_field_name] = values + return pandas.DataFrame(data=data, index=refs) - def find_library(self, name): - try: - return self.libraries[name] - except KeyError: - lib = Library(name) - self.libraries[name] = lib - return lib - - def to_pandas(self, ref_field_name = None, value_field_name = None): - import pandas - - ref_field_name = ref_field_name or "ref" - value_field_name = value_field_name or "value" - - fields = self.all_field_names() - data = {k: [] for k in fields} - refs = [] - values = [] - for ref, c in self.get_components().items(): - refs.append(c.ref) - values.append(c.value) - for field in fields: - data[field].append(c[field] if field in c else None) - -# del data[ref_field_name] - data[ref_field_name] = refs - data[value_field_name] = values - return pandas.DataFrame(data=data, index=refs) class Comp: - def __init__(self, ref, value, library, part, footprint): - self.ref = ref - self.value = value - self.footprint = footprint - self.library = library - self.part = part - self.fields = {} - - def add_field(self, key, value): - self.fields[key] = value - - def __contains__(self, key): - if key == 'ref': - return self.ref is not None - elif key == 'value': - return self.value is not None - else: - return key in self.fields - - def __getitem__(self, key): - if key == 'ref': - return self.ref - elif key == 'value': - return self.value - else: - return self.fields[key] + def __init__(self, ref, value, library, part, footprint): + self.ref = ref + self.value = value + self.footprint = footprint + self.library = library + self.part = part + self.fields = {} + + def add_field(self, key, value): + self.fields[key] = value + + def __contains__(self, key): + if key == 'ref': + return self.ref is not None + elif key == 'value': + return self.value is not None + else: + return key in self.fields + + def __getitem__(self, key): + if key == 'ref': + return self.ref + elif key == 'value': + return self.value + else: + return self.fields[key] diff --git a/src/ee/kicad/bom/io.py b/src/ee/kicad/bom/io.py index c7eef44..d7c0367 100644 --- a/src/ee/kicad/bom/io.py +++ b/src/ee/kicad/bom/io.py @@ -1,44 +1,45 @@ import xml.etree.ElementTree as ElementTree from ee.kicad.bom import * + def read_bom(path): - def child_text(e, child_tag): - child = e.find(child_tag) - - return child.text if child is not None else None - - def add_comp(b, comp): - ref = comp.get("ref") - value = child_text(comp, "value") - footprint = child_text(comp, "footprint") - libsource = comp.find("libsource") - l, p = (None, None) - if libsource is not None: - lib = libsource.get("lib") - part = libsource.get("part") - if lib is not None and part is not None: - l = b.find_library(lib) - p = l.add_part(part) - - c = Comp(ref, value, l, p, footprint) - - fields = comp.find("fields") - if fields is not None: - for f in fields.findall("field"): - key = f.get("name") - value = f.text - c.add_field(key, value) - - b.add_component(c) - return c - - with open(path) as f: - tree = ElementTree.parse(path) - root = tree.getroot() - - b = Bom() - comp = root.find("components").findall("comp") - for c in comp: - add_comp(b, c) - - return b + def child_text(e, child_tag): + child = e.find(child_tag) + + return child.text if child is not None else None + + def add_comp(b, comp): + ref = comp.get("ref") + value = child_text(comp, "value") + footprint = child_text(comp, "footprint") + libsource = comp.find("libsource") + l, p = (None, None) + if libsource is not None: + lib = libsource.get("lib") + part = libsource.get("part") + if lib is not None and part is not None: + l = b.find_library(lib) + p = l.add_part(part) + + c = Comp(ref, value, l, p, footprint) + + fields = comp.find("fields") + if fields is not None: + for f in fields.findall("field"): + key = f.get("name") + value = f.text + c.add_field(key, value) + + b.add_component(c) + return c + + with open(path) as f: + tree = ElementTree.parse(path) + root = tree.getroot() + + b = Bom() + comp = root.find("components").findall("comp") + for c in comp: + add_comp(b, c) + + return b diff --git a/src/ee/kicad/bom_tool/__init__.py b/src/ee/kicad/bom_tool/__init__.py index 9f450c9..f5d2e4f 100644 --- a/src/ee/kicad/bom_tool/__init__.py +++ b/src/ee/kicad/bom_tool/__init__.py @@ -3,108 +3,115 @@ import functools import itertools import pandas as pd + def _none_if_empty(l): - return l if l is not None and len(l) > 0 else None + return l if l is not None and len(l) > 0 else None + class Supplier(): - def __init__(self, name, bom_field = None): - self._name = name - self._bom_field = bom_field if not None else name + def __init__(self, name, bom_field=None): + self._name = name + self._bom_field = bom_field if not None else name + + @property + def bom_field(self): + return self._bom_field - @property - def bom_field(self): - return self._bom_field class Settings(): - def __init__(self, suppliers = None, part_field = None): - self._suppliers = suppliers if suppliers is not None else [] - self._part_field = part_field if part_field is not None else 'part' + def __init__(self, suppliers=None, part_field=None): + self._suppliers = suppliers if suppliers is not None else [] + self._part_field = part_field if part_field is not None else 'part' - @property - def suppliers(self): - return self._suppliers + @property + def suppliers(self): + return self._suppliers - @property - def part_field(self): - return self._part_field + @property + def part_field(self): + return self._part_field + + @part_field.setter + def part_field(self, value): + self._part_field = value - @part_field.setter - def part_field(self, value): - self._part_field = value class CsvFormat(): - def __init__(self, supplier = None, group_by_fields = None, required_fields = None): - self._supplier = supplier - self._group_by_fields = _none_if_empty(group_by_fields) - self._required_fields = _none_if_empty(required_fields) + def __init__(self, supplier=None, group_by_fields=None, required_fields=None): + self._supplier = supplier + self._group_by_fields = _none_if_empty(group_by_fields) + self._required_fields = _none_if_empty(required_fields) + + @property + def supplier(self): + return self._supplier - @property - def supplier(self): - return self._supplier + @property + def group_by_fields(self): + return self._group_by_fields - @property - def group_by_fields(self): - return self._group_by_fields + @property + def required_fields(self): + return self._required_fields - @property - def required_fields(self): - return self._required_fields def _all(fs, c): - for f in fs: - if not f(c): - return False - return True - -def to_panda(bom, bom_settings, csv_format): # type: (Bom, BomSettings, CsvFormat) -> None - filters = [] - - print("csv_format.supplier.bom_field={}".format(csv_format.supplier.bom_field)) - if csv_format.supplier is not None: - filters.append(lambda c: csv_format.supplier.bom_field in c) - - if csv_format.group_by_fields is not None: - filters.append(lambda c: all([field in c for field in csv_format.group_by_fields])) - - if csv_format.required_fields is not None: - filters.append(lambda c: all([field in c for field in csv_format.required_fields])) - - f = functools.partial(_all, filters) - print("len(filters)={}".format(len(filters))) - print("len(bom.get_components())={}".format(len(bom.get_components()))) - - filtered = [c for ref, c in bom.get_components().items() if f(c)] - print("filtered:, len={}".format(len(filtered))) - filtered.sort(key=lambda c: c.ref) - print("sorted filtered:, len={}".format(len(filtered))) - - counts=None - parts=[] - - frame = {} - for n in bom.all_field_names(): - frame[n] = [] - - if csv_format.group_by_fields is not None: - counts, parts, refs=([],[],[]) - for group, comps in itertools.groupby(filtered, key=lambda c: [field for field in c for c in csv_format.group_by_fields]): - gs = list(group) - cs = list(comps) -# counts.append(len(cs)) -# dpns.append(part) -# mpns.append(part) -# refs.append(functools.reduce(lambda a, b: a + ' ' + b, [c.ref for c in cs], '')) - print("group={}".format(gs)) - -# return pd.DataFrame(data={ -# 'count': counts, -# 'mpn': mpns, -# 'dpn': dpns, -# 'refs': refs, -# }) - else: - for ref, c in filtered: - for key, value in c: - frame[key] = value - - return pd.DataFrame(data=frame) + for f in fs: + if not f(c): + return False + return True + + +def to_panda(bom, bom_settings, csv_format): # type: (Bom, BomSettings, CsvFormat) -> None + filters = [] + + print("csv_format.supplier.bom_field={}".format(csv_format.supplier.bom_field)) + if csv_format.supplier is not None: + filters.append(lambda c: csv_format.supplier.bom_field in c) + + if csv_format.group_by_fields is not None: + filters.append(lambda c: all([field in c for field in csv_format.group_by_fields])) + + if csv_format.required_fields is not None: + filters.append(lambda c: all([field in c for field in csv_format.required_fields])) + + f = functools.partial(_all, filters) + print("len(filters)={}".format(len(filters))) + print("len(bom.get_components())={}".format(len(bom.get_components()))) + + filtered = [c for ref, c in bom.get_components().items() if f(c)] + print("filtered:, len={}".format(len(filtered))) + filtered.sort(key=lambda c: c.ref) + print("sorted filtered:, len={}".format(len(filtered))) + + counts = None + parts = [] + + frame = {} + for n in bom.all_field_names(): + frame[n] = [] + + if csv_format.group_by_fields is not None: + counts, parts, refs = ([], [], []) + for group, comps in itertools.groupby(filtered, + key=lambda c: [field for field in c for c in csv_format.group_by_fields]): + gs = list(group) + cs = list(comps) + # counts.append(len(cs)) + # dpns.append(part) + # mpns.append(part) + # refs.append(functools.reduce(lambda a, b: a + ' ' + b, [c.ref for c in cs], '')) + print("group={}".format(gs)) + + # return pd.DataFrame(data={ + # 'count': counts, + # 'mpn': mpns, + # 'dpn': dpns, + # 'refs': refs, + # }) + else: + for ref, c in filtered: + for key, value in c: + frame[key] = value + + return pd.DataFrame(data=frame) diff --git a/src/ee/kicad/bom_tool/predef.py b/src/ee/kicad/bom_tool/predef.py index 717f6d3..bfe8631 100644 --- a/src/ee/kicad/bom_tool/predef.py +++ b/src/ee/kicad/bom_tool/predef.py @@ -1,6 +1,7 @@ from ee.kicad.bom_tool import * -digikey = Supplier('Digi-Key', bom_field = 'digikey') +digikey = Supplier('Digi-Key', bom_field='digikey') + def digikeyCsvFormat(digikey_supplier): - return CsvFormat(supplier = digikey, group_by_fields = [digikey_supplier.bom_field]) + return CsvFormat(supplier=digikey, group_by_fields=[digikey_supplier.bom_field]) diff --git a/src/ee/tools/kicad_gerber.py b/src/ee/tools/kicad_gerber.py index e8604b8..be0e34d 100755 --- a/src/ee/tools/kicad_gerber.py +++ b/src/ee/tools/kicad_gerber.py @@ -164,11 +164,14 @@ for plan in plot_plan: if args.detect_files_only: for plan in plot_plan: - print plan.filename + print + plan.filename if args.protel_extensions: - print drlFileOut - print drlNpthFileOut + print + drlFileOut + print + drlNpthFileOut sys.exit(0) # Set some important plot options: diff --git a/src/ee/tools/read_ltspice_raw.py b/src/ee/tools/read_ltspice_raw.py index 279fe73..f04abb0 100644 --- a/src/ee/tools/read_ltspice_raw.py +++ b/src/ee/tools/read_ltspice_raw.py @@ -3,24 +3,26 @@ import sys raw = ee.read_ltspice_raw(sys.argv[1]) -#print("Variables:") -#for i, v in enumerate(raw.variables): +# print("Variables:") +# for i, v in enumerate(raw.variables): # print("{:2}: kind: {:20} expression: {}".format(i, v.kind, v.expression)) -#for i, v in enumerate(raw.variables): +# for i, v in enumerate(raw.variables): # print("{:2}: kind: {:20} expression: {}".format(i, v.kind, v.expression)) # for p in raw.values[i]: # print(" {}".format(p)) -x = raw.get_variable(idx = 0) -y = raw.get_variable(expression = 'V(load)') +x = raw.get_variable(idx=0) +y = raw.get_variable(expression='V(load)') xs = raw.get_values(y) import matplotlib -matplotlib.use('Agg') + +matplotlib.use('Agg') import matplotlib.pyplot as plt from matplotlib.ticker import FuncFormatter, MaxNLocator + fig = plt.figure() ax = fig.add_subplot(111) ax.plot(xs) @@ -28,4 +30,4 @@ ax.set_xlabel(x.expression) ax.set_ylabel(y.expression) with open("ltspice.png", "wb") as f: - plt.savefig(f, format="png") + plt.savefig(f, format="png") diff --git a/test/test_bom.py b/test/test_bom.py index 6da7798..fd8c6c6 100644 --- a/test/test_bom.py +++ b/test/test_bom.py @@ -7,58 +7,64 @@ from ee.kicad.bom.io import read_bom basedir = os.path.dirname(os.path.abspath(__file__)) + @pytest.mark.parametrize("s, ref, val, rest", [ ("C12", "C", 12, ""), ("C12n", "C", 12, "n"), ("C", "C", sys.maxsize, ""), ("Foo", "Foo", sys.maxsize, ""), ("+3.0VA1", "+3.0VA1", sys.maxsize, ""), - ]) +]) def test_split_ref(s, ref, val, rest): - assert split_ref(s) == (ref, val, rest) + assert split_ref(s) == (ref, val, rest) + def test_read_bom_1(): - bom = read_bom(basedir + '/../demo/kicad/bom/A64-OlinuXino_Rev_C.xml') - assert len(bom.get_components()) == 425 + bom = read_bom(basedir + '/../demo/kicad/bom/A64-OlinuXino_Rev_C.xml') + assert len(bom.get_components()) == 425 + def test_read_bom_2(): - bom = read_bom(basedir + '/../demo/kicad/bom/gw.xml') - assert len(bom.get_components()) == 165 + bom = read_bom(basedir + '/../demo/kicad/bom/gw.xml') + assert len(bom.get_components()) == 165 + + r5 = bom.get_component("R5") + assert r5.ref == "R5" + assert r5.value == "R0402_100R" + assert r5["value"] == "R0402_100R" + assert r5.footprint == "Resistors_SMD:R_0402" + assert r5.library.name == "gw-cache" + 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() - r5 = bom.get_component("R5") - assert r5.ref == "R5" - assert r5.value == "R0402_100R" - assert r5["value"] == "R0402_100R" - assert r5.footprint == "Resistors_SMD:R_0402" - assert r5.library.name == "gw-cache" - 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 not "foo" in r5 + with pytest.raises(KeyError): + r5["foo"] - assert not "foo" in r5 - with pytest.raises(KeyError): - r5["foo"] def test_read_bom_2_pandas(): - bom = read_bom(basedir + '/../demo/kicad/bom/gw.xml').to_pandas() - assert len(bom) == 165 + 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" - assert r5["value"] == "R0402_100R" -# assert r5["footprint"] == "Resistors_SMD:R_0402" -# assert r5.library.name == "gw-cache" -# 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() + 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" + assert r5["value"] == "R0402_100R" + # assert r5["footprint"] == "Resistors_SMD:R_0402" + # assert r5.library.name == "gw-cache" + # 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 not "foo" in r5 - with pytest.raises(KeyError): - r5["foo"] + assert not "foo" in r5 + with pytest.raises(KeyError): + r5["foo"] diff --git a/test/test_digikey.py b/test/test_digikey.py index 0b79777..6073e25 100644 --- a/test/test_digikey.py +++ b/test/test_digikey.py @@ -10,19 +10,19 @@ client = dk.DigikeyClient(digikey) def test_digikey_1(): - p = client.search("TCR2LF18LM(CTTR-ND") - assert isinstance(p, dk.DigikeyProduct) - assert p.part_number == "TCR2LF18LM(CTTR-ND" - assert len(p.attributes) > 5 - x = p.to_yaml() - print(type(x)) - print("{}".format(x)) - yaml.dump(x, sys.stdout) + p = client.search("TCR2LF18LM(CTTR-ND") + assert isinstance(p, dk.DigikeyProduct) + assert p.part_number == "TCR2LF18LM(CTTR-ND" + assert len(p.attributes) > 5 + x = p.to_yaml() + print(type(x)) + print("{}".format(x)) + yaml.dump(x, sys.stdout) def test_digikey_2(): - response = client.search("TCR2LF") - [print(p.part_id) for p in response.products] - assert len(response.products) == 28 - # p = products[0] - # assert p.part_number == "TCR2LF18LM(CTTR-ND" + response = client.search("TCR2LF") + [print(p.part_id) for p in response.products] + assert len(response.products) == 28 + # p = products[0] + # assert p.part_number == "TCR2LF18LM(CTTR-ND" diff --git a/test/test_formatting.py b/test/test_formatting.py index 6fa0b07..18f582c 100644 --- a/test/test_formatting.py +++ b/test/test_formatting.py @@ -2,6 +2,7 @@ import pytest import numpy as np from ee.formatting import eng_str + @pytest.mark.parametrize("input,expected", [ (0, "0"), (5.5, "5.5"), @@ -18,29 +19,32 @@ from ee.formatting import eng_str (0.000055, "55 u"), (0.0000055, "5.5 u"), (0.00000055, "550 p"), - ]) +]) def test_eng_str(input, expected): - assert expected == eng_str(input) - npinput = np.float64(input) - assert expected == eng_str(npinput) + assert expected == eng_str(input) + npinput = np.float64(input) + assert expected == eng_str(npinput) + @pytest.mark.parametrize("input,expected", [ (0, "0 A"), (5.5, "5.5 A"), (1100, "1.1 kA"), (0.05, "50 mA"), - ]) +]) def test_eng_str_with_unit(input, expected): - assert expected == eng_str(input, unit = 'A') + assert expected == eng_str(input, unit='A') + @pytest.mark.parametrize("input,expected", [ (100, ''), (101, ''), (102, ''), (103, ''), (104, ''), (105, ''), (106, ''), (107, ''), (108, ''), (109, ''), (110, ''), (111, ''), (112, ''), (113, ''), (114, ''), (115, ''), (116, ''), (117, ''), (118, ''), (119, ''), (120, ''), (121, ''), (122, ''), (123, ''), (124, ''), (125, ''), (126, ''), (127, ''), (128, ''), (129, ''), (130, ''), (131, ''), (132, ''), (133, ''), (134, ''), (135, ''), (136, ''), (137, ''), (138, ''), (139, ''), - ]) +]) def xx_test_eng_str2(input, expected): - assert expected == eng_str(input) + assert expected == eng_str(input) + @pytest.mark.parametrize("input,expected", [ (10, '10'), @@ -53,6 +57,6 @@ def xx_test_eng_str2(input, expected): (17, '17'), (18, '18'), (19, '19'), - ]) +]) def xx_test_eng_str3(input, expected): - assert expected == eng_str(input) + assert expected == eng_str(input) |