My Foray Into KiCad – APIs and CLIs

Jump straight to to the code.

I have not avoided KiCad in any way, I just don’t use ECAD software much, and when I do, it’s largely for work where we have always used Altium. This year we got some work done by a contract firm. They mentioned they could use Altium, but it was their preference to use KiCad instead. We said that was fine. And all was good in the world.

Several months later and we want to produce something they’ve designed. They provide us all the KiCad files as well as manufacturing outputs, but for us to release this internally, we need to have it formatted in a specific way to match our QMS expected templates. This should be easy though, just apply a template and we’re good. KiCad has a Drawing Sheet Editor which lets you create worksheets, and apply them to your schematics and boards, that sounds like what we need.

And that is true. For schematics, I was able to create a template exactly matching our Altium one, and easily generate documents matching our existing documents. PCBAs on the other hand was a whole different ball game. This ‘issue’ is largely self-inflicted. Our release documentation requires a lot of things to be present in a 3-page PDF that aren’t necessarily that important. Mainly because it’s just duplicating data that is already contained in the Gerbers and other manufacturing output. This is just placed in a PDF that makes it easy for a human to get a quick overview of the design. It has its place.

Example output of a generated PDF sheet that includes a 3D image generated by the KiCad CLI (details)

Included in this document are: a 3d-isometric view of the board; a PCB stack-up table; drill map; and a single page with all the layers shown. And Altium’s templates makes this trivial to generate for new designs. KiCad does not. And that’s not KiCad’s fault, it shouldn’t set out to just replicate everything Altium does. These aren’t things that are needed in an ECAD software, it’s just, we had it, and now we don’t.

So what can we do? We can apply a worksheet template to the PCB editor. But it can only really output each layer on a separate page. It’s a start. We can also have custom layers, and put whatever we want on those custom layers, which can be output to separate pages. What can’t we do? Mainly, the Drawing Sheet Editor (as far as I could tell) doesn’t let you template these additional layers’ contents. It can’t generate 3d images of the board, it can’t automatically populate a drill map or board stack-up table. KiCad does support most of these things, just not in an automated template kind of way, so if I wanted to to do this for every new design we release, it’s a lot of time consuming, error-prone work, to get the desired output.

What KiCad does have though is a newly revamped API, as well as several CLI options. And what can we do with these? Well, we can get a long way to matching our Altium implementation.

We can take 3 new layers, and pretend each of them is the desired output page we want. We can use the CLI to do things like generate drill maps and 3D views of the board. We can apply template sheets to the project, and we can generate the final files. We can then use the API to add the elements to our 3 layers; get information from the board and automatically generate table outputs. And then we can piece this together with a PDF editor library. Do we get exactly the same output as with Altium? No, but we get pretty close, and it has everything we need.

You can see a semi-stripped down version of this on GitHub.

Organizing a Round Robin – ish

Moving to Ottawa I was lucky enough to fall into a great group of squash players at my local club. A bunch of the players had a standing appointment every week, and we’d rock up and play some squash. It was a bit haphazard as to who you would play, but you always got a match. One of the members eventually started pre-scheduling the matches for everyone, using one of various online round-robin calculators, this way it ensured you got a match, and, for the most part, meant you got to play someone different each week.

The online, free, round-robin generating software isn’t that great though. It lets you plug in a bunch of names, and it generates one full round robin, which was then applied to each week. But not everyone is available every week, so you end up manually making changes, and before you know it, chaos reigns, and you’re playing the same person multiple times.

To be fair, this isn’t a major reason to do what I did, but it was certainly a contributor. One of the great things about squash in Ottawa, is they use on an online ranking system called Rankenstein. It’s used by pretty much everyone in the greater Ottawa region, and it’s used both for scheduling and tracking the City League, as well as tournament records, and challenge matches. With this data available to me, a desire for a better scheduling system, and a node.js learning side-quest, I was set.

Most of the system was fairly straight-forward to develop. There’s nothing particularly fancy about what I did. I would schedule a month’s worth of matches at time, soliciting everyone’s availability, and only scheduling a match for those people who expect to be there. I also have access to the Rankenstein data, so I can see when last everyone played each other, whether part of our weekly forays, or in a separate context.

With this information I had everything I needed to schedule matches for everyone, every week, with the data in-hand to reduce the likelihood of a repeat match. I just had one piece out-standing, how do you actually figure out a round-robin schedule.

If you’re just creating a simple round-robin for X players, it’s pretty straightforward. If you have 6 players, you just pair player 1 up with each player week after week, then player 2, etc. If you have an odd number of players, you just add a “Bye” player. Then you get a simple round robin like this:

week 1week 2week 3week 4week 5
1v21v31v41v51v6
3v42v62v52v32v4
5v64v53v64v63v5

