The adventure of rendering SVGs with KiCad pcbnew python api.
What I wanted to do was pretty simple, at least I thought, I wanted to replicate the comportment of the "Export → SVG" button in KiCad.
What I wanted to do was pretty simple, at least I thought, I wanted to replicate the comportment of the "Export → SVG" button in KiCad.
Pretty easy no? Load the board, create a plot controller change a few options, and we're ready. And this where it went haywire:
|
|
Multiple issues with this:
SetColorMode
set to True
, there isn't any colour hereThis, is in retrospect, pretty easy to fix but a nightmare to find.
The pcbnew lib exposes the ComputeBoundingBox
function and (luckily) the EDA_RECT
class.
To get the center point for the board, we can do:
|
|
Then we can enable the use of the auxiliary origin and set it to the origin of the pcb bounding box:
|
|
First issue solved
Thanks to the bounding box from the first issue, we know exactly what size the output should, so let's change it.
According to the documentation, the BOARD
class has a function called GetPageSettings
and I know from reading the source code, that GetPageSettings
returns a pointer to a PAGE_INFO
class that holds the page size. So one would think that we could do something like this (at least that's what is done in the GUI code when you click "Export → SVG"):
|
|
But no, no, no, the PAGE_INFO
class doesn't have a swig proxy in the pcbnew file, so you actually can't modify it. You can use it, but cannot call or access anything inside (the only statements posing an issue are SetWidthMils
and SetHeightMils
)
Traceback (most recent call last):
File ".\main.py", line 60, in generate
k = generator.generate(options.canvas, options.color, **options.options)
File ".\generators\spotify\main.py", line 281, in generate svgs = pcb2svg.generate_svg_from_gerber_and_drill(kicad_pcb_file.name, theme=color)
File ".\tools\kicad\pcb2svg.py", line 281, in generate_svg_from_gerber_and_drill
currpageInfo.SetWidthMils(int(pcb_bounding_box.GetWidth() / pcbnew.IU_PER_MILS)) AttributeError: 'SwigPyObject' object has no attribute 'SetWidthMils'
So, how do you bypass that? By modifying the exported SVG, of course.
Looking at an SVG generated by the GUI, we can deduce the values that would actually need replacing from the bounding box
python IU_PER_MM = pcbnew.IU_PER_MILS / 2.54 * 1000 VIEW_BOX_DIVIDER = 100 # Why that value? Wish I knew
new_svg_attributes = {
"width": f"{round(pcb_bounding_box.GetWidth() / IU_PER_MM, 5)}cm",
"height": f"{round(pcb_bounding_box.GetHeight() / IU_PER_MM, 5)}cm",
"viewBox": f"0 0 {int(pcb_bounding_box.GetWidth() / VIEW_BOX_DIVIDER)} {int(pcb_bounding_box.GetHeight() / VIEW_BOX_DIVIDER)}",
}
Thereafter, we can use python XML library to properly edit the attributes of the SVG (i.e., not a brute force .replace
)
|
|
And success we now have a black & white SVG
You would think that using SetColorMode(True)
would actually enable colours. Why would that be the case, though? Why would it be easy?
As with the first issue, it's in retrospect a pretty easy fix.
Turns out that you need to tell the thing to use the damn default colour scheme because why a default colour scheme would be used by default.
|
|
Success we now have ugly colours but at least they are there
Once again, one would think that we could use the color_settings
var to change the colours, but that would be too easy. The COLOR_SETTINGS
class doesn't have a swig proxy in the pcbnew, either. Great.
So to modify the colours, we need to pull out the big guns and use .replace
on the SVG to get colour that are somewhat OK.
After a bit of poking around in the SVG, the colour are these (didn't bother with the silkscreen for now):
|
|
We can then use a bit of code to replace everything:
|
|
You can note that, as the mask layer isn't actually a mask layer but only a layer, I need to invert top_mask
and top_layer
. It isn't great but works for now as I wanted to avoid dealing with it any more.
And finally, we have our SVG (rendered with the black ENIG theme)
This may appear elementary, but doing things without any proper docs and with APIs that lacks some features can take quite a while. Getting to this state took at somewhere between 8 and 10 hours of reading the documentation, reading the source code and trial & error.
I have great respect for the KiCad devs and while it might look a lot like a rant towards KiCad, it is not.
I realize that generating SVG from the python API probably isn't the principal use of the lib.
In my opinion, an API layer should either expose everything that need to access what the API is for or should not exist, no in-between. A few parts don't have a swig proxy and are a pain to get around. I would also have expected a better doc of the api from such a big project.
But then again, KiCad is free and open-source you can't rant at the devs (or anything really). If I had more time, I could implement it and ask to merge it upstream and solve it for everyone, however I do have a life.
Want to chat about this article? Just post a message down here. Chat is powered by giscus and all discussions can be found here: TheStaticTurtle/blog-comments