Placement side in BOM

Dear ones,

Is it possible to output the assembly side of the components in the BOM? I have neither found a possibility with the schematic editor, the PCB editor nor “Interactive Html Bom”. But maybe someone else knows a Python script or a plugin that does this?

I have over 1200 parts. It would take a long time to do it manually. Our manufacturer needs that information.

Ideally, I would even like the output to be grouped according to this:
10nF - FrontSide - C1, C2, C6
10nF - BottomSide - C3, C5
100nF - FrontSide - C4,7
100nF - BottomSide - C8

Best regards,
Emanuel

Hmm, i looked through my notes for when I did my PCBA, using an old method based on an XSLT script. The CPL (POS) CSV file has a field called Layer, which is top or bottom. Although this is not in the BOM file, the top/bottom information is there and can be correlated with the CPL file via the RefDes.

So maybe there is a way to do this in Interactive HTML BOM already, somebody else can say.

Thank you for your answer. Unfortunately I don’t understand much of it.

Interactive Html Bom can display the placement side, so the information must be there. It just needs to be output in a column and at best grouped by that column.

Have you tried contacting the author of Interactive HTML BOM via their Github site?

Just a thought…
I have generated BOMs only from schematic and at schematic there is no top/bottom information, I think.
I have read that there is also a way to generate BOM form PCB and I think that only generating BOM from PCB (I have never tried) you can have a chance to get top/bottom info.

I have not contacted the author of Interactive Html Bom. It could have been that there is a much simpler solution here.

Personally, I don’t like having the assembly side in the BOM at all, because the BOM is intended for purchasing. The assembly side is already in the Pick&Place. But our manufacturer wants that.

@Piotr: I also had this thought, but the parts list output from the PCB editor didn’t help me any further.

What I was trying to tell you in an earlier post is that the information is split across two files, keyed by RefDes, and if someone (you?) is good at programming, it can be extracted into another column, or separate BOM files.

Ok, since I couldn’t resist a quick programming exercise I wrote this Python script:

#!/usr/bin/env python3

"""Read CPL and BOM CSV files and add a layer column to BOM file"""

import sys
import argparse
import csv


def process(cpl, bom):
    """Read CPL saving layer, then iterate through BOM, adding layer"""
    layer = {}          # map from RefDes to layer
    with open(cpl) as filehandle:
        reader = csv.DictReader(filehandle)
        for row in reader:
            layer[row['Designator']] = row['Layer']
    with open(bom) as filehandle:
        reader = csv.DictReader(filehandle)
        reader.fieldnames.append('Layer')
        writer = csv.DictWriter(sys.stdout, reader.fieldnames)
        writer.writeheader()
        for row in reader:
            if ',' in row['Designator']:        # handle comma separated designators
                for designator in row['Designator'].split(','):
                    row['Designator'] = designator
                    row['Layer'] = layer[designator]
                    writer.writerow(row)
            else:
                row['Layer'] = layer[row['Designator']]
                writer.writerow(row)


def main():
    """Main routine"""
    # pylint: disable=global-statement
    parser = argparse.ArgumentParser(
        description='Read CPL and BOM CSV files and add a layer column to BOM file')
    parser.add_argument('CPL')
    parser.add_argument('BOM')
    args = parser.parse_args()
    try:
        process(args.CPL, args.BOM)
    except IOError:
        print(f"Cannot process input files")


if __name__ == '__main__':
    main()

Given this CPL:

