Basics of Design CC Blog Research & Design Hub

Create 3D Models of Components and Board Outlines in KiCad

Written by Matthew Oppenheim

Using the CadQuery Library for Python

KiCad software is popular for creating printed circuit boards. In this article, Matthew steps us through how he uses 3D computer-aided design software called CadQuery—a library for Python—to create 3D models of components and draw complex PCB board outlines through code.


  • How can you use CadQuery to create 3D models of components?
  • How can you draw complex PCB board outlines through code?
  • What is KiCad?
  • How does CadQuery work with KiCad?
  • KiCad
  • CadQuery
  • Python
  • KiCad | www.kicad.org

KiCad [1] has rapidly become one of the most popular open-source and freely available packages for schematic entry and PCB layout. Every design package has its own quirks and characteristics. I have spent more than a few hours searching for solutions to problems with the software.

In this article, I present two solutions solved by using 3D computer-assisted design (CAD). The CAD package presented is CadQuery [2], which is a library for Python. This library enables us to create complex CAD objects through code. I hope that others will benefit from and improve on these solutions. If you do have suggestions please search out my blog [3] and leave a post.

The two solutions I present in this article are how I use CadQuery to: 1) create 3D models of components, and 2) draw complex PCB board outlines. I present how I used CadQuery to create a 3D model of a pogo pin (a spring-loaded electrical connector mechanism), and to create a complex board outline that can be imported into KiCad.

CadQuery is one of the few CAD packages that models objects by using code to describe the object. The final object is rendered using a program called CQ-editor. CadQuery uses the Python programming language. There are two advantages of using code to create the model. First, we can create a template for the component or outline that can easily be modified by changing the values of named variables, to create similar items. Second, writing code fits with how many embedded design folks think.

I don’t present CadQuery as being better than other CAD packages. Rather, I think of it as one that fits with the way I think, and I suspect will also work for other people who program embedded devices. The software allows me to create 2D and 3D objects using the same methodology that I use to create firmware. Break down the design into the smallest components that are practical to work with, and re-use as many of these components as possible.

For the project I discuss here, I used KiCad V. 7.0.9 running on Debian V. 11 (“bullseye”) and CadQuery V. 0.2.0.

INSTALLING CADQUERY

There are two components of CadQuery to install—the Python CadQuery library, and the CQ-editor [4]. CQ-editor is a visual editor that displays the 3D model created by our Python code.

To install the CadQuery Python library use this command:

pip3 install –pre cadquery –user

We use –pre flag to install V. 2 of CadQuery. Leaving out this flag installs v.1 of CadQuery, which does not have all the features that we need. The –user flag installs the CadQuery library to your user directory instead of the system installation.

Sometimes downloading one Python library also causes other Python libraries to be updated, which can end up creating dependency issues with other libraries. This is called “dependency hell.” Been there. Installing Python libraries to your user directory makes it easier to undo this mess and not corrupt your system installation of Python. The cleanest method to avoid dependency issues between libraries in Python is to use a virtual environment to run different projects using the venv command. I am too slack to stick to doing this. I just tidy up the Python libraries in my home directory, whenever I have problems.

We install the CQ-editor from its GitHub site [4]. At the time of this writing (December, 2023), we need to install the unstable build to get all the necessary features. Download the file for your operating system from the CQ-editor “releases/tag/nightly” page [5]. Running the file you downloaded should bring up the CQ-editor. This shows both the Python code and the 3D model created by your code, when you press the “play” icon at the top of the screen.

We can load and edit code directly in the CQ-editor, but I rarely do any editing in it. If you load your Python code into CQ-editor then select “File, Automatic reload and preview,” you can edit the Python code in your personal favourite editor. I use Vim, since Vi is what we had when I started out using Unix and, we considered ourselves lucky. Each time that the Python code is saved, CQ-editor reloads the code and displays an updated 3D object. Error messages are displayed in the “Current traceback” window, in the unlikely event you make a mistake.

Now that we have the necessary CadQuery components installed and running, it is time to start using them.

CREATING A 3D MODEL OF A POGO PIN

Having a 3D model of our final PCB and all the components on it has a couple of uses. We can use the model to test that our PCB will fit inside a case, or to design a case around the PCB. If we have several boards to fit together, then 3D models of each one will help with testing that the boards connect correctly.

Many manufacturers now supply 3D models for their components that can be installed into KiCad. However, the spring-loaded pogo pin I spec’d for a recent project did not have a 3D model, so I decided to draw one up from scratch. The pogo pin I chose is made by Xinyangze, part number YZ08015063P-02, and is available from JLCPCB Manufacturing [6].

