As a software engineer, I'm used to having fully automated every step of the deployment process and reducing the manual input as much as possible. There is just something so satisfying about clicking a button and watching it doing all the work.

But for my weather station project, this seemed not possible. When I started with this project, I used KiCad 6 with an unversioned directory on my development machine and started designing. I frequently made mistakes or broke parts of the schematic - so I needed some way of being able to roll back and tagging releases. This was also important when I ordered PCBs - without tags it is hard to link your order to the iteration of your KiCad project. So it was time to implement CI for KiCad!

All beginnings are rough

Initially, I started searching the internet for tools that would help me with this. I wanted a simple pipeline, that performs the DRC and ERC, generates images of the schematic, individual layers and a combination of all layers, a BOM (bill of material) and the production files required to send the project to a manufacturer.

At first, I didn't find any applications or tools, that could do this in a headless environment (e.g. the GitHub actions). But then, I came across the KiPlot project, which seemed to exactly do what I wanted:

All hopes are quickly shattered though, if the last commit was 7 years ago. when KiCad 7 wasn't even on the horizon. Soon after, I found KiBot, which is an active maintained fork of the original project - and it's compatible with KiCad 7!
And thanks to the maintainers of this project, it even runs in headless environments and doesn't require a dedicated GPU to render images.

Working with Git and KiCad

Next, I needed a git server to push my version controlled files. But would git even be suitable for this? Turns out the KiCad files are just text files with their own file extensions, such as .kicad_sch:

