Forum: Platinen KiCAD: eigenes Python-Skript für BOM-Export als CSV


Announcement: there is an English version of this forum on EmbDev.net. Posts you create there will be displayed on Mikrocontroller.net and EmbDev.net.
von Terence S. (takeshi)


Lesenswert?

TL;DR:

Ich möchte das Skript bearbeiten und arbeite dafür mit PyCharm. In KiCAD 
klappt das Original, in PyCharm nicht, da kann es die Netzliste nicht 
öffnen, das resultiert immer in einer Fehlermeldung. Dabei bräuchte ich 
Hilfe.

Hintergrund:

Meine Symbole enthalten neben den vier Standardfeldern noch weitere 
Felder. Ich möchte eine BOM erstellen, in der die Bauteile nicht nur 
nach dem Wert und dem Footprint gruppiert werden, sondern auch nach 
einigen benutzerdefinierten Feldern. Das kann keines der mitgelieferten 
Skripte bewerkstelligen, also muss ich selbst Hand anlegen. Und da 
hapert es nun.

Das Problem:

Ich beherrsche andere Programmiersprachen, aber bei Python steige ich 
einfach nicht durch. Ich komme mit dem einfachen Texteditor nicht 
weiter, also habe ich PyCharm installiert, weil das auch im Studium 
verwendet wurde. Eigentlich war geplant das originale Skript damit 
auszuführen und mir anzugucken, was es macht und welche Variablen gerade 
da sind. Leider funktioniert das originale Skip, in PyCharm jedoch 
nicht. Es wird in beiden Fällen Python 3,8 verwendet.

In den Einstellungen sind die Parameter übergeben:
1
"/home/user/KiCAD/Projekte/test/test.net" "/home/user/KiCAD/Projekte/test/test.csv"

Aus Rückgabe bekomme ich:
1
/usr/bin/python3.8 /usr/share/kicad/plugins/bom_csv_sorted_by_ref.py /home/user/KiCAD/Projekte/test/test.net /home/user/KiCAD/Projekte/test/test.csv
2
Traceback (most recent call last):
3
  File "/usr/lib/python3.8/xml/sax/expatreader.py", line 217, in feed
4
    self._parser.Parse(data, isFinal)
5
xml.parsers.expat.ExpatError: syntax error: line 1, column 0
6
7
During handling of the above exception, another exception occurred:
8
9
Traceback (most recent call last):
10
  File "/usr/share/kicad/plugins/bom_csv_sorted_by_ref.py", line 39, in <module>
11
    net = kicad_netlist_reader.netlist(sys.argv[1])
12
  File "/usr/share/kicad/plugins/kicad_netlist_reader.py", line 510, in __init__
13
    self.load(fname)
14
  File "/usr/share/kicad/plugins/kicad_netlist_reader.py", line 814, in load
15
    self._reader.parse(fname)
16
  File "/usr/lib/python3.8/xml/sax/expatreader.py", line 111, in parse
17
    xmlreader.IncrementalParser.parse(self, source)
18
  File "/usr/lib/python3.8/xml/sax/xmlreader.py", line 125, in parse
19
    self.feed(buffer)
20
  File "/usr/lib/python3.8/xml/sax/expatreader.py", line 221, in feed
21
    self._err_handler.fatalError(exc)
22
  File "/usr/lib/python3.8/xml/sax/handler.py", line 38, in fatalError
23
    raise exception
24
xml.sax._exceptions.SAXParseException: /home/user/KiCAD/Projekte/test/test.net:1:0: syntax error
25
26
Process finished with exit code 1

Ich habe die Pfade absolut angegeben, relativ (also mit "./test.net"), 
nur den Dateinamen ("test.net"), mit einfachen Anführungsstrichen, ohne 
Anführungsstriche, Slash durch Backslash ersetzt ... alles, was mir so 
eingefallen ist. Normalerweise sollte es ausreichen den Dateinamen in 
doppelten Anführungsstrichen anzugeben, wenn sich die Datei im gleichen 
Ordner wie das Skript befindet. So habe ich es zumindest gelesen und 
ausprobiert, aber ohne Erfolg. So schwer kann das doch nicht sein.

Hab auch versucht den Fehler entsprechend der Fehlermeldungen 
zurückzuverfolgen, aber da verstehe ich irgendwann nur Bahnhof.

von Nur_ein_typ (Gast)


Lesenswert?

