2021-02-14 08:51:14 -08:00
#!/usr/bin/env python
############################################################################################
# Hellen-One: A BOM processing script.
# (c) andreika <prometheus.pcb@gmail.com>
############################################################################################
from collections import OrderedDict
2022-02-07 06:02:33 -08:00
import csv , os , sys , re
include_pat = re . compile ( r ' #include \ s+ \ " ?([^ \ " ]+) \ " ?$ ' )
def read_repl_file ( csv_name , repl_base_path , replList ) :
print ( " Reading replacement list from the CSV file " + csv_name + " ... " )
2022-03-26 12:30:24 -07:00
with open ( csv_name , ' rt ' ) as f :
2022-02-07 06:02:33 -08:00
reader = csv . reader ( f , delimiter = ' , ' )
for row in reader :
# skip empty lines
if ( len ( row ) < 1 ) :
continue
# process includes
include = include_pat . match ( row [ 0 ] . strip ( ) )
if ( include ) :
csv_sub_name = os . path . join ( repl_base_path , include . group ( 1 ) )
if ( csv_sub_name != csv_name ) :
read_repl_file ( csv_sub_name , repl_base_path , replList )
# skip comments (this is not strictly CSV-compliant, but useful for our purposes)
if ( row [ 0 ] . startswith ( " # " ) ) :
continue
2022-06-11 08:09:56 -07:00
col0 = row [ 0 ] . split ( " , " )
for col in col0 :
subrow = [ col . strip ( ) , row [ 1 ] ]
if ( len ( row ) > 2 ) :
subrow . append ( row [ 2 ] )
if ( len ( row ) > 3 ) :
subrow . append ( row [ 3 ] )
replList . append ( subrow )
2021-02-14 08:51:14 -08:00
2023-05-18 10:12:16 -07:00
def printWarning ( text ) :
print ( text )
if warningFileName :
with open ( warningFileName , " a " ) as wf :
wf . write ( text + " \n " )
2021-02-14 08:51:14 -08:00
if len ( sys . argv ) < 2 :
2021-03-22 23:58:14 -07:00
print ( " Error! Please specify a BOM file name. " )
2021-02-14 08:51:14 -08:00
sys . exit ( 1 )
fileName = sys . argv [ 1 ]
if len ( sys . argv ) > 2 :
2021-03-22 23:58:14 -07:00
repl_csv = sys . argv [ 2 ]
2022-02-07 06:02:33 -08:00
repl_base_path = os . path . dirname ( repl_csv )
2021-02-14 08:51:14 -08:00
2023-05-18 10:12:16 -07:00
if len ( sys . argv ) > 3 :
warningFileName = sys . argv [ 3 ]
2021-03-22 23:58:14 -07:00
print ( " Opening BOM file " + fileName + " ... " )
2021-02-14 08:51:14 -08:00
rows = OrderedDict ( )
2023-01-03 11:35:36 -08:00
rowDes = OrderedDict ( )
2021-02-14 08:51:14 -08:00
emptyId = 1
2022-03-26 12:30:24 -07:00
with open ( fileName , ' rt ' ) as f :
2021-02-14 08:51:14 -08:00
reader = csv . reader ( f , delimiter = ' , ' )
2021-03-22 23:58:14 -07:00
print ( " Searching for duplicates... " )
2021-02-14 08:51:14 -08:00
for row in reader :
2021-09-20 23:34:08 -07:00
row [ 3 ] = row [ 3 ] . strip ( )
2021-02-14 08:51:14 -08:00
rowName = row [ 3 ]
2021-09-20 23:34:08 -07:00
row [ 1 ] = row [ 1 ] . split ( " , " )
2023-01-03 11:35:36 -08:00
for rDes in row [ 1 ] :
if rDes in rowDes :
print ( " * Error! Duplicate designators found in BOM: " + rDes )
sys . exit ( 3 )
rowDes [ rDes ] = 1
2021-02-14 08:51:14 -08:00
# all empty names should be saved separately
if not rowName :
rows [ " _ " + str ( emptyId ) ] = row
emptyId + = 1
continue
if rowName in rows :
oldRow = rows [ rowName ]
if oldRow [ 0 ] != row [ 0 ] :
2021-03-22 23:58:14 -07:00
print ( " * Error! Comment mismatch for the part # " + rowName + " : " + oldRow [ 0 ] + " != " + row [ 0 ] )
2021-02-14 08:51:14 -08:00
sys . exit ( 2 )
if oldRow [ 2 ] != row [ 2 ] :
2023-05-18 10:12:16 -07:00
printWarning ( " * Warning! Footprint mismatch for the part # " + rowName + " : " + oldRow [ 2 ] + " != " + row [ 2 ] )
2021-03-22 23:58:14 -07:00
print ( " * Duplicates found for " + rowName + " ( " + row [ 0 ] + " )! Merging... " )
2021-02-14 08:51:14 -08:00
row [ 1 ] = oldRow [ 1 ] + row [ 1 ]
rows [ rowName ] = row
#for idx,item in enumerate(row):
# print idx , ": ", item
2021-03-22 23:58:14 -07:00
replList = list ( )
2022-02-07 06:02:33 -08:00
read_repl_file ( repl_csv , repl_base_path , replList )
2021-03-22 23:58:14 -07:00
print ( " Processing the board replacements... " )
2021-02-14 08:51:14 -08:00
for r in replList :
2021-03-22 23:58:14 -07:00
reDesignator = r [ 0 ]
2022-03-26 12:30:24 -07:00
for rowName in list ( rows . keys ( ) ) :
2021-02-14 08:51:14 -08:00
row = rows [ rowName ]
if reDesignator in row [ 1 ] :
2021-03-22 23:58:14 -07:00
print ( " * Removing " + reDesignator + " from the old row... " )
2021-02-14 08:51:14 -08:00
row [ 1 ] . remove ( reDesignator )
if not row [ 1 ] :
2021-03-22 23:58:14 -07:00
print ( " * Deleting an empty row... " )
2021-02-14 08:51:14 -08:00
del rows [ rowName ]
2021-03-22 23:58:14 -07:00
if len ( r ) < 4 :
continue
reComment = r [ 1 ]
reFootprint = r [ 2 ]
rePartNumber = r [ 3 ]
2021-02-14 08:51:14 -08:00
# find the matching row by partnumber (if set)
if rePartNumber :
if rePartNumber in rows :
2021-03-22 23:58:14 -07:00
print ( " * Adding " + reDesignator + " to another existing row... " )
2021-02-14 08:51:14 -08:00
rows [ rePartNumber ] [ 1 ] + = [ reDesignator ]
else :
2021-03-22 23:58:14 -07:00
print ( " * Appending a new row for " + reDesignator + " ... " )
2021-02-14 08:51:14 -08:00
rows [ rePartNumber ] = [ reComment , [ reDesignator ] , reFootprint , rePartNumber ]
2022-02-07 06:02:33 -08:00
2023-05-18 10:12:16 -07:00
print ( " Checking for identical parts with different partnumbers... " )
commentAndFootprint = OrderedDict ( )
for rowName in rows :
row = rows [ rowName ]
cf = row [ 0 ] + " _ " + row [ 2 ]
if not row [ 3 ] :
continue
partName = ( " , " . join ( row [ 1 ] ) ) if ( type ( row [ 1 ] ) == list ) else row [ 1 ]
if ( cf in commentAndFootprint ) :
if ( commentAndFootprint [ cf ] [ 1 ] != row [ 3 ] ) :
printWarning ( " * Warning! Identical parts " + partName + " and " + commentAndFootprint [ cf ] [ 0 ] + " ( " + cf + " ) have different partnumbers: " + row [ 3 ] + " and " + commentAndFootprint [ cf ] [ 1 ] )
else :
commentAndFootprint [ cf ] = [ partName , row [ 3 ] ]
2022-02-07 06:02:33 -08:00
print ( " Final checks... " )
for rowName in rows :
row = rows [ rowName ]
if ( row [ 0 ] == ' ? ' and row [ 3 ] . lower ( ) == ' board_id ' ) :
partName = ( " , " . join ( row [ 1 ] ) ) if ( type ( row [ 1 ] ) == list ) else row [ 1 ]
print ( " Error! The components used by Board_ID detection method ( " + partName + " ) are undefined! " )
print ( " Please include corresponding board definition file (board_id_XXX.csv) to your BOM replacement file. " )
print ( " If your Board_ID is unknown (custom board), just add the following line to the end of your bom_replace file: " )
print ( " #include hellen-one/board_id/board_id_unknown.csv " )
sys . exit ( 2 )
2021-02-14 08:51:14 -08:00
2021-03-22 23:58:14 -07:00
print ( " Saving... " )
2022-03-26 12:30:24 -07:00
with open ( fileName , ' wt ' ) as new_f :
2021-02-14 08:51:14 -08:00
rowIdx = 0
for rowName in rows :
#for idx,item in enumerate(rows[rowName]):
# print idx , ": ", item
if rowIdx == 0 :
2022-03-26 12:30:24 -07:00
writer = csv . writer ( new_f , quoting = csv . QUOTE_NONE , quotechar = ' " ' , escapechar = ' ' , delimiter = ' , ' , lineterminator = ' \n ' )
2021-02-14 08:51:14 -08:00
elif rowIdx == 1 :
2022-03-26 12:30:24 -07:00
writer = csv . writer ( new_f , quoting = csv . QUOTE_ALL , quotechar = ' " ' , escapechar = ' ' , delimiter = ' , ' , lineterminator = ' \n ' )
2021-02-14 08:51:14 -08:00
row = rows [ rowName ]
# restore empty names
if rowName [ 0 ] == ' _ ' :
row [ 3 ] = " "
if type ( row [ 1 ] ) == list :
row [ 1 ] = " , " . join ( row [ 1 ] )
writer . writerow ( row )
rowIdx + = 1
2021-03-22 23:58:14 -07:00
print ( " Done! " )