Générer une règle PCB (cm et pouces) avec un script Python pcbnew
Une vraie règle PCB de 15 cm / 5 pouces dessinée par un script Python pcbnew : graduations en cuivre doré, chiffres en sérigraphie, jauges CMS et de perçage, DRC à 0.

Une règle en PCB est le goodie classique des fabricants de circuits imprimés, et c’est un excellent exercice de script : 288 objets à placer au dixième de millimètre près, impossibles à dessiner proprement à la main, mais qui tiennent en quelques boucles Python. Ce projet produit une vraie règle fabricable : 15 cm d’un côté, 5 pouces de l’autre, une jauge des tailles de boîtiers CMS et une jauge de perçage, avec un DRC à 0 erreur.
Le projet complet en vidéo, étape par étape (6 min 24, sous-titres anglais) :
Tout repose sur trois briques déjà vues dans les tutoriels : des segments (PCB_SHAPE), des textes de sérigraphie (PCB_TEXT) et des trous dessinés sur Edge.Cuts.
Le principe : une boucle et un modulo
Chaque graduation est un segment vertical. Sa longueur dépend uniquement de sa position : un grand trait tous les centimètres, un moyen tous les 5 mm, un petit sinon. Le modulo % fait tout le travail :
for i in range(151): # un trait par mm, de 0 à 15 cm
ln = 4.5 if i % 10 == 0 else 3.0 if i % 5 == 0 else 1.8
seg(board, (ZERO + i, Y0 + MARGIN), (ZERO + i, Y0 + MARGIN + ln))
L’échelle en pouces suit la même logique avec un pas de 25,4 / 16 mm et cinq longueurs de trait (pouce, 1/2, 1/4, 1/8, 1/16).
Ajustez la longueur ci-dessous pour voir combien d’objets le script crée :
Le script complet
Le script crée une carte vide, dessine tout, puis enregistre pcb_ruler.kicad_pcb. Lancez-le avec le Python fourni avec KiCad (voir l’installation), par exemple depuis la KiCad Command Prompt : python pcb_ruler.py.
import pcbnew
from pcbnew import Edge_Cuts, F_Cu, F_Mask, F_SilkS, FromMM, PCB_SHAPE, PCB_TEXT, VECTOR2I
X0, Y0, X1, Y1 = 100.0, 100.0, 262.0, 132.0 # contour de la carte, en mm
ZERO = 105.0 # x du repère 0, commun aux deux échelles
MARGIN = 0.75 # marge cuivre-bord (le DRC veut 0,5 mm + la demi-largeur du trait)
TICK_W = 0.22
def mm(x, y):
return VECTOR2I(FromMM(x), FromMM(y))
def shape(board, kind, layer, width):
s = PCB_SHAPE(board)
s.SetShape(kind)
s.SetLayer(layer)
s.SetWidth(FromMM(width))
board.Add(s)
return s
def seg(board, a, b, layer=F_Cu, w=TICK_W):
s = shape(board, pcbnew.SHAPE_T_SEGMENT, layer, w)
s.SetStart(mm(*a))
s.SetEnd(mm(*b))
return s
def circle(board, c, r, layer=Edge_Cuts, w=0.1):
s = shape(board, pcbnew.SHAPE_T_CIRCLE, layer, w)
s.SetCenter(mm(*c))
s.SetEnd(mm(c[0] + r, c[1]))
return s
def rect(board, cx, cy, w, h):
s = shape(board, pcbnew.SHAPE_T_RECTANGLE, F_Cu, 0.05)
s.SetStart(mm(cx - w / 2, cy - h / 2))
s.SetEnd(mm(cx + w / 2, cy + h / 2))
s.SetFilled(True)
return s
def text(board, s, x, y, size=1.6, bold=False):
t = PCB_TEXT(board)
t.SetText(s)
t.SetPosition(mm(x, y))
t.SetLayer(F_SilkS)
t.SetTextSize(VECTOR2I(FromMM(size), FromMM(size)))
t.SetTextThickness(FromMM(size * (0.2 if bold else 0.15)))
board.Add(t)
def outline(board, r=3.0):
for a, b in (((X0 + r, Y0), (X1 - r, Y0)), ((X1, Y0 + r), (X1, Y1 - r)),
((X1 - r, Y1), (X0 + r, Y1)), ((X0, Y1 - r), (X0, Y0 + r))):
seg(board, a, b, Edge_Cuts, 0.1)
for c, start in (((X0 + r, Y0 + r), (X0, Y0 + r)), ((X1 - r, Y0 + r), (X1 - r, Y0)),
((X1 - r, Y1 - r), (X1, Y1 - r)), ((X0 + r, Y1 - r), (X0 + r, Y1))):
a = shape(board, pcbnew.SHAPE_T_ARC, Edge_Cuts, 0.1)
a.SetCenter(mm(*c))
a.SetStart(mm(*start))
a.SetArcAngleAndEnd(pcbnew.EDA_ANGLE(90, pcbnew.DEGREES_T), True)
circle(board, (X1 - 6, (Y0 + Y1) / 2), 2.5) # trou d'accroche
def metric_scale(board):
for i in range(151): # un trait par mm, de 0 à 15 cm
ln = 4.5 if i % 10 == 0 else 3.0 if i % 5 == 0 else 1.8
seg(board, (ZERO + i, Y0 + MARGIN), (ZERO + i, Y0 + MARGIN + ln))
for c in range(16):
text(board, str(c), ZERO + 10 * c, Y0 + 6.9, 1.7)
text(board, "cm", ZERO + 154, Y0 + 6.9, 1.4)
def inch_scale(board):
for i in range(5 * 16 + 1): # traits au 1/16 de pouce, de 0 à 5 in
ln = 4.5 if i % 16 == 0 else 3.2 if i % 8 == 0 else 2.4 if i % 4 == 0 else 1.7 if i % 2 == 0 else 1.1
x = ZERO + i * 25.4 / 16
seg(board, (x, Y1 - MARGIN), (x, Y1 - MARGIN - ln))
for n in range(6):
text(board, str(n), ZERO + 25.4 * n, Y1 - 6.9, 1.7)
text(board, "inch", ZERO + 25.4 * 5 + 6, Y1 - 6.9, 1.4)
def gauges(board, ym=(Y0 + Y1) / 2):
x = 108.0
for name, w, h in [("0201", 0.6, 0.3), ("0402", 1.0, 0.5), ("0603", 1.6, 0.8),
("0805", 2.0, 1.25), ("1206", 3.2, 1.6), ("2512", 6.3, 3.2)]:
rect(board, x + w / 2, ym - 0.6, w, h) # taille réelle du boîtier CMS
text(board, name, x + w / 2, ym + 2.4, 1.0)
x += max(w, 4.0) + 2.6
x = 166.0
for d in [0.5, 0.8, 1.0, 1.5, 2.0, 3.0]:
c = (x + d / 2, ym - 0.6)
circle(board, c, d / 2) # vrai trou (Edge.Cuts)
circle(board, c, d / 2 + 0.85, F_Cu, 0.6) # anneau de cuivre, à 0,55 mm du trou
text(board, f"{d:g}", c[0], max(ym + 2.4, c[1] + d / 2 + 2.35), 1.0)
x += d + 4.2
text(board, "ScriptedPCB", 228.0, ym - 1.2, 2.2, bold=True)
text(board, "drawn with Python", 228.0, ym + 1.9, 1.1)
def expose_copper(board):
"""Mêmes formes sur F.Mask : pas de vernis sur le cuivre, qui ressort doré (finition ENIG)."""
for s in list(board.GetDrawings()):
if isinstance(s, PCB_SHAPE) and s.GetLayer() == F_Cu:
m = s.Duplicate()
m.SetLayer(F_Mask)
m.SetWidth(s.GetWidth() + FromMM(0.08))
board.Add(m)
board = pcbnew.CreateEmptyBoard()
outline(board)
metric_scale(board)
inch_scale(board)
gauges(board)
expose_copper(board)
pcbnew.SaveBoard("pcb_ruler.kicad_pcb", board)
print(len(board.GetDrawings()), "objects")

