Skip to main content

Overview

KiCad provides a powerful Python scripting interface that allows you to automate board design tasks, create custom workflows, and extend functionality. The Python API is built using SWIG (Simplified Wrapper and Interface Generator) and exposes core KiCad classes and functions.

Python Environment

Initialization

The Python scripting environment is initialized when KiCad starts. The system uses the SCRIPTING class defined in /scripting/python_scripting.h:
class SCRIPTING
{
public:
    SCRIPTING();
    ~SCRIPTING();
    
    static bool IsWxAvailable();
    static bool IsModuleLoaded( std::string& aModule );
    
    enum PATH_TYPE {
        STOCK,      // Bundled scripts
        USER,       // User scripts
        THIRDPARTY  // Third-party plugins
    };
    
    static wxString PyScriptingPath( PATH_TYPE aPathType = STOCK );
    static wxString PyPluginsPath( PATH_TYPE aPathType = STOCK );
};

Thread Safety

When interacting with the Python API from C++, use the PyLOCK class to ensure thread safety:
PyLOCK lock;
// Perform Python operations here

Core API Functions

The main Python API is exposed through pcbnew_scripting_helpers.h. Here are the essential functions:

Board Management

import pcbnew

# Get the currently active board
board = pcbnew.GetBoard()

# Load a board from file
board = pcbnew.LoadBoard("/path/to/file.kicad_pcb")

# Load with specific format
board = pcbnew.LoadBoard("/path/to/file.kicad_pcb", 
                         pcbnew.IO_MGR.KICAD_SEXP)

# Create a new empty board
board = pcbnew.CreateEmptyBoard()

# Create a new board with project
filename = "/path/to/new_board.kicad_pcb"
board = pcbnew.NewBoard(filename)

# Save a board
pcbnew.SaveBoard("/path/to/output.kicad_pcb", board)

Accessing Board Elements

# Get all footprints
for footprint in board.GetFootprints():
    print(f"Reference: {footprint.GetReference()}")
    print(f"Value: {footprint.GetValue()}")
    print(f"Position: {footprint.GetPosition()}")

# Get all tracks
for track in board.GetTracks():
    print(f"Start: {track.GetStart()}")
    print(f"End: {track.GetEnd()}")
    print(f"Width: {track.GetWidth()}")
    print(f"Layer: {track.GetLayer()}")

# Get all zones (copper pours)
for i in range(board.GetAreaCount()):
    zone = board.GetArea(i)
    print(f"Net: {zone.GetNetname()}")
    print(f"Layer: {zone.GetLayer()}")

# Get all drawings
for drawing in board.GetDrawings():
    print(f"Class: {drawing.GetClass()}")
    print(f"Layer: {drawing.GetLayer()}")

Unit Conversion

KiCad internally uses nanometers. Use these functions for conversion:
# Convert from millimeters to internal units
position_x = pcbnew.FromMM(10.5)  # 10.5mm

# Convert from mils to internal units
width = pcbnew.FromMils(10)  # 10 mils

# Convert to millimeters
mm_value = pcbnew.ToMM(position_x)

# Convert to mils
mils_value = pcbnew.ToMils(width)

# Using VECTOR2I for positions
position = pcbnew.VECTOR2I_MM(10, 20)  # x=10mm, y=20mm

Modifying Board Elements

# Create a new track
track = pcbnew.PCB_TRACK(board)
track.SetStart(pcbnew.VECTOR2I_MM(10, 10))
track.SetEnd(pcbnew.VECTOR2I_MM(20, 20))
track.SetWidth(pcbnew.FromMM(0.25))
track.SetLayer(pcbnew.F_Cu)
board.Add(track)

# Create a via
via = pcbnew.PCB_VIA(board)
via.SetPosition(pcbnew.VECTOR2I_MM(15, 15))
via.SetDrill(pcbnew.FromMM(0.3))
via.SetWidth(pcbnew.FromMM(0.6))
via.SetViaType(pcbnew.VIATYPE_THROUGH)
board.Add(via)

# Create a zone
zone = pcbnew.ZONE(board)
zone.SetLayer(pcbnew.F_Cu)
zone.SetNetCode(net.GetNetCode())
board.Add(zone)

# Remove an item (proper undo/redo support)
board.RemoveNative(footprint)

Plot Generation

Generate various output files programmatically:
# Create plot controller
pctl = pcbnew.PLOT_CONTROLLER(board)
popt = pctl.GetPlotOptions()

# Configure plot options
popt.SetOutputDirectory("output/")
popt.SetPlotFrameRef(False)
popt.SetAutoScale(False)
popt.SetScale(1)
popt.SetMirror(False)
popt.SetUseGerberAttributes(True)

# Plot a single layer to PDF
pctl.SetLayer(pcbnew.F_SilkS)
pctl.OpenPlotfile("Silk", pcbnew.PLOT_FORMAT_PDF, "Assembly guide")
pctl.PlotLayer()

# Plot Gerber files
layers = [
    ("CuTop", pcbnew.F_Cu, "Top layer"),
    ("CuBottom", pcbnew.B_Cu, "Bottom layer"),
    ("PasteTop", pcbnew.F_Paste, "Paste top"),
    ("MaskTop", pcbnew.F_Mask, "Mask top"),
]

