Skip to content
This repository has been archived by the owner on Oct 2, 2020. It is now read-only.

MPN and Datasheet assingment tool #226

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 53 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,45 @@ KiCad utilities

**update_footprints.py**: This script updates the footprints fields of sch files using a csv as input.

**assign_mpns.py**: This script updates the MPN and Datasheet fields of sch files using a json lookup table as input.
The json file may look like this
```json
{
"C": [
{
"Value" : "100n",
"Footprint" : "Capacitors_SMD:C_0402",
"Tolerance" : "5%",
"MPN" : "12345",
"Datasheet" : "https://www.url.tld/12345.pdf"
},
{
"Value" : "1u",
"Footprint" : "Resistors_SMD:R_0402",
"Tolerance" : "10%",
"MPN" : "abcde",
"Datasheet" : "https://www.url.tld/abcde.pdf"
}
],
"R": [
{
"Value" : "100",
"Footprint" : "Capacitors_SMD:C_0402",
"Tolerance" : "5%",
"MPN" : "67890",
"Datasheet" : "https://www.url.tld/67890.pdf"
},
{
"Value" : "1k",
"Footprint" : "Resistors_SMD:R_0402",
"Tolerance" : "10%",
"MPN" : "fghij",
"Datasheet" : "https://www.url.tld/fghij.pdf"
}
]
}
```

## pcb directory

**kicad_mod.py**: A python class to handle KiCad footprint files, as know as kicad_mod.
Expand Down Expand Up @@ -75,7 +114,21 @@ How to use
# run the following command to see other options
./add_part_number.py -h

## Assigning Manufacturer Part Numbers (MPNs) and Datasheet Links

# first get into sch directory
cd sch

# use the following command to add the MPN and Datasheet field using the passed json lookup table file
# the lookup table is a dict of lists of dicts
# the key for each list is the acronym for the part category
# e.g. "R" for resistors, "C" for capacitors, "IC" for integrated circuits
# the behaviour is search for a complete matching ditch is the correct list for each part
./assign_mpns.py -s path_to_sch_file -l path_to_json_lut

# run the following command to see the help
./assign_mpns.py -h

## Footprint Checker

# first get into pcb directory
Expand Down
130 changes: 130 additions & 0 deletions sch/assign_mpns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-

from argparse import ArgumentParser
from json import load
from errno import ENOENT, EINVAL
from re import compile as regex
from sch import Schematic
from logging import getLogger, StreamHandler
from logging import ERROR, WARNING, INFO, DEBUG, NOTSET

cat_expr = regex('[a-zA-Z]+')

def parse_arguments():
parser = ArgumentParser(description='Tool to assign MPNs to symbols in a KiCAD schematic')
mutexg = parser.add_mutually_exclusive_group()
mutexg.add_argument("-q", "--quiet", action = "store_true", help = 'Turn off warnings')
mutexg.add_argument("-v", action = "count", help = 'Set loglevel')
parser.add_argument('-s', "--schematic", type = str, required=True, help = 'path to .sch file')
parser.add_argument('-l', "--lookuptable", type = str, required=True, help = 'path to mpn lookup table')
args = parser.parse_args()
return args

def read_schematic(path):
try:
return Schematic(path)
except FileNotFoundError as fnfe:
error(fnfe)
error("Could not find schematic file")
exit(ENOENT)

def read_lut(path):
try:
return load(open(path, 'r'))
except FileNotFoundError as fnfe:
log.error(fnfe)
log.error("Could not find lookup table file")
exit(ENOENT)
except json.decoder.JSONDecodeError as jsone:
log.error(jsone)
log.error("Could not read lookup table file")
exit(EINVAL)

def generate_logger(verbose, quiet):
logger = getLogger()
logger.addHandler(StreamHandler())
if verbose:
if 1 == verbose:
logger.setLevel(INFO)
logger.info("Verbose output.")
elif 2 <= verbose:
logger.setLevel(DEBUG)
logger.debug("Debug output.")
elif quiet:
logger.setLevel(ERROR)
return logger

def gen_comp_tuple(component):
comp_tuple = {
"Value" : component.fields[1]["ref"].replace('"', ''),
"Footprint" : component.fields[2]["ref"].replace('"', ''),
}
for field in component.fields[4:]:
key = field["name"].replace('"', '')
value = field["ref"].replace('"', '')
if key != "MPN":
comp_tuple.update({key : value})
return comp_tuple

def gen_part_tuple(part):
part_tuple = part.copy()
try:
part_tuple.pop("MPN")
part_tuple.pop("Datasheet")
except KeyError as ke:
log.error("{} missing for {}: {}".format(ke, category, part))
return part_tuple

def component_assert_mpn_field(component):
mpn_field = None
for field in component.fields[4:]:
if "MPN" == field["name"].replace('"', ''):
mpn_field = field
if not mpn_field:
mpn_field = component.addField({'name': '"MPN"', 'ref': '""'})
return mpn_field

def match_component(component, part_lut):
# ignore these
if '#PWR' in component.fields[0]['ref'] or\
'PWR_FLAG' in component.fields[1]['ref']:
return

Reference = component.fields[0]["ref"].replace('"', '')
category = cat_expr.match(Reference).group()
datasheet_field = component.fields[3]
mpn_field = component_assert_mpn_field(component)
comp_tuple = gen_comp_tuple(component)

if category in part_lut:
found = False
for part in part_lut[category]:
# generate reduced dict for matching
part_tuple = gen_part_tuple(part)
# in case of match, assign MPN and Datasheet and iterate for next part
if part_tuple == comp_tuple:
log.info("Matched {} with {}".format(Reference, part))
mpn_field["ref"] = "\"{}\"".format(part["MPN"])
datasheet_field["ref"] = "\"{}\"".format(part["Datasheet"])
found = True
break
if not found:
log.warning("No part found for {}: {}".format(Reference, comp_tuple))
else:
log.warning("Category {} not found".format(category))

def main():
global log
args = parse_arguments()
log = generate_logger(args.v, args.quiet)
sch = read_schematic(args.schematic)
part_lut = read_lut(args.lookuptable)

for component in sch.components:
match_component(component, part_lut)

sch.save()

if __name__ == "__main__":
main()