Designator,Val,Package,Mid X,Mid Y,Rotation,Layer
"C1","100n","C_0603_1608Metric",132.737200,-61.595000,-90.000000,top
"Q1","MMBT5551","SOT-23",115.017200,-59.055000,0.000000,top
"Q2","MMBT5551","SOT-23",115.017200,-63.500000,0.000000,top
"Q3","MMBT5551","SOT-23",115.017200,-67.945000,0.000000,bottom
"Q4","MMBT5551","SOT-23",106.237200,-71.120000,0.000000,bottom
"Q10","MMBT5551","SOT-23",96.046700,-55.880000,0.000000,bottom
"Q11","MMBT5551","SOT-23",96.046700,-60.960000,0.000000,bottom
"Q12","MMBT5551","SOT-23",96.046700,-66.040000,0.000000,bottom
"Q13","MMBT5551","SOT-23",96.046700,-71.120000,0.000000,bottom
"Q14","MMBT5551","SOT-23",101.126700,-55.880000,0.000000,bottom
"Q15","MMBT5551","SOT-23",101.126700,-60.960000,0.000000,bottom
"Q16","MMBT5551","SOT-23",101.126700,-66.040000,0.000000,bottom
"Q17","MMBT5551","SOT-23",101.126700,-71.120000,0.000000,bottom
"Q18","MMBT5551","SOT-23",106.206700,-55.880000,0.000000,top
"Q19","MMBT5551","SOT-23",106.206700,-60.960000,0.000000,top
"R1","22k","R_0603_1608Metric",120.732200,-60.325000,0.000000,top
"R2","22k","R_0603_1608Metric",120.732200,-64.135000,0.000000,top
"R3","22k","R_0603_1608Metric",120.542200,-67.945000,0.000000,bottom
"R4","22k","R_0603_1608Metric",105.977900,-67.310000,0.000000,bottom
"R9","22k","R_0603_1608Metric",132.737200,-66.040000,-90.000000,bottom
"R10","22k","R_0603_1608Metric",110.047200,-56.515000,0.000000,bottom
"R11","22k","R_0603_1608Metric",110.047200,-60.960000,0.000000,bottom
"R12","22k","R_0603_1608Metric",110.047200,-66.040000,0.000000,top
"R13","22k","R_0603_1608Metric",110.047200,-70.485000,0.000000,top

and this BOM:

Comment,Designator,Footprint,LCSC
"100n","C1","Capacitor_SMD:C_0603_1608Metric","C14663"
"MMBT5551","Q1,Q2,Q3,Q4,Q10,Q11,Q12,Q13,Q14,Q15,Q16,Q17,Q18,Q19","Package_TO_SOT_SMD:SOT-23","C2145"
"22k","R1,R2,R3,R4,R9,R10,R11,R12,R13","Resistor_SMD:R_0603_1608Metric","C31850"

running the program with ./addlayer.py modularnixie-pos.csv modularnixie.csv produces:

Comment,Designator,Footprint,LCSC,Layer
100n,C1,Capacitor_SMD:C_0603_1608Metric,C14663,top
MMBT5551,Q1,Package_TO_SOT_SMD:SOT-23,C2145,top
MMBT5551,Q2,Package_TO_SOT_SMD:SOT-23,C2145,top
MMBT5551,Q3,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q4,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q10,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q11,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q12,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q13,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q14,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q15,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q16,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q17,Package_TO_SOT_SMD:SOT-23,C2145,bottom
MMBT5551,Q18,Package_TO_SOT_SMD:SOT-23,C2145,top
MMBT5551,Q19,Package_TO_SOT_SMD:SOT-23,C2145,top
22k,R1,Resistor_SMD:R_0603_1608Metric,C31850,top
22k,R2,Resistor_SMD:R_0603_1608Metric,C31850,top
22k,R3,Resistor_SMD:R_0603_1608Metric,C31850,bottom
22k,R4,Resistor_SMD:R_0603_1608Metric,C31850,bottom
22k,R9,Resistor_SMD:R_0603_1608Metric,C31850,bottom
22k,R10,Resistor_SMD:R_0603_1608Metric,C31850,bottom
22k,R11,Resistor_SMD:R_0603_1608Metric,C31850,bottom
22k,R12,Resistor_SMD:R_0603_1608Metric,C31850,top
22k,R13,Resistor_SMD:R_0603_1608Metric,C31850,top

Do what you like with this script. Have fun.

3 Likes

Warum das nicht in die Stückliste gehört:

Die Bestückungsseite in der Stückliste aufführen macht keinen Sinn, sorry. Die Stückliste ist eine Materialliste, damit kann sichergestellt werden das alles Material vorhanden ist. In der Stückliste sind gleiche Teile meistens gruppiert. Beispielsweise kann eine einzige Zeile für 3 Widerstände, alle mit dem Wert 1 kΩ, haben, unabhängig davon auf welcher Seite diese Bestückt werden.
Darum ist das was du fragst nicht in KiCad integriert.