Terence S. schrieb:
> xml.sax._exceptions.SAXParseException:
> /home/user/KiCAD/Projekte/test/test.net:1:0: syntax error
>
> Hab auch versucht den Fehler entsprechend der Fehlermeldungen
> zurückzuverfolgen, aber da verstehe ich irgendwann nur Bahnhof.

Offensichtlich versucht das Skript, eine XML-Datei zu lesen, aber die 
Datei, die es zu lesen versucht, ist keine XML-Datei oder eine mit einem 
Syntaxfehler in Zeile 1.

Vielleicht magst Du das Skript und die beiden Dateien hier einmal als 
Anhänge posten?

von Max M. (prokrastinator)


Lesenswert?

Sowas in der Art hatte ich auch.
Der Kicad Netlist Reader war nicht aufzufinden. Fehler über Fehler.
Leg das mit admin Rechten ins kicad Verzeichnis in dem auch die BOM 
Scripte liegen.

Hab lange rumgewürgt und es dann irgendwann so gelassen.
Ist schlecht umgesetzt.
Wozu gibt es das scripting verz. im dokumenten Kicad 6 Verzeichnisbaum?

von Terence S. (takeshi)


Angehängte Dateien:

Lesenswert?

Danke, ich denke das hat mir schon weitergeholfen! Lösung unten.

Das sind die Standard-Dateien, die mit KiCAD mitgeliefert werden. Habe 
sie dennoch mal angehängt.

In KiCAD wird mir diese Befehlszeile angezeigt:
1
"/usr/bin/python3" "/usr/share/kicad/plugins/bom_csv_sorted_by_ref.py" "%I" "%O.csv"

bom_csv_sorted_by_ref.py importiert die beiden Skripte 
kicad_netlist_reader.py und kicad_utils.py und scheitert an der 
folgenden Zeile:
1
net = kicad_netlist_reader.netlist(sys.argv[1])

Falls da auch noch jemand dran arbeitet, auf KiCAD.org wird das Ganze 
ein wenig beschrieben: 
https://docs.kicad.org/6.0/en/eeschema/eeschema.html#creating-customized-netlists-and-bom-files

Dort steht:
* %B ⇒ base filename and path of selected output file, minus path and 
extension.
* %I ⇒ complete filename and path of the temporary input file (the 
intermediate net file).
* %O ⇒ complete filename and path of the user chosen output file.

Da wird sogar drauf verwiesen, dass der Pfad zu der net-Datei übergeben 
wird. Nun habe ich mal nachgesehen und die exportierte net-Datei ist 
tatsächlich keine XML-Datei. Keine Ahnung, was für ein Format das ist, 
aber wenn ich das Skript über KiCAD ausführe, dann erzeugt es aus der 
net-Datei zusätzlich eine XML-Datei, die dann ebenfalls im Projektordner 
liegt. Die entspricht dann auch dem beschriebenen Format in der 
Dokumentation. Verwende ich die statt der net-Datei, dann klappt es. Das 
reicht mir, ich möchte nur das Skript testweise ausführen und kann das 
mit der einen XML-Datei füttern.

Danke! Ich hoffe nun komme ich wieder allein klar.

von Terence S. (takeshi)


Lesenswert?

Inzwischen konnte ich alles umsetzen, was ich umsetzen wollte. Für die 
Nachwelt möchte ich noch hinzufügen, wie ich es angegangen habe. Es sei 
noch einmal betont, ich habe keine Ahnung von Python, mein Weg ist ggf. 
extrem umständlich.

Als Basis dient das Skript "bom_csv_grouped_by_value.py". Das liest mit 
der Methode netlist() von kicad_netlist_reader.py die Netzliste ein und 
speichert das Ergebnis in der Variable "net". Die Methode comp() enthält 
die Liste aller Bauteile.

Das Skript bom_csv_grouped_by_value.py fügt standardmäßig alle 
benutzerdfinierten Felder als Spalte nach den Standardfeldern hinzu. Das 
habe ich komplett herausgelöscht, da ich ein festes Muster an 
benutzerdefinierten Feldern habe, zum Beispiel das Feld "Hersteller". 
Auf diese möchte ich gezielt zugreifen.
1
def KnrGetFieldHersteller(self):
2
        return self.element.get("field", "name", "Hersteller")
3
4
5
kicad_netlist_reader.comp.getFieldHersteller = KnrGetFieldHersteller

Damit füge ich der Klasse comp eine Methode hinzu, die den Wert des 
Felds "Hersteller" zurückgibt.

Die Methode myEqu() habe ich um eine Abfrage des Herstellers erweitert.
1
def myEqu(self, other):
2
    result = True
3
    if self.getValue() != other.getValue(): 