Les points qui comptent pour la fabrication
- Marge au bord de la carte. Des traits qui touchent le contour déclenchent l’erreur DRC copper_edge_clearance (0,5 mm par défaut). Avec
MARGIN = 0.75, le bord du trait (0,22 mm de large) reste à plus de 0,5 mm du contour. - Cuivre apparent. Par défaut, le vernis épargne recouvre le cuivre.
expose_copper()duplique chaque forme de cuivre surF.Mask: à ces endroits, le vernis est retiré et le cuivre reçoit la finition de surface (dorée en ENIG). - Vrais trous. Un cercle sur
Edge.Cutsà l’intérieur de la carte devient une découpe. L’anneau de cuivre autour est volontairement éloigné de 0,55 mm du trou pour rester dans la règle de marge au bord. - Échelle exacte. Toutes les cotes sont en millimètres et converties par
FromMM: la règle fabriquée mesure juste, à la tolérance du fabricant près.
Vérifiez le résultat sans ouvrir l’interface avec kicad-cli pcb drc --exit-code-violations pcb_ruler.kicad_pcb (code de sortie 0 = aucune violation), comme expliqué dans le tuto headless.
Vernis noir et rendu 3D
La couleur du vernis se règle dans Fichiers → Options CI… → Stackup du Circuit Imprimé → Couches Physiques, colonne Couleur (choisissez Black pour les deux couches de vernis et White pour la sérigraphie), et la Finition cuivre sur ENIG. Pour le rendu 3D en ligne de commande, demandez à kicad-cli d’utiliser ces couleurs :
kicad-cli pcb render --use-board-stackup-colors --quality high -o regle.png pcb_ruler.kicad_pcb

La couleur réelle se choisit au moment de la commande chez le fabricant ; celle de l’empilement sert surtout à l’aperçu.
Pour aller plus loin
Changez range(151) pour une règle de 10, 20 ou 30 cm (pensez à agrandir le contour), ajoutez votre logo, ou remplacez la jauge CMS par des empreintes réelles chargées avec FootprintLoad, comme dans le tuto de placement.