mirror of
https://github.com/whowechina/popn_pico.git
synced 2024-12-12 23:01:08 +01:00
204 lines
6.3 KiB
Python
204 lines
6.3 KiB
Python
#!/usr/bin/env python
|
|
"""
|
|
xml2bom.py
|
|
Copyright 2015 Adam Greig
|
|
Licensed under the MIT licence, see LICENSE file for details.
|
|
|
|
Convert Farnell BOM XMLs to a useful text report, including sanity checking,
|
|
and outputting quickpaste formats for Farnell, RS and DigiKey.
|
|
"""
|
|
|
|
from __future__ import print_function, division
|
|
import os.path
|
|
import datetime
|
|
import argparse
|
|
import xml.etree.ElementTree as ET
|
|
|
|
parser = argparse.ArgumentParser(
|
|
prog='xml2bom',
|
|
description="Convert KiCAD EESchema XML BOMs to an expanded text format")
|
|
parser.add_argument("input", help="input filename")
|
|
parser.add_argument("output", nargs='?', default=None, help="output filename")
|
|
parser.add_argument("-x", "--quantity", type=int, help="quantity multiplier")
|
|
group = parser.add_mutually_exclusive_group()
|
|
group.add_argument("-i", "--include", nargs='+', help="parts to include")
|
|
group.add_argument("-e", "--exclude", nargs='+', help="parts to exclude")
|
|
args = parser.parse_args()
|
|
|
|
tree = ET.parse(args.input)
|
|
parts = {}
|
|
missing_order_code = []
|
|
missing_footprint = []
|
|
inconsistent_order_code = {}
|
|
quantity_multiplier = 1
|
|
|
|
if args.quantity:
|
|
quantity_multiplier = args.quantity
|
|
|
|
|
|
def ignore_part(ref):
|
|
if args.include and ref not in args.include:
|
|
return True
|
|
elif args.exclude and ref in args.exclude:
|
|
return True
|
|
return False
|
|
|
|
|
|
for comp in tree.getroot().iter('comp'):
|
|
ref = comp.get('ref')
|
|
if ignore_part(ref):
|
|
continue
|
|
val = comp.findtext('value')
|
|
foot = comp.findtext('footprint')
|
|
fields = {}
|
|
part = {"ref": ref, "value": val, "footprint": foot, "fields": fields}
|
|
|
|
for field in comp.iter('field'):
|
|
name = field.get('name')
|
|
number = field.text
|
|
fields[name] = number
|
|
|
|
if name not in parts:
|
|
parts[name] = {}
|
|
if number not in parts[name]:
|
|
parts[name][number] = []
|
|
elif (parts[name][number][0]['value'] != val or
|
|
parts[name][number][0]['footprint'] != foot):
|
|
if name not in inconsistent_order_code:
|
|
inconsistent_order_code[name] = {}
|
|
if number not in inconsistent_order_code[name]:
|
|
inconsistent_order_code[name][number] = [
|
|
parts[name][number][0]]
|
|
inconsistent_order_code[name][number].append(part)
|
|
parts[name][number].append(part)
|
|
|
|
# Store parts missing order codes or footprints
|
|
if not fields:
|
|
missing_order_code.append(part)
|
|
if not foot:
|
|
missing_footprint.append(part)
|
|
|
|
missing_footprint_report = "\n".join(
|
|
"{:6} {:15}".format(p['ref'], p['value']) for p in missing_footprint)
|
|
|
|
missing_order_code_report = "\n".join(
|
|
"{:6} {:15} {}".format(p['ref'], p['value'], p['footprint'])
|
|
for p in missing_order_code)
|
|
|
|
inconsistent_order_code_report = "\n".join(
|
|
" {}\n".format(name) + " " + "~"*len(name) + "\n" + "\n".join(
|
|
" {}: ".format(number) + "\n" + "\n".join(
|
|
" " +
|
|
"{:6} {:15} {}".format(p['ref'], p['value'], p['footprint'])
|
|
for p in inconsistent_order_code[name][number]
|
|
) for number in inconsistent_order_code[name]
|
|
) for name in inconsistent_order_code)
|
|
|
|
|
|
def farnell_formatter(number, parts):
|
|
qty = len(parts)
|
|
footprints = " ".join(set(
|
|
str(p['footprint']).split(":")[-1] for p in parts))
|
|
values = " ".join(set(p['value'] for p in parts))
|
|
note = "{}x {} {}".format(qty, values, footprints)
|
|
return "{},{},{}\n".format(number, qty * quantity_multiplier, note[:30])
|
|
|
|
|
|
def rs_formatter(number, parts):
|
|
qty = len(parts)
|
|
refs = "".join(p['ref'] for p in parts)
|
|
footprints = "-".join(set(
|
|
str(p['footprint']).split(":")[-1] for p in parts))
|
|
values = "-".join(set(p['value'] for p in parts))
|
|
return "{},{},,{}x--{}--{}--{}\n".format(
|
|
number, qty * quantity_multiplier, qty, values, footprints, refs)
|
|
|
|
|
|
def digikey_formatter(number, parts):
|
|
qty = len(parts)
|
|
refs = " ".join(p['ref'] for p in parts)
|
|
footprints = " ".join(set(
|
|
str(p['footprint']).split(":")[-1] for p in parts))
|
|
values = " ".join(set(p['value'] for p in parts))
|
|
return "{},{},{}x {} {} {}\n".format(
|
|
qty * quantity_multiplier, number, qty, values, footprints, refs)
|
|
|
|
|
|
vendor_bom_formatters = {
|
|
"farnell": farnell_formatter,
|
|
"rs": rs_formatter,
|
|
"digikey": digikey_formatter,
|
|
}
|
|
|
|
vendor_boms = []
|
|
for name in parts:
|
|
bom_text = " {}\n".format(name) + " " + "~"*len(name) + "\n"
|
|
for number in parts[name]:
|
|
if name.lower() in vendor_bom_formatters:
|
|
bom_text += vendor_bom_formatters[name.lower()](
|
|
number, parts[name][number])
|
|
else:
|
|
qty = len(parts[name][number])
|
|
bom_text += "{},{},{}x {} {}\n".format(
|
|
number, qty * quantity_multiplier, qty,
|
|
" ".join(p['ref'] for p in parts[name][number]),
|
|
",".join(set(
|
|
str(p['footprint']).split(":")[-1]
|
|
for p in parts[name][number])))
|
|
|
|
vendor_boms.append(bom_text)
|
|
vendor_boms = "\n\n".join(vendor_boms)
|
|
|
|
assembly_bom = "\n".join("\n".join(
|
|
"{:20} {:<3} {:15} {:<15} {:<}".format(
|
|
number, len(parts[name][number]),
|
|
",".join(set(str(p['value']) for p in parts[name][number])),
|
|
",".join(set(str(p['footprint']).split(":")[-1]
|
|
for p in parts[name][number])),
|
|
" ".join(sorted(p['ref'] for p in parts[name][number])))
|
|
for number in parts[name])
|
|
for name in parts)
|
|
|
|
report = """Bill Of Materials
|
|
=================
|
|
|
|
Source file: {source}
|
|
Date: {date}
|
|
|
|
Parts Missing Footprints
|
|
------------------------
|
|
{missing_footprint}
|
|
|
|
Parts Missing Order Codes
|
|
-------------------------
|
|
{missing_code}
|
|
|
|
Inconsistent Order Codes
|
|
------------------------
|
|
{inconsistent_code}
|
|
|
|
Vendor Specific BOMs
|
|
--------------------
|
|
{vendor_boms}
|
|
|
|
Assembly BOM
|
|
------------
|
|
{assembly_bom}
|
|
|
|
""".format(
|
|
source=os.path.basename(args.input),
|
|
date=datetime.datetime.now().isoformat(),
|
|
missing_footprint=missing_footprint_report,
|
|
missing_code=missing_order_code_report,
|
|
inconsistent_code=inconsistent_order_code_report,
|
|
vendor_boms=vendor_boms,
|
|
assembly_bom=assembly_bom)
|
|
|
|
print(report)
|
|
if args.output:
|
|
filename = args.output
|
|
if filename[-4:].lower() != ".bom":
|
|
filename += ".bom"
|
|
with open(filename, 'w') as f:
|
|
f.write(report)
|