This progress report is published a bit earlier than usual to coincide with the release of version 1.1.0.

Python module enhancements

The python module has seen quite a lot of development this month in order to make it possible to do export tasks in continuous integration environments and other scripts.

STEP export

Adding STEP export to the python module was pretty straightforward as the STEP exporter itself doesn’t have any dependencies inside of Horizon EDA. However this means that the python module now has a hard dependency on opencascade.


Implementing running checks from the python module was a bit more involved though as it required two changes to be made beforehand:

First of all, we need to be able to return the check results. The easiest way of accomplishing this is serializing the results as JSON as there’s already code to create a python dict from a C++ json object.

Second, we need to provide something that implements IDocument as it’s required by the check cache. Up until this point the only implementation of IDocument was to be found in the Core class, that’s unsuitable for inclusion in the python module as it has indirect UI dependencies. To make it easier to write other classes that implement this interface, common parts were factored out from Core and CoreBoard to Document and DocumentBoard. All of this was enabled by the introduction of the IDocument interface a few months earlier.

With this in place, the only left things left to do were to make the BoardWrapper class that holds the Board and associated objects inherit from DocumentBoard, implement the required virtual methods and write the code that calls the checks and takes care of argument type conversion.

This makes Horizon EDA one of the few PCB layout tools that can run a headless design rule check.

Pool update

With software projects these days, it’s usual to have some sort of continuous integration that tests pull requests as they’re opened to make sure that the code still builds (and passes tests) with the PR merged. So why not have CI for the pool repository?

The first step in this is to make sure that the pool still updates without errors, i.e. all dependencies are met. To do so the python module gained support for opening and updating pools.

Docker image

To make the pool CI convenient to implement on a variety of CI platforms, the horizon python module is now available on dockerhub! The Dockerfile to build the image is quite simple thanks to Horizon EDA trying to be as maintainer-friendly as possible.

Continuous integration for the pool repository

With the above things in place, actually implementing CI for the pool repository was rather straightforward and adding more checks in the future is even easier!

Exporting 3D renderings

Since setting up continuous integration can be a bit dull at times, I decided it’s time for some fun features that nobody ever asked for but might come in handy nevertheless!

Originally, the only way to export a 3D rendering of the PCB was to take a screenshot of the 3D view which has several shortcomings:

  • Hard to reproduce the exact camera angle
  • No alpha transparency
  • Resolution limited by screen resolution
  • Impossible to automate

All of these shortcomings have been eliminated with the python module now being able to export 3D renderings. In order to reuse the existing OpenGL code and shaders, OSMesa is used to create an offscreen OpenGL context without the need for any GPU hardware as it makes use of Mesa’s software renderer. All of the UI-independent parts of Canvas3D were factored out into Canvas3DBase to avoid code duplication. The rendered image can then either be directly saved to a PNG file or retrieved as a cairo surface for further post processing.

Since exporting a single still image is boring, let’s have some fun by exporting many turning them into a silly loop, because we can!

Here’s the code:

import horizon
import numpy as np
import subprocess
import matplotlib

prj = horizon.Project('hubble-pub/hw/main/hubble.hprj')
brd = prj.open_board()
ex = brd.export_3d(720, 540)
ex.render_background = True
ex.background_top_color = matplotlib.colors.to_rgb("#333365")
ex.background_bottom_color = matplotlib.colors.to_rgb("#B3A26B")
ex.cam_distance *= 1.2

prefix = 'brd'
n = 60*5
for i, angle, h in zip(range(n), np.linspace(0, 360, n, endpoint=False), np.linspace(0, 1, n, endpoint=False)) :
    ex.cam_azimuth = angle
    ex.solder_mask_color = matplotlib.colors.hsv_to_rgb((h,1,.9))
    ex.render_to_png("%s%02d.png"%(prefix, i))
    print(i)["ffmpeg", "-y", "-r", "60", "-i", prefix+"%02d.png", "-pix_fmt", "yuv420p", prefix+".mp4"])

Roundoff polygon vertex

Unfortunately, no one jumped in to solve #322 and other users started asking for this feature, so I decided to implement it. Compared to other tools, implementing this tool involved a bit of trigonometry to be worked out beforehand in order to place the arc’s center at the correct position. This tool also makes use of the non-modal tool window infrastructure to provide a means of directly entering the radius with a live preview.

Select on work layer only

When the selection filter got revamped to also support filtering layers, the “Select only on work layer” checkbox got removed as I deemed it’s now redundant. However, users told me that it made working on boards with many layers more difficult than before. To alleviate this, the selection filter dialog grew a new checkbox “Work layer only” that makes the selection filter behave the same way as it did before as in that the selection filter specifies the objects and the work layer specifies the layer objects can be selected on.

Release 1.1.0

Last but not least, this progress report marks the release of version 1.1.0 “Blue sky”. For a summary of the changes relative to version 1.0.0, see the changelog or the last progress report posts.