Faire pivoter une position autour d'un centre arbitraire avec l'API Python de KiCad

Publié le 28 septembre 2026 par leZ

Calculez la nouvelle position d'un point après rotation autour d'un centre quelconque — la brique de base de tout placement en réseau ou en panneau, avec KiCad.

Avant / après : la résistance R1 tournée de 90° autour du centre (100, 100) mm, de (120, 100) à (100, 80), par script
R1 avant, puis après rotation de 90° autour du centre.

Placer une grille de footprints identiques ne suffit pas toujours : parfois, il faut répartir des composants autour d’un point précis — un connecteur au centre d’une couronne de LED, une série de trous de fixation autour du centre mécanique d’une board. Cela revient à faire pivoter une position autour d’un centre qui n’est pas l’origine (0, 0). Les objets du board (ZONE, PCB_TRACK…) savent le faire eux-mêmes, comme vu dans le tuto duplication de zone — mais une position brute, elle, n’a pas toujours cette méthode sous la main.

Pourquoi une rotation manuelle

Testé : VECTOR2I — le type utilisé pour toute position en unités internes — n’a aucune méthode Rotate (AttributeError: type object 'VECTOR2I' has no attribute 'Rotate'). Seuls les objets déjà posés sur la board (FOOTPRINT, ZONE, PCB_TRACK, qui héritent de BOARD_ITEM) exposent .Rotate(centre, angle). Pour une position brute — une coordonnée de config, un point calculé avant même de créer l’objet — il faut faire le calcul soi-même.

Côté IPC, Vector2 expose directement .rotate(angle, centre) — pas besoin de formule manuelle pour un Vector2 existant. Elle reste utile pour tout ce qui n’est pas encore un Vector2 : une paire de coordonnées lue d’un fichier CSV, une liste de positions de panneau avant placement.

La formule

Le principe est toujours le même, qu’on l’écrive à la main ou qu’elle soit cachée dans une méthode : ramener le point à l’origine en soustrayant le centre, appliquer la rotation, puis retranslater :

from math import sin, cos, radians

def rotate_point(x, y, cx, cy, angle_deg):
    dx, dy = x - cx, y - cy
    a = radians(angle_deg)
    xn = dx * cos(a) + dy * sin(a)
    yn = dy * cos(a) - dx * sin(a)
    return cx + xn, cy + yn

Testé : appliquée à la main sur un point puis comparée au résultat de ZONE.Rotate() (pcbnew) et de Vector2.rotate() (IPC) sur les mêmes valeurs, cette formule donne exactement les mêmes coordonnées au nanomètre près dans les deux cas — les trois méthodes utilisent la même convention.

Appliquer la rotation à un footprint

import pcbnew
from pcbnew import VECTOR2I, FromMM, ToMM

board = pcbnew.GetBoard()
fp = board.FindFootprintByReference("R1")

cx, cy = 100, 100  # centre de rotation, en mm
angle = 90

x, y = ToMM(fp.GetPosition().x), ToMM(fp.GetPosition().y)
new_x, new_y = rotate_point(x, y, cx, cy, angle)

fp.SetPosition(VECTOR2I(FromMM(new_x), FromMM(new_y)))
fp.SetOrientationDegrees((fp.GetOrientationDegrees() - angle) % 360)

Testé : R1 initialement en (87.63, 118.11) mm, orientation 0°. Après rotation de 90° autour de (100, 100) mm : nouvelle position (118.11, 112.37) mm, orientation -90° — confirmé identique après SaveBoard/LoadBoard.

from kipy import KiCad
from kipy.geometry import Vector2, Angle
from kipy.util.units import to_mm

board = KiCad().get_board()
fp = next(f for f in board.get_footprints() if f.reference_field.text.value == "R1")

center = Vector2.from_xy_mm(100, 100)
angle = Angle.from_degrees(90)

fp.position = fp.position.rotate(angle, center)
fp.orientation = Angle.from_degrees((fp.orientation.degrees - angle.degrees) % 360)
board.update_items(fp)

Testé sur la même board, le même footprint R1 : résultat identique au pcbnew près — (118.11, 112.37) mm, orientation -90°, confirmé après relecture (get_footprints() à nouveau) et après board.save().

⚠️ Ne pas oublier l’orientation propre de l’objet

Faire pivoter uniquement la position ne suffit pas dès que l’objet a lui-même une orientation — un footprint, un texte, un pad oblong. Le point tourne bien autour du centre, mais l’objet reste affiché dans son ancienne orientation si vous oubliez la seconde ligne (SetOrientationDegrees(... - angle ...) / fp.orientation = ...), visible ci-dessus. Les deux tests plus haut confirment que la formule nouvelle_orientation = (ancienne_orientation - angle) % 360 donne le bon résultat sur les deux API — c’est la même rotation appliquée deux fois : une fois à la position, une fois à l’orientation propre de l’objet.

Essayez ci-dessous : la formule de rotation manuelle appliquée à un point situé à 80 unités du centre.

dx, dy = x - cx, y - cy
xn = dx*cos(0°) + dy*sin(0°)
yn = dy*cos(0°) - dx*sin(0°)
new_x, new_y = cx + xn, cy + yn
0
centre

Pour aller plus loin

Cette brique se généralise à toute disposition radiale — répartir N connecteurs identiques autour d’un centre en bouclant sur angle = i * (360 / N), ou repositionner un sous-circuit entier (footprints, zones, pistes) à un autre endroit d’un panneau en appliquant la même rotation à chacun de ses éléments, comme dans le tuto duplication de zone.