(kicad_sch
	(version 20250114)
	(generator "eeschema")
	(generator_version "9.0")
	(uuid "3e093a46-43d3-4239-969a-ffefe6fd6b27")
	(paper "A4")
	(lib_symbols
		(symbol "Connector:6P6C"
			(pin_names
				(offset 1.016)
			)
			(exclude_from_sim no)
			(in_bom yes)
			(on_board yes)
			(property "Reference" "J"
				(at -5.08 11.43 0)
				(effects
					(font
						(size 1.27 1.27)
					)
					(justify right)
				)
			)

Perfect, so I created a local repository and pushed it to GitHub. Now let's tackle the CI.

Building the CI

First, it should be noted that there are different variants & versions of the KiBot available. They are all based on Debian and only work with the matching major version of KiCad - meaning that KiBot 7 won't work with KiCad 8.

There's the auto_full images and the autoimages. The full images contain additional dependencies, such as LaTeX and Blender, which can be useful if you want certain outputs - more on this later.

To start, I simply copied the GitHub actions workflow in the documentation to my private repository:

name: Run KiBot

on:
  push:
    paths:
    - '**.sch'
    - '**.kicad_pcb'
  pull_request:
    paths:
      - '**.sch'
      - '**.kicad_pcb'

jobs:
  example:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: INTI-CMNB/KiBot@v2_k7
      with:
        config: config.kibot.yaml
        dir: output
        schema: 'schematic.sch'
        board: 'pcb.kicad_pcb'
    - name: upload results
      if: ${{ always() }}
      uses: actions/upload-artifact@v4
      with:
        name: output
        path: output

And because I didn't read the documentation at first, I didn't understand that I needed to create a configuration file too, which defines how KiBot should process the KiCad files and what outputs to generate.

I started adding a few simple outputs that I definetly need, such as the IBoM:

kiplot:
  version: 1

preflight:
  run_erc: true
  update_xml: true
  run_drc: true
  check_zone_fills: true
  ignore_unconnected: false

global:
  units: millimeters

outputs:
  - name: iBoM
    comment: Interactive HTMl BoM
    type: ibom
    dir: assembly/interactive-bill-of-materials/
    options:
      highlight_pin1: true
      checkboxes: "Placed"
      dark_mode: true

Running this workflow would start KiBot and upload the results as artifacts to the GitHub actions run - perfect!

Time to add more outputs

Since the basic concept of the workflow worked now, I started adding more outputs to the config:

kiplot:
  version: 1

preflight:
  run_erc: true
  update_xml: true
  run_drc: true
  check_zone_fills: true
  ignore_unconnected: false

global:
  units: millimeters

outputs:
  - name: iBoM
    comment: Interactive HTMl BoM
    type: ibom
    dir: assembly/interactive-bill-of-materials/
    options:
      highlight_pin1: true
      checkboxes: "Placed"
      dark_mode: true
+ - name: 'print_sch'
+   comment: "Print schematic (PDF)"
+   type: pdf_sch_print
+   dir: schema/
+   options:
+     output: full_schematic.pdf
+ - name: 'print_sch_svg'
+   type: svg_sch_print
+   dir: schema/images
+   comment: "Generate SVG from schematics"
+   options:
+     monochrome: false
+     all_pages: true
+     background_color: true
+     output: "%I.%x"
+     frame: false # do not include frame and title block
+ - name: 'print_front'
+   comment: "Print layer 1"
+   type: pdf_pcb_print
+   dir: pcb/layers/
+   options:
+     output_name: layer_1.pdf
+   layers:
+     - layer: F.Cu
+ - name: 'print_back'
+   comment: "Print layer 4"
+   type: pdf_pcb_print
+   dir: pcb/layers/
+   options:
+     output_name: layer_2.pdf
+   layers:
+     - layer: B.Cu
+ - name: basic_position_CSV
+   comment: Components position for Pick & Place
+   type: position
+   dir: positions/csv
+   options:
+     format: CSV
+     only_smd: false
+     output: '%i %f_pos_%D.%x'
+ - name: basic_render_3d_0deg_top
+   comment: 3D view from 0 degrees top
+   type: render_3d
+   dir: renders/top
+   output_id: _view
+   options:
+     output: '%f-%i%I%v-0deg.%x'
+     ray_tracing: true
+     rotate_x: 0
+     rotate_z: 0
+     download: true
+     height: 1440
+     width: 2560
+     no_virtual: true
+ - name: basic_render_3d_30deg_top
+   comment: 3D view from 30 degrees top
+   type: render_3d
+   dir: renders/top
+   output_id: _view
+   options:
+     output: '%f-%i%I%v-30deg.%x'
+     ray_tracing: true
+     rotate_x: 3
+     rotate_z: -1
+     download: true
+     height: 1440
+     width: 2560
+     no_virtual: true
+ - name: basic_render_3d_0deg_bottom
+   comment: 3D view from 0 degrees bottom
+   type: render_3d
+   dir: renders/bottom
+   output_id: _view
+   options:
+     output: '%f-%i%I%v-0deg.%x'
+     ray_tracing: true
+     rotate_x: 0
+     rotate_z: 0
+     download: true
+     view: bottom
+     height: 1440
+     width: 2560
+     no_virtual: true
+ - name: basic_render_3d_30deg_bottom
+   comment: 3D view from 30 degrees bottom
+   type: render_3d
+   dir: renders/bottom
+   output_id: _view
+   options:
+     output: '%f-%i%I%v-30deg.%x'
+     ray_tracing: true
+     rotate_x: 3
+     rotate_z: -1
+     download: true
+     view: bottom
+     height: 1440
+     width: 2560
+     no_virtual: true
+ - name: step
+   type: step
+   comment: Export the PCB as a 3D model
+   dir: 3d_models/step
+ - name: position
+   type: position
+   dir: positions/pos
+   comment: Positions for components
+ - name: virtualRealityModelLanguage
+   type: vrml
+   dir: 3d_models/vrml
+   comment: VRML 3d model
+   options:
+     download: true

This created the following file tree:

  • schema/
    • images.png
    • full_schematic.pfg
    • images/
      • sheet-1.svg
      • sheet-2.svg
  • 3d_models/
    • step/
      • model-3D.step
    • vrml/
      • model-vrml.wrl
      • shapes3D/
        • XXXX.wrl
  • assembly/
    • interactive-bill-of-materials/
      • project-ibom.html
  • pcb/
    • layers/
      • layer_1.pdf
      • layer_2.pdf
  • positions/
    • csv/
      • top_pos_project.csv
      • bottom_pos_project.csv
    • pos/
      • top_pos_project.pos
      • bottom_pos_project.pos
  • renders/
    • top/
      • project-3D_top-view-30deg.png
      • project-3D_top-view-0deg.png
    • bottom/
      • project-3D_bottom-view-30deg.png
      • project-3D_bottom-view-0deg.png

The resulting ZIP archive, uploaded to the GitHub artifacts, was about 12-16MB. But there was one issue with the file formats of some of the files - when sending them for review to friends or forums, I prefer to upload JPEG or PNG pictures instead, because PDFs aren't widely used and can embed malicious code.

So I updated my workflow file to convert SVG to PNG and PDF to PNG using Inkscape:

+     - name: Install Inkscape
+       run: sudo apt-get install -y inkscape

      - name: Checkout Code
        uses: actions/checkout@v4

      - uses: INTI-CMNB/KiBot@v2_k7
        with:
          config: config.kibot.yaml
          dir: output
          schema: 'schematic.sch'
          board: 'pcb.kicad_pcb'

+     - name: Convert SVG to PNG
+       run: |
+         for svg_file in $(find ./output/schema/images/ -type f -name "*.svg"); do
+           png_file="${svg_file%.svg}.png"
+           sudo inkscape -d 400 "$svg_file" -o "$png_file"
+         done

+     - name: Convert PDF to PNG
+       run: |
+         for pdf_file in $(find ./output/pcb/layers/ -type f -name "*.pdf"); do
+           png_file="${pdf_file%.pdf}.png"
+           sudo inkscape -d 300 "$pdf_file" -o "$png_file"
+         done

      - name: upload results
        if: ${{ always() }}
        uses: actions/upload-artifact@v4
        with:
          name: output
          path: output

Leveraging the matrix 😎

As my project grew, I start splitting modules of the PCB into their own project, since KiCad recommends to only have one PCB per project.
This also required my workflow to be able to handle multiple project files.
GitHub has a great system for this: https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/running-variations-of-jobs-in-a-workflow#using-a-matrix-strategy.

So let's implement it in the workflow:

jobs:
  run-kibot:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        module:
          - name: motherboard
            config: pcbs/motherboard/config.kibot.yaml
            dir: motherboard-outputs
            schema: pcbs/motherboard/motherboard.kicad_sch
            board: pcbs/motherboard/motherboard.kicad_pcb
          - name: module-temperature
            config: pcbs/modules/temperature/config.kibot.yaml
            dir: module-temperature-outputs
            schema: pcbs/modules/temperature/module_temperature.kicad_sch
            board: pcbs/modules/temperature/module_temperature.kicad_pcb

Then, we reference the variables in the steps that will be dynamic depending on the project:

      - name: Run KiBot
        uses: INTI-CMNB/KiBot@v2_dk9
        with:
          config: ${{ matrix.module.config }}
          dir: ${{ matrix.module.dir }}
          schema: ${{ matrix.module.schema }}
          board: ${{ matrix.module.board }}

And let's adjust the upload too, so we don't overwrite the archives with each other:

      - name: Upload Outputs
        uses: actions/upload-artifact@v4
        with:
          name: ${{ matrix.module.name }}-outputs
          path: ${{ matrix.module.dir }}

Conclusion

It seems that Git is capable enough to handle KiCad projects and they can be especially useful if you are used to software development. The GitHub workflow I presented you today shows, that automating is also possible and you could even order PCB automatically.

I think such workflows could be really useful to small projects, where only a few people are involved with the development of a PCB and software for it. Scaling up will be hard, since manually resolving merge conflicts in the KiCad files will be tedious and prone for failure. Another problem could be the runners and data space on GitHub; rendering 3D images with ray tracing takes up to 2 minutes per image on my project, because it is CPU bound. Using a graphic card would speed up the render significantly. And since every run creates between 5 and 20 MB of artifacts, storage on the free GitHub version will fill up quickly. It might be worth it to consider uploading to external storage or pay for additional storage.

Personally, I'm very satisfied with this solution and will continue to use it in my projects. What do you think? Have you used KiCad yet?

Share this post