4
        result = False
5
    elif self.getPartName() != other.getPartName():
6
        result = False
7
    elif self.getFootprint() != other.getFootprint():
8
        result = False
9
    elif self.getFieldHersteller() != other.getFieldHersteller():
10
        result = False
11
12
    return result
Damit wird das Bauteil in eine eigene Gruppe einsortiert, wenn der Wert, 
Bibliotheksname (getPartName), der Footprint, oder der Hersteller von 
den bisher eingelesenen Bauteilen abweicht.

In der Ausgabe der gruppierten Liste nutze ich dann ebenfalls 
self.getFieldHersteller(), um den Herstellernamen auszugeben.
1
item = 0
2
for group in grouped:
3
    del row[:]
4
    refs = ""
5
6
    for component in group:
7
        if len(refs) > 0:
8
            refs += ", "
9
        refs += component.getRef()
10
        c = component
11
12
    item += 1
13
    row.append( item )
14
    row.append( len(group) )
15
    row.append( refs )
16
    row.append( c.getValue() )
17
    row.append( c.getFieldHersteller() )
18
    row.append( c.getFootprint() )
19
20
    writerow( out, row  )
Das Ganze ist für weitere Felder eingebaut, die der Übersicht wegen in 
den Codeschnipseln nicht enthalten sind. Ich denke, das kann sich jeder 
selbst herleiten.

Und zuletzt habe ich gesehen, dass auch die Daten der Schaltplanseiten 
ausgelesen werden. Die wollte ich dann sogleich im Header der CSV-Datei 
verwursten.
Dafür zunächst wieder eine weitere Methode für eine bestehende Klasse:
1
def KnrGetSheet(self):
2
        sheet = self.design.getChild("sheet")
3
        return sheet.getChild("title_block")
4
5
6
kicad_netlist_reader.netlist.getSheet = KnrGetSheet
Es wird für jede Schaltplanseite ein Element im Array angelegt. Mit 
"title_block" nehme ich immer die Hauptseite.
Mit einer eigenen Klasse lese ich wiederum die Daten aus und speichere 
sie, da es mit nicht gelang alle Felder direkt auszulesen.
1
class sheet():
2
    def __init__(self, s=""):
3
        self.title = None
4
        self.company = None
5
        self.rev = None
6
        self.date = None
7
        self.source = None
8
        self.comment = ["", "", "", "", "", "", "", "", "", ""]
9
10
        if s != "":
11
            self.load(s)
12
13
    def load(self, s):
14
        self.title = s.get("title")
15
        self.company = s.get("company")
16
        self.rev = s.get("rev")
17
        self.date = s.get("date")
18
        self.source = s.get("source")
19
        for comment in s.getChildren('comment'):
20
            if int(comment.attributes['number']) < len(self.comment):
21
                self.comment[int(comment.attributes['number'])] = comment.attributes['value']
22
23
    def getTitle(self):
24
        return self.title
25
26
    def getCompany(self):
27
        return self.company
28
29
    def getRev(self):
30
        return self.rev
31
32
    def getDate(self):
33
        return self.date
34
35
    def getSource(self):
36
        return self.source
37
38
    def getComment(self, i=0):
39
        if i < len(self.comment):
40
            return self.comment[i]
41
        else:
42
            return self.comment[0]
Vielleicht hilft das ja jemandem sein Skript anzupassen.

Antwort schreiben

Die Angabe einer E-Mail-Adresse ist freiwillig. Wenn Sie automatisch per E-Mail über Antworten auf Ihren Beitrag informiert werden möchten, melden Sie sich bitte an.

Wichtige Regeln - erst lesen, dann posten!

  • Groß- und Kleinschreibung verwenden
  • Längeren Sourcecode nicht im Text einfügen, sondern als Dateianhang

Formatierung (mehr Informationen...)

  • [c]C-Code[/c]
  • [code]Code in anderen Sprachen, ASCII-Zeichnungen[/code]
  • [math]Formel in LaTeX-Syntax[/math]
  • [[Titel]] - Link zu Artikel
  • Verweis auf anderen Beitrag einfügen: Rechtsklick auf Beitragstitel,
    "Adresse kopieren", und in den Text einfügen




Bild automatisch verkleinern, falls nötig
Bitte das JPG-Format nur für Fotos und Scans verwenden!
Zeichnungen und Screenshots im PNG- oder
GIF-Format hochladen. Siehe Bildformate.

Mit dem Abschicken bestätigst du, die Nutzungsbedingungen anzuerkennen.