With 3D CAD, the trick is to see what simple shapes can be assembled to make the final complex shape. If we look at the datasheet for the pogo pin [7], we can see that it comprises three co-axial pins with a washer that sits on top of the PCB to support the pogo pin. The washer can be considered as a short co-axial pin as well. Each pin has a different curvature or filleting at one end. The washer can be treated as a co-axial pin with no filleting. So, I created a method called “pin” that creates a cylinder with a fillet at one end. By calling this method four times—each time with the parameters for a different part of the pogo pin—we can build up the complete model.

The protruding pin at the base of the pogo pin has the fillet at the bottom, while the other pin objects that make up the complete model have a fillet at the top, apart from the washer shape that sits on top of the PCB, which has no fillet. I mirrored the bottom-most pin before adding it to the assembly, so that the fillet is at the bottom. The washer part that sits on the PCB does not have any fillet. However, CadQuery does not like it when we assign a fillet of value zero. I gave the washer a very small fillet, and the part rendered. All we then need to do is to place each of the component pins in the correct location, and combine them into a single assembly.

Listing 1 shows the Python code that generates the 3D model of my pogo pin. The dimensions of each of the four pins that comprise the complete assembly are defined. The pin method is called four times, each time with the parameters for the pin that is being added to the assembly. As the pins are added to the assembly, I keep track of where they are added with the stack_height variable.

LISTING 1
Python script to create a 3D model of a pogo pin in CadQuery.

import cadquery as cqfrom cadquery import exporters# base of pin that goes through PCBbase_pin_diameter = 0.7base_pin_fillet = 0.05base_pin_length = 0.8# body pin, that goes above washerbody_pin_diameter = 1.5# body_pin_taper not specified on data sheet, guesstimatedbody_pin_fillet = 0.2body_pin_length = 3.5# top pintop_pin_diameter = 0.9top_pin_fillet = 0.45top_pin_length = 1.5# washer shape that sits on PCBwasher_diameter = 2.0# fractional fillet as a value of 0 causes the code to crashwasher_fillet = 0.001washer_height = 0.5def pin(diameter, length, fillet_radius):    ‘’’ Create a pin ‘’’    pin = cq.Workplane(‘XY’)\            .circle(diameter/2)\            .extrude(length)\            .faces(“+Z”).fillet(fillet_radius)    return pinpogo_pin = cq.Assembly()# create the base pin that fits into the PCBbase_pin = pin(base_pin_diameter, base_pin_length, base_pin_fillet)# mirror this pin so that the fillet is at the basebase_pin = base_pin.mirror(mirrorPlane=”XY”, basePointVector=(0, 0, base_pin_length/2))pogo_pin.add(base_pin)# keep track of high the assembly isstack_height=base_pin_length# add the washer that sits on the PCBpogo_pin.add(pin(washer_diameter, washer_height, washer_fillet),\        loc=cq.Location(cq.Vector(0, 0, stack_height)))stack_height+=washer_height# create the body pin that fits on top of the ‘washer’pogo_pin.add(pin(body_pin_diameter, body_pin_length, body_pin_fillet),\        loc=cq.Location(cq.Vector(0, 0, stack_height)))stack_height+=body_pin_length# create the top pinpogo_pin.add(pin(top_pin_diameter, top_pin_length, top_pin_fillet),\        loc=cq.Location(cq.Vector(0, 0, stack_height)))# render objectshow_object(pogo_pin)# save step filepogo_pin.save(‘pogo_pin.step’)
LISTING 2
Python script to create a 2D board outline to import into the KiCad Edge.Cuts layer.