Was du machen kannst:

  • Du kannst die Bauteilpositionierungsdatei (Dateiendung .pos oder -pos.csv) verwenden. Wenn das nicht reicht: Welche Information fehlt dir?
  • Sollte das nicht genügen: Du könntest ein kleines Skript schreiben, welches die Bauteilpositionierungsdatei und die Stückliste (Dateiendung .csv) einliest und diese miteinander kombiniert. Das dürfte nicht so schwer sein.

Edit: Skript erstellt

Dieses Skript liest eine Stückliste und zwei Bauteilpositionierungsdateien, eine für die Oberseite und eine für die Unterseite. Es wird eine Stückliste ausgegeben welches die Bestückungsseite enthält.

#!/usr/bin/env python3

import argparse
import csv
import sys


parser=argparse.ArgumentParser()
parser.add_argument("--oberseite",action="store",type=str,required=True,dest="oben",help="Bauteilpositionierungsdatei Oberseite im ASCII-Format")
parser.add_argument("--unterseite",action="store",type=str,required=True,dest="unten",help="Bauteilpositionierungsdatei Unterseite im ASCII-Format")
parser.add_argument("--stueckliste",action="store",type=str,required=True,dest="stueckliste",help="Stückliste im CSV-Format")
argumente=parser.parse_args(sys.argv[1:])

def referenzenAusPos(dateiname):
  """
  Lese die Referenzen aus einer Bauteilpositionierungsdatei (*.pos) aus.
  Annahme: Die Referenzen sind immer am Zeilenanfang und beginnen nicht mit einem #
  """
  with open(dateiname) as d:
    liste=[]
    for zeile in d: 
      zeile=zeile.strip()
      if len(zeile)>3:
        if zeile[0]!='#':
          liste.append( zeile.split(' ')[0] )
    return liste
      

#Referenzenliste, wo welches Bauteil bestückt wird.
teileOben  = referenzenAusPos(argumente.oben)
teileUnten = referenzenAusPos(argumente.unten)

def ausgabe(referenz,zeile,seite):
  """
  Gibt eine Zeile der Stückliste aus.
  Zeile ist eine Liste der Kolonnen einer Zeile
  Annahme: zeile[0] enthält die Referenzen. Diese wird ersetzt durch referenz
  Die Letzt Kolonnen enthält die Bestückungsseite
  """
  print( '"'+referenz+'"', end=',' )
  for kollone in zeile[1:]: print( '"'+kollone+'"', end=',' )
  print( '"'+seite+'"'  )


#Lese die Stückliste. Teile diese auf so das jedes Bauteil einzeln aufgelistet wird.
#Gebe jedes Bauteil inklusive Bestückungsseite aus.
#Bauteile ohne Bestückungsseite werden nicht ausgegeben
#Annahme: die erste Kolonne enthält die Referenzen
with open(argumente.stueckliste,newline='') as s:
  leser = csv.reader( s, delimiter=',', quotechar='"' )
  for zeile in leser:
    if len(zeile)>1:
      for referenz in zeile[0].split(','):
        referenz = referenz.strip()
        if referenz in teileOben : ausgabe( referenz, zeile, "oben"  )
        if referenz in teileUnten: ausgabe( referenz, zeile, "unten" )
          

Funktioniert mit dieser Stückliste:

"Source:","--------"
"Date:","Mi 21 Feb 2024 12:24:22"
"Tool:","Eeschema 7.0.10+1"
"Generator:","/usr/share/kicad/plugins/bom_csv_grouped_by_value_with_fp.py"
"Component Count:","14"
"Ref","Qnty","Value","Cmp name","Footprint","Description","Vendor","DNP"
"C1, C3, C4","3","10uF","C","footprints:3216_(1206)","","",""
"C2","1","100nF","C","footprints:2012_(0805)","","",""
"E1, E2","2","M3","M3","footprints:Hole_M3","","",""
"E3, E4","2","fiducial","fiducial","footprints:fiducial","","",""
"E5","1","mechanics","mechanics","------:mechanik","","",""
"IC1","1","TSSP77038","TSSP77038","footprints:TSSP770__","","",""
"R1","1","1k","R","footprints:2012_(0805)","","",""
"V1","1","IR","LED","footprints:SIM-012SB","","",""
"V2","1","Green","LED","footprints:2012_(0805)_POL","","",""
"X1","1","MicroMatch-6Pol","MicroMatch-6Pol","footprints:MicroMatch-6Pol","","",""