for name, layer_id, description in layers:
    pctl.SetLayer(layer_id)
    pctl.OpenPlotfile(name, pcbnew.PLOT_FORMAT_GERBER, description)
    pctl.PlotLayer()

# Always close the plot controller
pctl.ClosePlot()

Footprint Libraries

# Get all configured footprint libraries
libraries = pcbnew.GetFootprintLibraries()
for lib in libraries:
    print(f"Library: {lib}")

# Get footprints from a specific library
footprints = pcbnew.GetFootprints("Capacitor_SMD")
for fp in footprints:
    print(f"Footprint: {fp}")

DRC (Design Rule Check)

# Run DRC and save report
result = pcbnew.WriteDRCReport(
    board,
    "/path/to/drc_report.txt",
    pcbnew.EDA_UNITS_MILLIMETRES,
    True  # Report all track errors
)

Export Formats

# Export to SPECCTRA DSN format
pcbnew.ExportSpecctraDSN(board, "/path/to/output.dsn")

# Import SPECCTRA SES
pcbnew.ImportSpecctraSES(board, "/path/to/input.ses")

# Export to VRML
pcbnew.ExportVRML(
    "/path/to/output.wrl",
    1.0,      # MM to VRML unit scale
    True,     # Include unspecified components
    False,    # Include DNP components
    True,     # Export 3D files
    True,     # Use relative paths
    "3d/",    # 3D files subdirectory
    0.0,      # X reference
    0.0       # Y reference
)

Interactive Features

When running in the PCBNew GUI:
# Get user units (inches or mm)
user_units = pcbnew.GetUserUnits()
# Returns: 0 = Inches, 1 = mm, -1 if frame not set

# Get current selection
selection = pcbnew.GetCurrentSelection()
for item in selection:
    print(f"Selected: {item.GetClass()}")

# Focus view on an item
pcbnew.FocusOnItem(footprint, pcbnew.F_Cu)

# Refresh the display
pcbnew.Refresh()

# Update UI (layer manager, etc.)
pcbnew.UpdateUserInterface()

# Check if running in action plugin context
if pcbnew.IsActionRunning():
    print("Running as action plugin")

Advanced Examples

Creating Custom Footprints

def create_custom_resistor(value, ref_prefix="R"):
    """Create a custom SMD resistor footprint"""
    footprint = pcbnew.FOOTPRINT(board)
    footprint.SetReference(ref_prefix + "1")
    footprint.SetValue(value)
    
    # Create pads
    pad_size = pcbnew.VECTOR2I_MM(1.0, 1.5)
    pad_spacing = pcbnew.FromMM(2.0)
    
    for i, name in enumerate(["1", "2"]):
        pad = pcbnew.PAD(footprint)
        pad.SetSize(pad_size)
        pad.SetShape(pcbnew.PAD_SHAPE_RECT)
        pad.SetAttribute(pcbnew.PAD_ATTRIB_SMD)
        pad.SetLayerSet(pad.SMDMask())
        pad.SetPadName(name)
        
        x_pos = -pad_spacing/2 if i == 0 else pad_spacing/2
        pad.SetPosition(pcbnew.VECTOR2I(x_pos, 0))
        footprint.Add(pad)
    
    # Add silkscreen
    silk = pcbnew.PCB_SHAPE(footprint)
    silk.SetStart(pcbnew.VECTOR2I_MM(-2, -1))
    silk.SetEnd(pcbnew.VECTOR2I_MM(2, -1))
    silk.SetLayer(pcbnew.F_SilkS)
    silk.SetWidth(pcbnew.FromMM(0.15))
    footprint.Add(silk)
    
    return footprint

Batch Processing

import os
import glob

def process_all_boards(directory):
    """Process all KiCad boards in a directory"""
    for filepath in glob.glob(os.path.join(directory, "*.kicad_pcb")):
        print(f"Processing: {filepath}")
        
        board = pcbnew.LoadBoard(filepath)
        
        # Perform operations
        for footprint in board.GetFootprints():
            # Example: Update all resistor values
            if footprint.GetValue().startswith("10k"):
                footprint.SetValue("10.0k")
        
        # Save changes
        pcbnew.SaveBoard(filepath, board)

Error Handling

try:
    board = pcbnew.LoadBoard("/path/to/board.kicad_pcb")
    if board is None:
        print("Failed to load board")
except Exception as e:
    print(f"Error: {e}")
    import traceback
    traceback.print_exc()

Best Practices

  1. Always close plot controllers: Call pctl.ClosePlot() when finished
  2. Use proper units: Always convert to internal units using FromMM() or FromMils()
  3. Validate board loading: Check if LoadBoard() returns None
  4. Avoid print statements: Print statements can cause issues when running from GUI
  5. Use RemoveNative(): For proper undo/redo support when removing items
  6. Lock Python GIL: Use PyLOCK when calling from C++

Common Pitfalls

  • Unicode handling: Always encode/decode strings properly for cross-platform compatibility
  • Layer IDs: Use layer constants like F_Cu, B_Cu instead of numeric values
  • Coordinate system: KiCad uses internal units (nanometers), not millimeters
  • Memory management: Some objects are owned by the board, others must be explicitly added

See Also

Build docs developers (and LLMs) love