import cadquery as cq# square buttons on micro:bitmicrobit_button_size = 6.2microbit_x = 51.8microbit_y = 42.0microbit_z = 1.6microbit_corner_radius = 3.0# distance from edge of each side of microbit to nearest button edge# measured from board, supplied drawings to not exactly matchbutton_x = 2.8# distance from top edge of microbit to buttonbutton_y = 18.0# button height above the boardbutton_z = 4.0# 4mm holeshole_dia = 4.0# y offset from middle of boardhole_y = -(microbit_y/2-7)# hole names, reading from left to righthole_names = [‘0’, ‘1’, ‘2’, ‘3V’,’GND’]# x offset from left edgeholes= [(-21.5,hole_y), (-11.5,hole_y), (0,hole_y), (11.5,hole_y), (21.5,hole_y)]holes_2= [(-21.5,hole_y), (-11.5,hole_y), (0,hole_y), (11.5,hole_y), (21.5,hole_y)]def microbit_outline(x, y, z):    # microbit outline and 4mm holes    microbit_outline = cq.Workplane(‘XY’)\        .rect(x, y)\        .extrude(z)\        .edges(‘|Z’)\        .fillet(microbit_corner_radius)        # .pushPoints(holes).circle(hole_dia/2).cutThruAll()    return microbit_outlinebutton_separation = microbit_x - 2 * (button_x+microbit_button_size/2)microbit = (    cq.Workplane(‘XY’)\        .box(microbit_x, microbit_y, microbit_z)        .edges(‘|Z’)        .fillet(microbit_corner_radius)        .pushPoints(holes).circle(hole_dia/2).cutThruAll()        .pushPoints([(button_separation/2, 0), (-button_separation/2, 0)])        .rect(microbit_button_size, microbit_button_size).cutThruAll()        )# Render the solidshow_object(microbit)cq.exporters.export(microbit, “microbit_2d.dxf”, cq.exporters.ExportTypes.DXF)

The step file for the 3D model that is created using the script can be imported into KiCad and associated with the component. This allows the component to be displayed in the PCB editor. A screenshot of the CQ-editor showing the pogo pin is given in Figure 1.

FIGURE 1
CQ-editor screenshot showing the pogo pin.
FIGURE 1
CQ-editor screenshot showing the pogo pin.
CREATING A COMPLEX BOARD OUTLINE

Drawing complex PCB board outlines in KiCad can be tricky. KiCad enables us to draw straight edges and curves in the ”Edge.Cuts” layer to define the edge of the board. A simple semi-circular curve connecting two lines can be produced by selecting the lines, right-clicking and using the “fillet” function. However, the shape of this curve cannot be changed, only the radius. This is a clumsy way of creating a complex board outline. One feature of KiCad that has me saying naughty words occurs when I run the design rule check (DRC) in the PCB layout package and I am told that the board outline has gaps. I can’t see where these gaps are, and I am careful to snap the ends of each straight edge and curve that the board outline contains to the same grid point, so that they connect correctly. I resort to repeatedly shuffling around the components of the board edge at increasing zoom factors, until I pass the DRC or my wife turns the computer off, because she would really like to get to sleep without listening to my ongoing “conversation” with KiCad.

My solution is to draw the PCB outline using CadQuery, then insert this outline into KiCad. Using code, we can create a complex outline using equations, which are impractical to draw in KiCad directly. CadQuery can save this outline as a DXF file. However, this DXF file cannot be imported directly into KiCad.

To get the DXF file created by CadQuery into KiCad, I use the free and open-source CAD package, FreeCAD [8], with a plug-in. The plug-in does some magic on the DXF file, and saves it as a KiCad board or directly inserts it into your PCB board file in the outline layer. With practice this only takes a couple of minutes. It would be nice to automate this through a script. I have to weigh the time taken to write and debug this script against the fact I don’t use this procedure more than once each couple of weeks.

CadQuery exports SVG format files as well as DXF format. One question is why not export an SVG file and import this into the Edge.Cuts layer of KiCad? I tried this, and the SVG file will import into the Edge.Cuts layer. However, the dimensions of the board outline are wrong. For whatever reason, the size information is lost and the SVG outline is scaled down in size.

This can be fixed with some simple arithmetic. Measure a known dimension in KiCad, and apply a scaling factor to the imported SVG file. However, getting an accurate dimension in KiCad can be a little tricky, since the resolution of your measurement is constrained to the grid resolution that you chose for your PCB design. It is easy to forget this and get an incorrect measurement, resulting in an incorrect scaling factor and an outline that is not what you intended it to be. Going the DXF route is, therefore, a more robust solution.

First of all, we need to design the board outline in CadQuery. For this example I am copying the board shape of the micro:bit educational board (Micro:bit Educational Foundation [9]), with cut-outs where the buttons on the micro:bit are, and the 4mm holes that allow the board to be used with 4mm banana plugs. This is for a project to create a board that will fit on top of a micro:bit board. The signals and power from the micro:bit are connected to this daughter-board, using the pogo pins that I modeled earlier. The boards connect using screws, nuts, and stand-offs through the 4mm holes of the micro:bit and the new PCB.

FIGURE 2
PCB board outline created with CadQuery.
FIGURE 2
PCB board outline created with CadQuery.

