3進CPUの製作において、基板の中に同じパターンを何度も繰り返し配置することがあったのですが、階層シートとPythonによるリファレンスの一括変更で効率化できたので備忘録として残しておきます。
なおこの記事は KiCad 8.0 のバージョンに基づいて作成しています。バージョンによっては仕様が変わる可能性があるのでご注意ください。
目次
基板例
例として3進CPU用の加算器基板を用いて説明します。
この基板には全加算器が4個と半加算器が1個乗っています。全加算器には半加算器が2個含まれるので、半加算器は合計で9個あることになります。
このように同じパターンを何度も利用する場合は、回路図上でもPCBエディタ上でも逐一配置するのは大変なので、KiCadの機能でできるだけ再利用できるようにしましょう。
回路図編
階層シートとは
KiCadには階層シートという機能があり、簡単に言うと回路図をパッケージ化して再利用することができます。
例えば、加算器基板のトップシートは以下のようになっていて、とても300近い数の部品があるようには見えません。
しかし図中のHA0, FA1~4というのが階層シートで別の回路図にリンクされています。
全加算器と半加算器の階層シートはそれぞれ以下のようになっています。
基板全体の階層構造は以下のようになっていて合計で14のページがありますが、ファイルとしてはトップ、全加算器、半加算器の3つしかありません。
また同一のファイルを参照しているので修正があったときに全体に反映されるのもありがたい点です。
階層シートの使い方
回路図に階層シートを追加するには画面右側のメニューの中にある「シートを追加」を押します。
回路図の適当な場所に配置したら以下のダイアログが出てくるので、名前とファイル名を指定します。
ファイル名を指定する場所で既存のファイルを選択することも可能です。
作成された階層シートはダブルクリックで開けます。
階層シートに回路図を作成したら、外部との入出力になる部分に階層ラベルを追加します。
階層ラベルを追加し終わったら上位のシートに戻って、「シートピンをインポート」ボタンを押してから作成した階層シートをクリックすると、先ほど追加した階層ラベルが順にインポートされるので使いやすい位置に配置しましょう。
出来上がった階層シートはコピペすることで簡単に複製することができます。
階層シートのコピペ、削除を繰り返しているうちに、ページ番号が飛び飛びになったり、孫シートの順番が違っている場合があるかと思います。
そういった場合はトップシートで全部の階層シートをまとめて切り取り、貼り付けで再配置を何回かやるとページ番号がきれいにそろいます。
アノテーション
アノテーションをするときには、シート番号ごとにナンバリングするように設定しましょう。
これによってあとでリファレンスを一括変更するのがやりやすくなります。
PCBレイアウト編
アノテーションが完了したらPCBエディタを開き、まずはシート1つ分の部品をいつも通りに配置します。
ちなみにこちらは半加算器1つ分の回路です。
これをコピペすることにより複製します。
Pythonコンソールの使用
次に複製した部品のリファレンスを変更します。部品を一つずつダブルクリックして編集することも可能ですが、Pythonコンソールを使用することでまとめて変更することが可能です。
Pythonコンソールは画面上部のツールアイコンから開くことができます。
コンソールを開いたら次のプログラムを入力します。
コピペする場合は全部まとめてではなく関数単位でする必要があります。ファイルをインポートする方法は記事の後ろで書いています。
このプログラムは id研さんのブログ記事 を参考にさせていただきました。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
import pcbnew import re def _get_selected_modules(): modules = pcbnew.GetBoard().Footprints() return filter(lambda m: m.IsSelected(), modules) def add_reference_number_selected(number): selected = _get_selected_modules() for module in selected: refdes = module.GetReference() res = re.match(r'([A-Z]*)([0-9]*)', refdes) new_refdes = f'{res.groups()[0]}{number + int(res.groups()[1])}' print('rename: ' + refdes + ' -> ' + new_refdes) module.SetReference(new_refdes) |
add_reference_number_seleced()
はエディタ上で選択状態にある部品のリファレンス番号に引数で指定した数字を足す関数になっています。
例えば、先ほど複製した半加算器の部品を選択した状態で、コンソールから add_reference_number_seleced(300)
を実行するとこのようにリファレンス番号を一括で変更できます。
フットプリントの再リンク
リファレンスを変更しただけでは回路図のシンボルとの対応関係は更新されていません。
そのため、配線をする前に「回路図から基板を更新」を開いて更新します。このとき、「リファレンス指定子に基づいて回路図のシンボルとフットプリントを再リンク」の項目にチェックを入れます。
これでネットリストの情報も更新されたので配線を進めていけます。
Tips
ここからは雑多な内容を備忘録として書いておきます。
ファイルのインポート
コンソールを開くたびに関数を手動で定義するのは面倒なので、ファイルにまとめておいてインポートしたいです。
注意が必要なのは Python が動いている環境で、以下のようにカレントディレクトリを確認すると、プロジェクトのディレクトリ等ではなくKiCadのインストールされたディレクトリとなっていました。
>>> os.getcwd()
'C:\\Program Files\\KiCad\\8.0'
ここのディレクトリにファイルを置くとインポートできるようになります。
あるいは、ファイルを別の場所に置いておきたい場合は、sysモジュールからパスを追加することで可能です。
例えば、D:\Ternary\kicad\library
というディレクトリに util.py
というファイルを置いた場合、以下のようにするとインポートすることが可能です。
>>> import sys
>>> sys.path.append("D:\\Ternary\\kicad\\library")
>>> import util
サジェスチョンのエラーの対応
KiCad8.0 の問題だと思うのですが、コンソールにプログラムを入力していると、関数の「(」を入力したときに以下のようなエラーが出てしまいます。
これはサジェスチョンの機能の例外処理に問題があるようです。
解決法を探していたところ、こちらに対応策が載っていました。
以下のコードをファイルに保存しておき、そのファイルをインポートすることで例外処理を施した関数に上書きされるので、エラーが発生しなくなります。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273 import inspectimport wx.py.introspectfrom wx.py.introspect import getRoot, getBaseObject# Workaround for https://gitlab.com/kicad/code/kicad/-/issues/14155def getCallTip(command="", locals=None):"""For a command, return a tuple of object name, argspec, tip text.The call tip information will be based on the locals namespace."""calltip = ("", "", "") # object name, argspec, tip text.# Get the proper chunk of code from the command.root = getRoot(command, terminator="(")try:if locals is not None:obj = eval(root, locals)else:obj = eval(root)except:return calltipname = ""obj, dropSelf = getBaseObject(obj)try:name = obj.__name__except AttributeError:passtip1 = ""argspec = ""if inspect.isbuiltin(obj):# Builtin functions don't have an argspec that we can get.passelif inspect.isfunction(obj):# tip1 is a string like: "getCallTip(command='', locals=None)"argspec = str(inspect.signature(obj))if dropSelf:# The first parameter to a method is a reference to an# instance, usually coded as "self", and is usually passed# automatically by Python; therefore we want to drop it.temp = argspec.split(",")if len(temp) == 1: # No other arguments.argspec = "()"elif temp[0][:2] == "(*": # first param is like *args, not selfpasselse: # Drop the first argument.argspec = "(" + ",".join(temp[1:]).lstrip()tip1 = name + argspecdoc = ""if callable(obj):try:doc = inspect.getdoc(obj)except:passif doc:# tip2 is the first separated line of the docstring, like:# "Return call tip text for a command."# tip3 is the rest of the docstring, like:# "The call tip information will be based on ... <snip>firstline = doc.split("\n")[0].lstrip()if tip1 == firstline or firstline[: len(name) + 1] == name + "(":tip1 = ""else:tip1 += "\n\n"docpieces = doc.split("\n\n")tip2 = docpieces[0]tip3 = "\n\n".join(docpieces[1:])tip = "%s%s\n\n%s" % (tip1, tip2, tip3)else:tip = tip1calltip = (name, argspec[1:-1], tip.strip())return calltipwx.py.introspect.getCallTip = getCallTip
シルクのフォントサイズを一括設定
フォントサイズをまとめて変更したかったので、次のようなプログラムを書きました。
1 2 3 4 5 6 7 |
def set_font_size_selected(width, height, thickness): selected = _get_selected_modules() for module in selected: t = module.Reference() t.SetTextWidth(int(width * 1000000)) t.SetTextHeight(int(height * 1000000)) t.SetTextThickness(int(thickness * 1000000)) |
_get_selected_modules()
は先のリファレンス変更のプログラム中に入っています。
フォントに限らず様々な要素を Python から編集することが可能なようです。どのような関数が使えるのかは、こちらのリファレンスを参照してください。
リファレンスの記号を変更
もともと NMOS の BSS138 と PMOS の BSS84 にどちらも Q というリファレンス記号が割り当てられていたのですが、これをそれぞれ N と P に変更したいと思いました。
回路図側では、もともと使っていたシンボルライブラリのコピーを作成してリファレンス記号を変更したオリジナルのシンボルライブラリを作成しました。
回路図上で部品を右クリックして「シンボルを変更」を押すと以下のような画面が出てくるので、値が一致するシンボルをまとめて変更することができます。
回路図のシンボルを変更してアノテーションした後は、すでに部品は配置済みの部品のリファレンスを更新しないといけませんでした。
もともとのリファレンス番号は NMOS と PMOS の両方を合算した通し番号がついていましたが、それぞれ単独での通し番号に変わるので、単純に数字を足し引きするだけではできません。
そこで以下のプログラムを作成しました。PMOS を全部集めてソートし、ページ番号ごとにグループ分けして、その中で通し番号を再度付け直しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
import pcbnew import itertools board = pcbnew.GetBoard() footprints = board.GetFootprints() pmos = list(filter(lambda f: f.GetValue() == "BSS84", footprints)) pmos.sort(key=lambda q: int(q.GetReference()[1:])) def page(q): ref = q.GetReference() return int(ref[1:]) // 100 groups = [(k, list(g)) for k, g in itertools.groupby(pmos, key=page)] for p, qs in groups: for i in range(len(qs)): qs[i].SetReference(f'P{p}{i+1:02}') |
是非皆さんも Python コンソールを利用して KiCad をちょっと便利に使ってみましょう!