But this isn’t the only solution. Yes, you can switch the week’s around, but there’s also different combinations of ways you can have people playing. For example, if we just looked at the first week, if we say 1 is going to play 2, you can also have 3v5 (with 4v6) and 3v6 (with 4v5).

And it was with a pen and paper as I was trying to figure out these various combinations that I noticed an underlying pattern. If we have 2 players, there’s only 1 possible combination. If we have 3, we include a bye player, so we have 4 players. With 4 players, there is also only one set of options. 1v2 & 3v4, 1v3 & 2v4, 1v4 & 2v3. With 5 or 6 players, just pull out the two players and have them play each other, and the last 4 play the above solution. So we have 1v2, 1v3, 1v4, 1v5, 1v6, and for each of those, the other 4 play the above solution. If we then went to 2v3, and tried to generate the rest, we’d end up repeating one already created, so it’s not necessary.

So for a 4 player pool, we have 3 potential sessions. For a 6 player pool, we have 3 potential sessions for each 4 player pool, which is left over from the 5 player possible matches, for a total of 15 matches.

If we pushed that up to 8 players, we do the same thing, we pull out 1v2, 1v3, 1v4, 1v5, 1v6, 1v7 and 1v8, then recursively call the function for the remaining 6 players. Which gives us 7 possibilities, times the original 15, for a total of 105. so for an even number of players N greater than 4, the number of potential sessions is a geometric progression. 6 players = (6-1)*3. 8 players = (8-1)*(6-1)*3. 10 players = (10-1)*(8-1)*(6-1)*3.

This can get quite large; for 12 players it would be 10,395 sessions, each with 6 matches. Fortunately the computer is doing all the hard work, and we’re usually limited to a max of 14 players. I’m also only scheduling one week at a time, so I don’t have to figure out perfect rounds for n-1 weeks.

So we get to implement recursion!

function get_all_possible_sessions(players)
{
    if (players.length % 2 != 0 || players.length < 2)
    {
        logger.warn("get_all_sessions - number of players not even or less than 2");
        return [new Session("",[],[new Match(-1, -1)])];
    }
    else if (players.length == 2)
    {
        return [new Session([new Match(players[0], players[1])])];
    }
    else if (players.length == 4)
    {
        let allSessions= [];
        allSessions.push(new Session([new Match(players[0], players[1]), new Match(players[2], players[3])]));
        allSessions.push(new Session([new Match(players[0], players[2]), new Match(players[1], players[3])]));
        allSessions.push(new Session([new Match(players[0], players[3]), new Match(players[1], players[2])]));
        return allSessions;
    }
    else
    {
        let allSessions= [];
        for (let i = 1; i < players.length; i ++)
        {
            let match = new Match(players[0], players[i]);
            // let tempPlayers = structuredClone(players); only introduced in v17
            let tempPlayers = JSON.parse(JSON.stringify(players))
            tempPlayers.splice(tempPlayers.indexOf(players[0]),1);
            tempPlayers.splice(tempPlayers.indexOf(players[i]),1);

            let sessions = get_all_possible_sessions(tempPlayers);
            sessions.forEach(session => {
                session.matches.push(match);
            });
            allSessions.push(...sessions);
        }
        return allSessions;
    }
}

Before I looked at how to program this particular topic, I had spent some frustrating time with ChatGPT, trying to get it to give me something useful, but I really struggled to get it to understand what I wanted. No manner of, “that’s not what I asked for, I actually want…” seemed to be able to get me what I want. It’s possible that newer models will perform better, but in the end I’m happy I was able to figure out a straight-forward solution myself.

Once we have all the session and match combinations, we update each one specifying when last those two players played each (or are scheduled to play each other). We also pass in information related to scheduled byes, to try avoid the same player being scheduled a bye more than once in a set.

Put it all together and you can share a link to your group with something like this:

You’ve got the matches for each week, along with how many days since the players last played each other, and a historical win ratio.

There’s a login field for whoever’s administrating, and they get the same interface with additional options to make changes, schedule new sessions etc. They can manually override matches if someone can’t make it anymore. You can also manually schedule some games, and then let the system generate the remaining matches, based on players not yet scheduled.

It’s been almost a year since I started using this for the scheduling, and it’s worked pretty well. I’ve made small improvements and adjustments over the year, and it works pretty well for what it is. There’s definitely more complex stuff I’d like to do withe project, but the amount of time to implement it just doesn’t warrant the small benefit I’d experience from it, so for now this project is pretty much complete.

The project is written in Node.js, using Express and ejs templates. My biggest issue is that at the time, my web-host only let me run Node.js 14, although it looks like they now support up to 22. The next project is in the works, time to tick React off my development checklist.