KiCAD Pcbnew マクロ Tips
KiCAD 5 の頃の内容です。
KiCAD 4.0.0 の Python マクロは Pcbnew のみサポートされており、boost Python ラッパーです。Eeschema でのマクロサポートはコードの大幅リファクタやファイルフォーマット変更後になるため、だいぶ先になるようです。
- リファレンス
- コンフィグパス
- プラグインパス
- Python シェル
- 高機能シェル
- 位置やサイズについて
- 値と参照
- モジュールの取得
- 表示状態と PCB_VISIBLE 列挙型
- 網掛けの種類
- 現在のPcbnew画面に他の板のデータを読み込み
- 正規表現に一致する参照シルクの表示状態を変更
- レイヤーを変更
- Dimensions - Text and Drawings
- フットプリントの読み込み
- ボードの回転
- GUI
Pcbnew の Python コンソールで複数行のコードを実行するには、コンテキストメニューから Paste Plus を選択してコードを貼り付けます。関数としておいてペースト、関数を実行すると便利です。
ファイルフォーマット
KiCAD 4.0 では S式 (S-expression) 形式になっており、スクリプトで扱いやすくなっています。
コード実行
Python スクリプトは pcbnew モジュールを起点として以下の二種類のスクリプトとして実行できます。
- Pcbnew - Tools - Scripting Console から実行
- 外部の Python から実行
コンフィグパス
Preferences - Configure Paths で設定できるパスは環境変数として os.environ から読み込めます。以下のような変数が利用できます。
Python シェル
Pcbnew - Tools - Scripting Console で表示される Python シェルは wxPython の pyCrust のものを使用しています。
このシェルはいくつか機能があり、shell 変数から利用できます。詳細は以下参照。
便利なものをいくつか紹介します。
高機能シェル
import wx; f = wx.py.editor.EditorNotebookFrame(title="KiCad PCB"); f.Show()
https://lists.launchpad.net/kicad-developers/msg21851.html
KiCAD 4.0 がリリースされたあとの開発版ではシェルが高機能のものに置き換わっています。シェルの起動時に自動的にスクリプトを実行できます。実行されるスクリプトは Options - Startup - Edit Startup script から編集できます。ファイルは ~/.config/kicad/PyShell_pcbnew_startup.py に保存されます。
プラグインパス
Pythonスクリプトをプラグインパスに配置しておくと自動的に読み込まれてモジュールの register() 関数が実行されます。
KICAD_PATH/scripting/plugins # Linux の場合、以下も含まれます ~/.kicad_plugins ~/.kicad/scripting/plugins
コンソールから実行したい関数の入ったファイルを入れておくとインポートして簡単に実行できます。
位置やサイズについて
内部の座標や長さなどの値は nm 単位で管理されています。そのため、1 mm は 1,000,000 倍にして設定します。
取得した座標や長さの値は 1/1,000,000 で 1 mm になります。
pcbnew モジュールには数値変換用の関数などが用意されています。
- wxPoint: 座標など内部形式
- wxPointMM: mm 単位で生成
- wxPointMils: mil 単位で生成
- wxRect: サイズなど内部形式
- wxRectMM: mm 単位
- wxRectMils: mil 単位
- wxSize: サイズなど内部形式
- wxSizeMM: mm 単位
- wxSizeMils: mil 単位
- ToMM: mm に変換
- ToMils: mil に変換
- FromMM: mm から
- FromMils: mil から
値と参照
APIから取得した値が wxPoint などの型の場合、内部の値への参照になっています。
たとえば、グリッドの原点を取得して後で元に戻したい場合は x, y の値をコピーしておく必要があります。
board = pcbnew.GetBoard() origin = board.GetGridOrigin() old_origin = pcbnew.wxPoint(origin.x, origin.y) del origin # ... ここでグリッドの原点が変更された場合、origin変数から取得できる値も変わる board.SetGridOrigin(old_origin)
ソフトによっては構造体はコピーだったりしますが Pcbnew ではそうではない様子。
表示状態と PCB_VISIBLE 列挙型
ボード上で表示と非表示を切り替えられる要素の表示状態は以下のようにして取得、変更できます。
これらに使用する値は pcbnew からアクセスできる PCB_VISIBLE 列挙型のものです。PCB_VISIBLE の値はビットシフトの値です。
https://github.com/KiCad/kicad-source-mirror/blob/master/include/layers_id_colors_and_visibility.h#L403
board = pcbnew.GetBoard() bits = board.GetVisibleElements() bits &= ~(1 << pcbnew.RATSNEST_VISIBLE) # 未接続のライン表示をオフ bits |= (1 << pcbnew.GRID_VISIBLE) # グリッドを表示 board.SetVisibleElements(bits)
網掛けの種類
ハッチの種類は CPolyLine クラスに定義されている列挙型です。
import pcbnew pcbnew.CPolyLine.DIAGONAL_EDGE # polygon/PolyLine.h # enum HATCH_STYLE { NO_HATCH, DIAGONAL_FULL, DIAGONAL_EDGE
モジュールの取得
ボード上のモジュールは GetModules() メソッドでイテレータとして取得できます。
import pcbnew for module in pcbnew.GetBoard().GetModules(): module
特定のリファレンスを持つモジュールを取得するには FindFootprintByReference() メソッドを使用できます。
module = pcbnew.GetBoard().FindFootprintByReference("SW1") if module: module.SetPosition(pcbnew.wxPointMM(100, 100))
以前は FindFootprintByReference メソッドは FindModuleByReference という名前でした。
現在のPcbnew画面に他の板のデータを読み込み
#import pcbnew board = pcbnew.GetBoard() path = "/mnt/hd/docs/kicad/Navi3_Main/data.kicad_pcb" pcbnew.IO_MGR.Load(pcbnew.IO_MGR.KICAD, path, board) #board.Save(path)
同じ場所に何かが配置されていると重なってしまいます。色々なボードをパネライズするのに使用できます。
この方法で読み込んでいくとファイル内部の Custom Track Widths のリストが重複したもので増大します。GUI から編集すると重複は取り除かれますが、コードで圧縮するには以下のようにします。
#import pcbnew board = pcbnew.GetBoard() tws = board.GetTrackWidthList() ts = list(set(tws)) tws.resize(len(ts)) for n, t in enumerate(ts.sorted()): tws[n] = t
set を使用した方法では順番が保持されないためソートしています。元の順序を保つには工夫が必要です。
正規表現に一致する参照シルクの表示状態を変更
#import pcbnew def set_visible(pattern, visibility=True): import re exp = re.compile(pattern) for module in pcbnew.GetBoard().GetModules(): if exp.match(module.GetReference()): module.Reference().SetVisible(visibility)
SR1, SR2, ... SR10 の参照を非表示にするには、set_visible("SR\d*", False) などとします。
module.GetReference() はリファレンスを文字列で返します。一方、module.Reference() はリファレンステキストオブジェクトを返します。
正規表現に一致したモジュールのみを返すジェネレータを用意しておくと便利です。
def enumerate_matched_modules(board, exp): if isinstance(exp, str): import re exp = re.compile() for module in board.GetModules(): if exp.match(module.GetReference()): yield module
これを使うと前出の set_visible 関数は以下のように書けます。
def set_visible(pattern, visibility=True): for module in enumerate_matched_modules(pcbnew.GetBoard(), pattern): module.Reference().SetVisible(visibility)
あまり複雑でない用途のために以下のような関数を作成しました。
import re def apply_matched_modules(board, exp, func): exp = re.compile() for module in board.GetModules(): if exp.match(module.GetReference()): func(module)
パターンにマッチするリファレンスのモジュールを引数として渡した関数 func を呼び出します。
func = lambda module: module.Reference().SetVisible(False) apply_matched_modules(pcbnew.GetBoard(), "SR\d*", func)
レイヤーを変更
以下のコードでは指定したレイヤーの図形描写 (外形線など) を別のレイヤーに移動します。
import pcbnew def iter_drawings_from_layer(board, layer, classes): for d in board.GetDrawings(): if d.GetLayer() == layer and isinstance(d, classes): yield d def move_drawings_to_layer(board, source_layer, dest_layer): for d in iter_drawings_from_layer(board, source_layer, pcbnew.DRAWSEGMENT): d.SetLayer(dest_layer) board = pcbnew.GetBoard() move_drawings_to_layer(board, pcbnew.Edge_Cuts, pcbnew.Eco2_User) #move_drawings_to_layer(board, pcbnew.Eco2_User, pcbnew.Edge_Cuts)
ここでは、外形線を Edge.Cuts レイヤーから Eco2.User レイヤーに移動しています。
Dimensions - Text and Drawings
Dimensions - Text and Drawings の値の取得は以下のようにします。
import wx board = pcbnew.GetBoard() settings = board.GetDesignSettings() wx.MessageBox(message=str(settings.m_ModuleTextWidth))
他の値は dir(settings) として一覧を参照してください。
フットプリントの読み込み
pcbnew モジュールには FootprintLoad 関数が用意されており、フットプリントをファイルから読み込めるように見えます。しかし、この関数は内部でレガシーフットプリント専用になっており、新しい形式のフットプリントファイル *.pretty/*.kicad_mod には使用できません。
新しい形式のフットプリントは以下のようにして自分で読み込みます。
def load_module(board, path, name): plug = pcbnew.IO_MGR.PluginFind(pcbnew.IO_MGR.KICAD) # pretty/kicad_mod 用 module = plug.FootprintLoad(path, name) pcbnew.IO_MGR.PluginRelease(plug) if board: board.Add(module) return module board = pcbnew.GetBoard() path = "/home/hoge/kicad/mods/holes.pretty" # ライブラリパス name = "M2.3" # 拡張子 .kicad_mod なしで module = load_module(board, path, name) module.SetPosition(pcbnew.wxRectMM(100, 100))
ボードの回転
ボードの Rotate メソッドは実装されていないため、呼び出すとエラーになります。自分でボード上のコンポーネントを回転させなければいけません。
def rotate(_board, _center, _angle): _angle = _angle * 10 def _rotate(name): for i in getattr(_board, name)(): i.Rotate(_center, _angle) map(_rotate, ("GetDrawings", "GetModules", "GetTracks")) for i in range(_board.GetAreaCount()): _board.GetArea(i).Rotate(_center, _angle) board = pcbnew.GetBoard() center = pcbnew.wxPointMM(100, 100) angle = 30 rotate(board, center, angle)
回転中心を指定しなければいけません。角度は時計回りで指定します。負の値を渡すと半時計回りに回転します。
GUI
マクロ用の GUI は wxPython を使うと簡単に作成できます。xwFormubuilder 3.4 以降ではウィンドウを作成する Python のコードを出力してくれるので、それを利用できます。
ダイアログなどを表示する際に wx.App の MainLoop を呼び出すと KiCAD が終了できなくなるので注意が必要です。
メッセージダイアログは以下のようにして表示できます。
import wx wx.MessageBox("Message", "Caption")
wx.DirSelector や wx.FileSelector なども便利です。
wxPython で作成したダイアログを表示したまま KiCAD を終了してもダイアログは表示されたまま残ります。KiCAD や Pcbnew の終了を知るためのコールバックなどは無いようです(BZR 5980)。
ボードオブジェクトに弱参照を設定してオブジェクトの破棄でコールバックを受けるようにしてみました。しかし、内部でボードオブジェクトが頻繁に作り替えられているため、Pcbnew のウィンドウが表示されてからすぐにボードオブジェクトは破棄されてしまいます。
weakref で無理やり監視しようとすると以下のようになりましたが、いまいちです。
import weakref import threading class MacroWindowKiller: def __init__(self, dialog): self.dialog = dialog self.ref = None self.thread = threading.Timer(3.0, self.set_ref) self.thread.start() def set_ref(self): import pcbnew board = pcbnew.GetBoard() if board and not board.IsEmpty(): self.ref = weakref.ref(pcbnew.GetBoard(), self.closed) else: self.close() def closed(self, ref): self.thread.cancel() self.thread = threading.Timer(2.0, self.set_ref) self.thread.start() def close(self): self.ref = None self.thread.cancel() self.dialog.OnClose(None) self.dialog = None