Listing 2 shows the CadQuery code used to create the 3D model of this board and save this model as a DXF file. A screenshot taken from CQ-editor showing the board outline that this code creates is given in Figure 2. The final 3D view of the board rendered in KiCad is shown in Figure 3a and Figure 3b. Note the lovely pogo pins created from the code presented earlier in this article.

Figure 3a
FIGURE 3B
3D model of a KiCad PCB top (a)
3D model of a KiCad PCB base (b).
FIGURE 3B
3D model of a KiCad PCB top (a)
3D model of a KiCad PCB base (b).

I installed FreeCAD V. 0.19. Instructions on how to install this software are on the FreeCAD website [8]. We need to install a plug-in to convert the DXF file to the KiCad PCB board format. On the top menu bar of FreeCAD, go to “Tools, Addon manager, Workbenches,” and select “kicadStepUpMod.” Once this is installed, restart FreeCAD. Don’t think you can get away with not restarting FreeCAD. You really have to do it. Guess how I learned this?

After restarting FreeCAD, select the “KiCadStepUp” plug-in from the drop-down list in the middle of the top menu bar. This list defaults to “Start” on start-up. A new menu bar with many incomprehensible symbols appears. We will use two of these, as shown in Figure 4.

FIGURE 4
FreeCAD screenshot showing KiCadStepUp menu options.
FIGURE 4
FreeCAD screenshot showing KiCadStepUp menu options.

Import the DXF file created by your CadQuery script using “File, Import.”

You need to select all the parts of the DXF file that you want to use as your board outline. These parts all need to be on the same plane. Selecting the parts might be tedious if there are a lot of them. I’m not familiar enough with FreeCAD to know if there is a “select all” method for a single plane. I press down on the control key and select all the segments that I need for the board outline. Once I figure out a slicker way of doing this I’ll write it up as a blog post on my website.

Now click on the “ksu 2D object (or DXF) to Sketch” button on the toolbar that was created when you installed the plug-in. (See the annotated screenshot of the menu bar in Figure 4.)

Scroll to the bottom of the Model window on the left in the Combo View pane. Select the sketch that has just been created. Figure 5 is an example screenshot of this.

FIGURE 5
FreeCAD screenshot showing which file to export to KiCad.
FIGURE 5
FreeCAD screenshot showing which file to export to KiCad.

Click on the “ksu Push Sketch to PCB Edge” button in the menu bar. This option is indicated in Figure 4.

You will get the option to push the outline directly to your KiCad PCB file, into the outline layer. I have not tried this. I’ve always selected the option to create a separate KiCad format PCB file.

Import the newly created file into your KiCad PCB using the menu option File, Append Board in the KiCad PCB editor.

Run DRC to check this is all good. Access the 3D viewer using the menu options “View, 3D Viewer,” and finally, enjoy the glory of a fully joined-up outline.

FINAL THOUGHTS

The key to solving any problem is to break the problem down into small pieces, each of which can be solved. This is how we build our circuits and write our firmware. CadQuery enabled me to solve a couple of the stages of creating a PCB using the same mindset. I welcome any suggestions on how to improve solving the two problems I’ve presented here. 

RESOURCES
KiCad | www.kicad.org

REFERENCES
[1]  https://www.kicad.org
[2]  https://github.com/CadQuery/cadquery
[3]  https://mattoppenheim.com/
[4]  https://github.com/CadQuery/CQ-editor
[5]  https://github.com/CadQuery/CQ-editor/releases
[6]  https://jlcpcb.com/ partdetail/Xinyangze-YZ08015063P02/C5157301
[7]  https://datasheet.lcsc.com/lcsc/2209201730_Xinyangze-YZ08015063P-02_C5157301.pdf
[8]  https://www.freecad.org
[9]  https://microbit.org

Code and Supporting Files

PUBLISHED IN CIRCUIT CELLAR MAGAZINE • MARCH 2024 #404 – Get a PDF of the issue

Keep up-to-date with our FREE Weekly Newsletter!

Don't miss out on upcoming issues of Circuit Cellar.


Note: We’ve made the Dec 2022 issue of Circuit Cellar available as a free sample issue. In it, you’ll find a rich variety of the kinds of articles and information that exemplify a typical issue of the current magazine.

— ADVERTISMENT—

Advertise Here

Would you like to write for Circuit Cellar? We are always accepting articles/posts from the technical community. Get in touch with us and let's discuss your ideas.

Sponsor this Article

Supporting Companies

Upcoming Events


Copyright © KCK Media Corp.
All Rights Reserved

Copyright © 2026 KCK Media Corp.

Create 3D Models of Components and Board Outlines in KiCad

by Matthew Oppenheim time to read: 13 min