Diesen Bauteilpositionierungsdateien:

### Footprint positions - created on Mi 21 Feb 2024 12:32:28 ###
### Printed by KiCad version 7.0.10+1
## Unit = mm, Angle = deg.
## Side : bottom
# Ref     Val       Package                PosX       PosY       Rot  Side
C3        10uF      3216_(1206)         37.4400    43.1800  180.0000  bottom
C4        10uF      3216_(1206)         37.4400    39.5800  180.0000  bottom
V1        IR        SIM-012SB            1.6000     3.2000  -155.1000  bottom
## End
### Footprint positions - created on Mi 21 Feb 2024 12:32:28 ###
### Printed by KiCad version 7.0.10+1
## Unit = mm, Angle = deg.
## Side : top
# Ref     Val              Package                PosX       PosY       Rot  Side
C1        10uF             3216_(1206)         29.0000    42.3000  180.0000  top
C2        100nF            2012_(0805)         29.0000    47.0000  180.0000  top
E1        M3               Hole_M3             27.0000    19.0000    0.0000  top
E2        M3               Hole_M3             27.0000    34.0000    0.0000  top
E3        fiducial         fiducial            25.9000    13.5000    0.0000  top
E4        fiducial         fiducial            31.1000    44.8000    0.0000  top
E5        mechanics        mechanik             0.0000     0.0000    0.0000  top
IC1       TSSP77038        TSSP770__           25.7000    52.9000  -24.9000  top
R1        1k               2012_(0805)         29.2000    25.0000    0.0000  top
V2        Green            2012_(0805)_POL     29.2000    27.4000    0.0000  top
X1        MicroMatch-6Pol  MicroMatch-6Pol     26.1000     3.6000   90.0000  top
## End

Ergibt

"C1","3","10uF","C","footprints:3216_(1206)","","","","oben"
"C3","3","10uF","C","footprints:3216_(1206)","","","","unten"
"C4","3","10uF","C","footprints:3216_(1206)","","","","unten"
"C2","1","100nF","C","footprints:2012_(0805)","","","","oben"
"E1","2","M3","M3","footprints:Hole_M3","","","","oben"
"E2","2","M3","M3","footprints:Hole_M3","","","","oben"
"E3","2","fiducial","fiducial","footprints:fiducial","","","","oben"
"E4","2","fiducial","fiducial","footprints:fiducial","","","","oben"
"E5","1","mechanics","mechanics","deea0096:mechanik","","","","oben"
"IC1","1","TSSP77038","TSSP77038","footprints:TSSP770__","","","","oben"
"R1","1","1k","R","footprints:2012_(0805)","","","","oben"
"V1","1","IR","LED","footprints:SIM-012SB","","","","unten"
"V2","1","Green","LED","footprints:2012_(0805)_POL","","","","oben"
"X1","1","MicroMatch-6Pol","MicroMatch-6Pol","footprints:MicroMatch-6Pol","","","","oben"

Das Skript könnte Probleme machen wenn du spezielle Zeichen am falschen Ort hast. Beispielsweise wenn ein " in dem Bauteilnamen ist.

Edit 2: retiredfeline wahr wohl schneller.

Genau das habe ich oben ja auch geschrieben, zum einen, dass es für den Einkauf ist und diese Information nicht in die Stückliste gehört. Zum anderen, dass ich die Bauteile gruppieren werde, wie oben beispielhaft gezeigt. Damit verdoppelt man aber die meisten Bauteilpositionen und hat redundante Angaben in der Stückliste. Ich bin damit nicht glücklich. Ich werde mal unseren Fertiger bedrängen, anders klarzukommen.

Danke für das Script, ich werde es mir ansehen. / Thank you @retiredfeline for the script. I’ll have a look at it.

Vermutung: Der Bestücker will wissen wann er welche Rollen in der Bestückungsmaschine montieren soll. Wahrscheinlich hat er kein Platz um alle Artikel in der Bestückungsmaschine gleichzeitig zu haben oder er verwendet 2 Bestückungsmaschinen (einmal für oben und einmal für unten). Darum will er wissen welche Rollen er für die Oberseitenbestückung montieren muss und welche für die Unterseite.

