はじめに
この記事は「大規模ソフトウェアを手探る」という実験のレポートの子記事です。親記事はこちら
KiCADのIssueに挑戦したことを記事にしました。
取り組んだIssue
取り組んだIssueはこちら
バスエントリのサイズをGUIで変更可能にするというIssueです。
KiCADの回路図エディタにはバスという複数の配線をまとめる機能があり、そのバスと配線を繋ぐ部品がバスエントリです。
下の回路図の青い縦線がバス、緑の横線が配線、そしてそれらをつなぐ斜めの線がバスエントリです。

KiCAD8.0では一度設置したバスエントリのサイズは変更できません。最初から小さいサイズのバスエントリを設置することはできるのですが、(bus_entry (size 1.27 1.27))という文字列をコピー&ペーストしなければなりません。
バスエントリは斜めの線であること、サイズの変更が簡単にできないことの2点が原因でバスを使った回路図は汚くなりがちです。
この問題をどうするかについてはここで議論されているのですが、上記のコピペが紹介されてる以外は根本的な解決には至っておりません。
そこでGUIでサイズを変更になるような機能改善に挑戦しました。
コードの解析
Issueに対してMaintainerの方が "dialog_wire_bus_properties" を探ると良いとのコメントがされていました。それに則り、KiCADのソースコードの中でdialog_wire_bus_propertiesファイルを探してみたところ以下のような5つのファイルが見つかりました。
コードと実際のKiCADの機能や表示を見比べて解析したところ、これらのファイルは以下のような配線やバス、バスエントリをダブルクリックした時に出てくるダイアログボックスの実装を担っているようでした。
具体的にはdialog_wire_bus_properties_base.h
とdialog_wire_bus_properties_base.cpp
がダイアログボックスの構造や表示部分の実装、そしてdialog_wire_bus_properties.h
とdialog_wire_bus_properties.cpp
がダイアログボックスの入出力の処理を行っているようでした。
このダイアログボックスを修正してバスエントリサイズを指定できるようにすることを目標としました。
コードの修正1
すでに実装されているJunction sizeの変更に関する実装を参考にして、バスエントリのサイズを指定できる入力ボックスを以下のように実装しました。
まず、dialog_wire_bus_properties_base.cpp
を修正してバスエントリサイズの入力欄を作成しました。引数の数字の意味はよくわからなかったので何度もビルドして調整しました。
m_busEntrySizeLabel = new wxStaticText( this, wxID_ANY, _("Bus entry size:"), wxDefaultPosition, wxDefaultSize, 0 );
m_busEntrySizeLabel->Wrap( -1 );
gbSizer1->Add( m_busEntrySizeLabel, wxGBPosition( 4, 0 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxRIGHT, 5 );
m_busEntrySizeCtrl = new wxTextCtrl( this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0 );
m_busEntrySizeCtrl->SetMinSize( wxSize( 146,-1 ) );
gbSizer1->Add( m_busEntrySizeCtrl, wxGBPosition( 4, 1 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxEXPAND, 5 );
m_busEntrySizeUnits = new wxStaticText( this, wxID_ANY, _("unit"), wxDefaultPosition, wxDefaultSize, 0 );
m_busEntrySizeUnits->Wrap( -1 );
gbSizer1->Add( m_busEntrySizeUnits, wxGBPosition( 4, 2 ), wxGBSpan( 1, 1 ), wxALIGN_CENTER_VERTICAL|wxLEFT, 3 );
次にdialog_wire_bus_properties.cpp
を修正して入力されたサイズを反映させる処理を実装しました。
(bus_entry (size 1.27 1.27))の文字列をコピペすると小さいサイズのバスエントリを追加できるという情報に基づいてbus_entry
の文字列を検索してKiCADの実装を探ったところ、バスエントリのタイプはSCH_BUS_WIRE_ENTRY_T
で、サイズの情報はVECTOR2I
として持っていました。
Junction sizeの実装を真似ながら、TransferDataToWindow
関数で現在のバスエントリサイズを取得してダイアログに表示する処理、TransferDataFromWindow
関数で入力された数字をbusEntry->SetSize()
に渡してサイズの変更の処理を行うようにしました。
今回編集しているダイアログは、ワイヤやバスと共通のものなので条件分岐が非常に多いです。選択したアイテムがバスエントリではない場合、m_busEntrySize.SetValue( INDETERMINATE_ACTION )
として薄く表示して入力できないようにしています。
dialog_wire_bus_properties.cppの変更箇所
DIALOG_WIRE_BUS_PROPERTIES::DIALOG_WIRE_BUS_PROPERTIES( SCH_EDIT_FRAME* aParent,
std::deque<SCH_ITEM*>& aItems ) :
DIALOG_WIRE_BUS_PROPERTIES_BASE( aParent ),
m_frame( aParent ),
m_items( aItems ),
m_wireWidth( aParent, m_staticTextWidth, m_lineWidth, m_staticWidthUnits ),
m_junctionSize( aParent, m_dotSizeLabel, m_dotSizeCtrl, m_dotSizeUnits ),
m_busEntrySize( aParent, m_busEntrySizeLabel, m_busEntrySizeCtrl, m_busEntrySizeUnits )// ここを追加
{
... // 省略
}
bool DIALOG_WIRE_BUS_PROPERTIES::TransferDataToWindow()
{
VECTOR2I busEntrySize( -1, -1 );
for( SCH_ITEM* item : m_items )
{
if( item->HasLineStroke() )
{
stroke = item->GetStroke();
color = stroke.GetColor();
if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
{
SCH_BUS_ENTRY_BASE* busEntry = static_cast<SCH_BUS_ENTRY_BASE*>( item );
busEntrySize = busEntry->GetSize();
}
}
... //省略
}
if( std::all_of( m_items.begin(), m_items.end(),
[&]( const SCH_ITEM* item )
{
return item->Type() != SCH_BUS_WIRE_ENTRY_T
|| static_cast<const SCH_BUS_ENTRY_BASE*>( item )->GetSize() == busEntrySize;
} ) )
{
if( busEntrySize.x >=0 && busEntrySize.y >=0 )
m_busEntrySize.SetValue( busEntrySize.x );
else
{
// No bus entry found in selected items: disable m_busEntrySize
m_busEntrySize.Enable( false );
m_busEntrySize.SetValue( INDETERMINATE_ACTION );
}
}
else
{
m_busEntrySize.SetValue( INDETERMINATE_ACTION );
}
}
bool DIALOG_WIRE_BUS_PROPERTIES::TransferDataFromWindow()
{
for( SCH_ITEM* item : m_items )
{
if( item->HasLineStroke() )
{
if (item->Type() == SCH_BUS_WIRE_ENTRY_T )
{
SCH_BUS_ENTRY_BASE* busEntry = static_cast<SCH_BUS_ENTRY_BASE*>( item );
if( !m_busEntrySize.IsIndeterminate() )
{
busEntry->SetSize(VECTOR2I(m_busEntrySize.GetValue(), m_busEntrySize.GetValue()));
}
}
}
}
}
これらの変更によりダイアログボックスにBus entry sizeの入力欄が追加されて大きさを変えられるようになりました。
マージリクエストの作成と返信
GUIでバスエントリを変更する機能を実装できたので、マージリクエストの作成をしました。
するとOwnerの方から以下のような返信が...
大まかに約すと、
- ダイアログの編集はwxFormbuilderを使って、".fpb"ファイルを作成してね
- バスエントリサイズは50mil単位に制限されなけれえばならないよ。あらゆる値を受け付ける今の実装ではバグの温床になってしまうよ
- マージリクエストをするときはrebaseしてね
- clang-formatを使ってね
というものでした。3,4はKiCADの規約を読んでなかった筆者が悪いのですが、1,2は初耳です。
どうやらKiCADのGUIはwxFormbuilderというツールで作成されており、そこで*_base.cpp
や*_base.h
を自動生成しているようです。そのため*.fpb
ファイルをwxFormbuilderで修正しない限り、次の自動生成の際に今回修正した*_base.cpp
が上書きされてしまいます。
また、バスエントリサイズは50mil単位にしないといけないというのは、別の数字(例えば35mil)でも表示はできるものの、バスと配線を繋ぐバスエントリサイズとしての役割を果たすかどうかはわからないとのことでした。KiCADの回路図エディタの標準のグリッドサイズが50milのため、バスや配線が置けるのは50mil間隔であり、バスエントリのサイズが変な数字だと上手く接続できないという理由のようです。
コードの修正2
ダイアログの入力欄を数字入力ではなく、選択肢とすることにしました。
wxFormBuilder
まずwxFormBuilderで一回目の修正の時には使わなかったdialog_wire_bus_properties.fpb
ファイルを開きます。(wxFormBuilderのインストールはこちら)
画面の左にあるObject treeにテキストや入力欄を追加して、そして細かい位置を右側にあるpropertiesで修正します。
修正後の写真は以下の通り。このままfile > Generate Code とするだけで_base.h
と_base.cpp
が自動生成されます。
一回目の修正時に直接_base.cpp
を触って、パラメータの意味がわからないまま、ビルドしては調整、ビルドしては調整と繰り返していたのがこんな簡単にできるなんて...
wxFormbulder、便利すぎます。
選択式に変更
バスエントリサイズについて100mils
か50mils
かを選ぶ形にしました。
入力式ではなく選択式にした結果、m_junctionSizeに使われている便利なオブジェクトが使えず、KiCADでの単位の実装を探る必要が生じました。
GUIでのデバッガの使い方がわからなかったため、unitという文字列を検索して出てきた関数の定義元を辿るなどして地道に探りました。
その結果、KiCADの内部では独自の単位が使われているということが分かりました。
milからKiCADの内部単位に直すにはEDA_UNIT_UTILS::UI::FromUserUnit
関数を使用する必要があります。TransferDataToWindow
関数で現在のバスエントリのサイズを確認して100mils
か50mils
のどちらかを表示し、TransferDataFromWindow
関数で入力に応じてsetSize
をしています。
dialog_wire_bus_properties.cppの変更箇所
DIALOG_WIRE_BUS_PROPERTIES::DIALOG_WIRE_BUS_PROPERTIES( SCH_EDIT_FRAME* aParent,
std::deque<SCH_ITEM*>& aItems ) :
DIALOG_WIRE_BUS_PROPERTIES_BASE( aParent ),
m_frame( aParent ),
m_items( aItems ),
m_wireWidth( aParent, m_staticTextWidth, m_lineWidth, m_staticWidthUnits ),
m_junctionSize( aParent, m_dotSizeLabel, m_dotSizeCtrl, m_dotSizeUnits )
{
m_busEntrySizeCombo->Append( "100mils" );
m_busEntrySizeCombo->Append( "50mils" );
... // 省略
}
bool DIALOG_WIRE_BUS_PROPERTIES::TransferDataToWindow()
{
VECTOR2I busEntrySize( -1, -1 );
for( SCH_ITEM* item : m_items )
{
if( item->HasLineStroke() )
{
stroke = item->GetStroke();
color = stroke.GetColor();
if( item->Type() == SCH_BUS_WIRE_ENTRY_T )
{
SCH_BUS_ENTRY_BASE* busEntry = static_cast<SCH_BUS_ENTRY_BASE*>( item );
busEntrySize = busEntry->GetSize();
}
}
... //省略
}
if( std::all_of( m_items.begin(), m_items.end(),
[&]( const SCH_ITEM* item )
{
return item->Type() != SCH_BUS_WIRE_ENTRY_T
|| static_cast<const SCH_BUS_ENTRY_BASE*>( item )->GetSize()
== busEntrySize;
} ) )
{
if( busEntrySize.x >= 0 && busEntrySize.y >= 0 )
{
if( busEntrySize.x
== EDA_UNIT_UTILS::UI::FromUserUnit( schIUScale, EDA_UNITS::MILS, 50 ) )
m_busEntrySizeCombo->SetStringSelection( "50mils" );
else if( busEntrySize.x
== EDA_UNIT_UTILS::UI::FromUserUnit( schIUScale, EDA_UNITS::MILS, 100 ) )
m_busEntrySizeCombo->SetStringSelection( "100mils" );
else
{
m_busEntrySizeCombo->SetStringSelection( INDETERMINATE_STYLE );
}
}
else
{
m_busEntrySizeLabel->Enable( false );
m_busEntrySizeCombo->Enable( false );
m_busEntrySizeCombo->SetStringSelection( INDETERMINATE_STYLE );
}
}
else
{
m_busEntrySizeCombo->SetStringSelection( INDETERMINATE_STYLE );
}
}
bool DIALOG_WIRE_BUS_PROPERTIES::TransferDataFromWindow()
{
for( SCH_ITEM* item : m_items )
{
if( item->HasLineStroke() )
{
// 省略
if( item->Type() == SCH_BUS_WIRE_ENTRY_T
&& m_busEntrySizeCombo->GetStringSelection() != INDETERMINATE_STYLE )
{
if( m_busEntrySizeCombo->GetStringSelection() == "100mils" )
static_cast<SCH_BUS_ENTRY_BASE*>( item )->SetSize( VECTOR2I(
EDA_UNIT_UTILS::UI::FromUserUnit( schIUScale, EDA_UNITS::MILS, 100 ),
EDA_UNIT_UTILS::UI::FromUserUnit( schIUScale, EDA_UNITS::MILS,
100 ) ) );
else if( m_busEntrySizeCombo->GetStringSelection() == "50mils" )
static_cast<SCH_BUS_ENTRY_BASE*>( item )->SetSize( VECTOR2I(
EDA_UNIT_UTILS::UI::FromUserUnit( schIUScale, EDA_UNITS::MILS, 50 ),
EDA_UNIT_UTILS::UI::FromUserUnit( schIUScale, EDA_UNITS::MILS, 50 ) ) );
}
}
}
}
}
修正後のダイアログは以下の通りです。Bus entry sizeの入力欄を押すと50milsか100milsかの選択肢が表示され、どちらかを選ぶことができます。これで大きさを50mil単位に制限できました。
マージリクエスト ~2度目の挑戦~
masterからrebaseもして、clang-formatも使い、万全の状態でpushしました。
するとこんな返信が...
mm単位を使う人にとっては不便では?とのことでした。
多くの回路素子のピンの間隔が100milであるように、回路の世界では未だヤードポンド法のmil「ミル」やin「インチ」が使われています。そのため、KiCADには以下のように単位をすぐに切り替えられる機能があります。
mmを愛用する人にとってはmilで書かれると戸惑うかもしれません。とは言っても回路図なんて物理的な長さと全く関係ないし、そもそもニ択なんだからmilでもいいだろと個人的には思うのですが、 ありがたいことにメンションまでされたのでユーザーが使っている単位を参照して選択肢を変えることにしました。
コードの修正3
wxFormBuilderでbus entry sizeの選択窓の右にテキストを追加し、そこに単位を表示することにしました。
色々探った結果、現在ユーザーが使っている単位系はGetUserUnits
関数で取得できるとわかったので、単位系に応じて数字を単位のmsgと100mils,50milsにそれぞれ対応するbusEntryLargeSize
、busEntrySmallSize
を更新して表示するようにしました。
また、KiCADの文字列の機能として_( "a string to translate" )
とすると翻訳する文字列として扱われると教わったので使用しています。
G_WIRE_BUS_PROPERTIES::TransferDataToWindow()
{
... //省略
int largeSizeIndex = m_busEntrySizeCombo->FindString( busEntryLargeSize );
int smallSizeIndex = m_busEntrySizeCombo->FindString( busEntrySmallSize );
if( largeSizeIndex != wxNOT_FOUND )
m_busEntrySizeCombo->Delete( largeSizeIndex );
if( smallSizeIndex != wxNOT_FOUND )
m_busEntrySizeCombo->Delete( smallSizeIndex );
wxString msg;
switch( GetUserUnits() )
{
case EDA_UNITS::INCHES:
msg = _( "inches" );
busEntryLargeSize = "0.1";
busEntrySmallSize = "0.05";
break;
case EDA_UNITS::MILS:
msg = _( "mils" );
busEntryLargeSize = "100";
busEntrySmallSize = "50";
break;
case EDA_UNITS::MILLIMETRES:
msg = _( "mm" );
busEntryLargeSize = "2.54";
busEntrySmallSize = "1.27";
break;
default:
msg = _( "Units" );
busEntryLargeSize = _( "Large" );
busEntrySmallSize = _( "Small" );
break;
}
m_busEntrySizeUnits->SetLabel( msg );
m_busEntrySizeCombo->Append( busEntryLargeSize );
m_busEntrySizeCombo->Append( busEntrySmallSize );
}
デモ動画がこちらです。GUIでバスエントリのサイズを変更でき、表示が現在の単位系に合わせて変更されています。
マージリクエスト ~3度目の挑戦~
pullしてマージされることを期待していたのですが3週間経っても音沙汰がありません。一度催促のコメントをしたのですが無視されています。もしかしたら偉い方々の方針と齟齬があってマージされていないのかもしれませんが真相はわかりません。
終わりに
締まらない終わり方となってしまいましたが、KiCADを手探って機能追加できたことは事実です。貴重な経験となりました。KiCADの次のバージョンのリリース時にマージされていたりしないかなと淡い期待をしています。
他に取り組んだIssueについても親記事に載っているのでそちらもぜひご覧ください。