Naja, mit einem der Skripte solltest du ein Tabelle erstellen können die er dafür brauchen kann.

Alternative: In dem Interactive Html Bom kannst du oben rechts zwischen Oberseite, Unterseite und beide wechseln. Wenn du dort Oberseite auswählst kannst du die Stückliste kopieren mit nur den Teilen auf der Oberseite.

The best-case scenario has occurred. Our manufacturer no longer insists on this column in the parts list.

Thank you all for your thoughts!

I would expect mounting side to be present in the PnP files but in the BoM, perhaps optional.
IE top or bottom. The machine has to separately transpose coordinates and know what goes where.

BoM file might not need that information but in all BoMs I have seen from other tools it is there as option.
PnP files well it is a must.

Well, I think the question has been answered within the confines of what is possible in KiCAD today. So there is no point in me trying to answer or to chime in with some minutia of how the problem may be tackled more efficiently. However, I would like to comment on what I find is a common misunderstanding of what a BOM is (or should be) all about that is clearly expressed in this thread, particularly in the German comments.

The basic sentiment seems to be of the theme “the BOM is just for purchasing and thus nothing unrelated to purchasing should show up in the BOM”.

I couldn’t disagree more. This philosophy is simply based on some narrow personal understanding of what a BOM is or might be. In the real world people use BOMs for all sorts of purposes and in all sorts of ways. For this reason they tend to like BOMs to have (or have not) all sorts of bits of information and be grouped/sorted in all sorts of ways. Other EDA packages support this in various more or less sophisticated ways for a reason. EDA tools should be a support not a hindrance and not everyone’s workflow is the same.

Something not being possible technically is a reason (albeit not necessarily a good one) that something a user wants cannot be provided. There might even be a desire to implement such features, but in the larger picture of priorities it might rank rather low. But such reasons do not imply that what a user wants is fundamentally unreasonable. So I don’t think answers should be framed in such a way.

but… the BoM is only for purchasing/ planning
and the PnP files are only for placement.

Why so different ?
BoM lines have number of rows = number of different components

PnP files have number of rows = number of components / placements.

There is a bit of an overlap region for CMs
CMs need both the BOM and the PNP file, they’ll put it into a single database object.

Without commenting on whether the side belongs in the BOM or not, I see the problem of being able to provide it as due to the separation between attributes held by schematic application and attributes held by the layout application as due to the two main facets to the kicad application. That separation may diminish in future.

2 Likes

That is understood and appreciated. As I wrote:

Something not being possible technically is a reason (albeit not necessarily a good one) that something a user wants cannot be provided.

My point was that technical feasibility is one thing. A user having fundamentally unreasonable expectations or wanting to do things that make no sense is another. In this case it is clearly the former. It is presently not technically feasible to deliver the requested information in a single export file in KiCAD. The user’s expectation on the other hand is perfectly reasonable and other EDA packages can deliver on this, despite a separation of schematic and board.

Clearly at the moment the developer team has bigger fish to fry than to implement BOM functionality. The basic capability exists and it is good enough for most needs. So I didn’t want to comment on the KiCAD feature set here, but on the attitude of some posters trying to put the OP in a position of unreasonable expectation.

1 Like

I myself was not satisfied with having to write the assembly side in the BOM. For me, it doesn’t belong there either, although I would like to agree with the previous posters and let others do it this way.
Fortunately, I was able to reach an agreement with the long-standing manufacturer to omit this information from the BOM in future. PnP files already contain the assembly side. That should be enough.

Sorry, I’m late. Probably the author would have pointed out the function of the F, FB and B buttons which filters not only the pcb previews but also the bom list. With the HTML comes the top/bot info of the parts. This HTML output is handy for the manufacturer too - he could sort, group, filter, select and order columns prior to export/copy the output to his system/excel/calc/whatever.

If I select F and click the copy button and repeat this with B selection, I can copy two parts of the bom to external tools appending the top and bottom info. This is possible with or w/o grouping for parts/value/order number/etc. Interactive Html Bom is a great and handy thing! Thanks @qu1ck

1 Like

Great, I didn’t know that. Thank you!

But I always give the HTML file to the manufacturer as well. I don’t know if they use it, but it could be useful.
However, we also have to create our own BOM with sourcing information.