Pythonコードベースの作図ツールBLOCCONI(ブロッコニ)を使ってみた

## BLOCCONI(ブロッコニ)とは
**BLOC**k and **CON**nect **I**llustrator、つまりブロックやコネクタを使ったお絵描きがコーディングベースで簡単にできるツールらしい。

## インストール方法
```
pip install blocconi
```
（当記事執筆時点の最新バージョンは0.1.4）

## 使用方法

- 描画に使用する関数は基本的に以下の4つ
  - blocconi.make_canvas() ・・・新規キャンバスを作成する
  - blocconi.add_block() ・・・既存キャンバスに新規ブロックを追加する
  - blocconi.add_canvas() ・・・既存キャンバスに既存キャンバスを追加する
  - blocconi.add_connector() ・・・2個の既存ブロック（orキャンバス）同士をコネクタで結ぶ

- PDFファイルへの出力は以下の関数を使う
  - blocconi.save_pdf(pdf, mychip)

## 使用例
例として、プロセッサコア、チップ、ソケット、サーバーがネスト構造になっている簡単なブロックダイアグラムを描画してみる。コードは以下の通り。
```python:test.py
from blocconi import blocconi
from collections import OrderedDict
from matplotlib.backends.backend_pdf import PdfPages

### Step 1: チップ(Chip)の描画
mychip = OrderedDict()
blocconi.make_canvas(mychip, w=250, h=250, fc="#FFFFAA", ec="#000000", linewidth=0.1, text="Chip", fontsize=4, textpos_h="left", textpos_v="top")
blocconi.add_block(mychip, "core0",  x=35,  y=35,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core0",  fontsize=3)
blocconi.add_block(mychip, "core1",  x=81,  y=35,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core1",  fontsize=3)
blocconi.add_block(mychip, "core2",  x=127, y=35,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core2",  fontsize=3)
blocconi.add_block(mychip, "core3",  x=173, y=35,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core3",  fontsize=3)
blocconi.add_block(mychip, "core4",  x=35,  y=81,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core4",  fontsize=3)
blocconi.add_block(mychip, "core5",  x=81,  y=81,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core5",  fontsize=3)
blocconi.add_block(mychip, "core6",  x=127, y=81,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core6",  fontsize=3)
blocconi.add_block(mychip, "core7",  x=173, y=81,  w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core7",  fontsize=3)
blocconi.add_block(mychip, "core8",  x=35,  y=127, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core8",  fontsize=3)
blocconi.add_block(mychip, "core9",  x=81,  y=127, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core9",  fontsize=3)
blocconi.add_block(mychip, "core10", x=127, y=127, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core10", fontsize=3)
blocconi.add_block(mychip, "core11", x=173, y=127, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core11", fontsize=3)
blocconi.add_block(mychip, "core12", x=35,  y=173, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core12", fontsize=3)
blocconi.add_block(mychip, "core13", x=81,  y=173, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core13", fontsize=3)
blocconi.add_block(mychip, "core14", x=127, y=173, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core14", fontsize=3)
blocconi.add_block(mychip, "core15", x=173, y=173, w=42,  h=42,  fc="#55FFFF", ec="#000000", linewidth=0.1, text="core15", fontsize=3)

### Step 2: ソケット(DCM:Dual Chip Module)の描画
mydcm = OrderedDict()
blocconi.make_canvas(mydcm, w=280, h=580, fc="#CCCCCC", ec="#000000", linewidth=0.1, text="DCM", fontsize=5, textpos_h="left", textpos_v="top")
blocconi.add_canvas(mydcm, "chip0", x=15, y=30, canvas=mychip)
blocconi.add_canvas(mydcm, "chip1", x=15, y=315, canvas=mychip)

### Step 3: サーバーの描画
myserver = OrderedDict()
blocconi.make_canvas(myserver, w=800, h=650, fc="#FFCCCC", ec="#000000", linewidth=0.1, text="Server", fontsize=6, textpos_h="left", textpos_v="top")
blocconi.add_canvas(myserver, "dcm0", 100, 30, canvas=mydcm)
blocconi.add_canvas(myserver, "dcm1", 420, 30, canvas=mydcm)

### Step 4: サーバーの描画（コネクタあり）
myserver_conn = OrderedDict()
blocconi.make_canvas(myserver_conn, w=800, h=650, fc="#FFCCCC", ec="#000000", linewidth=0.1, text="Server", fontsize=6, textpos_h="left", textpos_v="top")
blocconi.add_canvas(myserver_conn, "dcm0", 100, 30, canvas=mydcm)
blocconi.add_canvas(myserver_conn, "dcm1", 420, 30, canvas=mydcm)
blocconi.add_connector(myserver_conn, "conn0", "dcm0.chip0", "R", "dcm1.chip0", "L", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn, "conn1", "dcm0.chip0", "R", "dcm1.chip1", "L", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn, "conn2", "dcm0.chip1", "R", "dcm1.chip0", "L", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn, "conn3", "dcm0.chip1", "R", "dcm1.chip1", "L", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn, "conn4", "dcm0.chip0", "B", "dcm0.chip1", "T", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn, "conn5", "dcm0.chip0", "B", "dcm0.chip1", "T", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn, "conn6", "dcm1.chip0", "B", "dcm1.chip1", "T", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn, "conn7", "dcm1.chip0", "B", "dcm1.chip1", "T", color="#000000", linewidth=1)

### Step 5: サーバーの描画（コネクタあり、縦バージョン）
myserver_conn_vertical = OrderedDict()
blocconi.make_canvas(myserver_conn_vertical, w=800, h=1250, fc="#FFCCCC", ec="#000000", linewidth=0.1, text="Server", fontsize=6, textpos_h="left", textpos_v="top")
blocconi.add_canvas(myserver_conn_vertical, "dcm0", 150, 30, canvas=mydcm)
blocconi.add_canvas(myserver_conn_vertical, "dcm1", 150, 630, canvas=mydcm)
blocconi.add_connector(myserver_conn_vertical, "conn0", "dcm0.chip0", "L", "dcm1.chip0", "L", color="#000000", linewidth=1, curve=100)
blocconi.add_connector(myserver_conn_vertical, "conn1", "dcm0.chip0", "L", "dcm1.chip1", "L", color="#000000", linewidth=1, curve=150)
blocconi.add_connector(myserver_conn_vertical, "conn2", "dcm0.chip1", "L", "dcm1.chip0", "L", color="#000000", linewidth=1, curve=50)
blocconi.add_connector(myserver_conn_vertical, "conn3", "dcm0.chip1", "L", "dcm1.chip1", "L", color="#000000", linewidth=1, curve=100)
blocconi.add_connector(myserver_conn_vertical, "conn4", "dcm0.chip0", "B", "dcm0.chip1", "T", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn_vertical, "conn5", "dcm0.chip0", "B", "dcm0.chip1", "T", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn_vertical, "conn6", "dcm1.chip0", "B", "dcm1.chip1", "T", color="#000000", linewidth=1)
blocconi.add_connector(myserver_conn_vertical, "conn7", "dcm1.chip0", "B", "dcm1.chip1", "T", color="#000000", linewidth=1)

# PDFファイルとして出力
pdf = PdfPages('test.pdf')
blocconi.save_pdf(pdf, mychip)
blocconi.save_pdf(pdf, mydcm)
blocconi.save_pdf(pdf, myserver)
blocconi.save_pdf(pdf, myserver_conn)
blocconi.save_pdf(pdf, myserver_conn_vertical)
pdf.close()
```

これを以下のように実行すると5ページから成るPDFファイル(test.pdf)が出力される。
```
python ./test.py
```



### Step 1: チップ(Chip)の描画
make_canvas関数でmychipという名前のキャンバス（実体はOrderedDict）を作成し、そこに対してadd_block関数でブロック（ここでは水色の正方形）を16個貼り付けている。

以下、mychip 可視化結果（test.pdf 1ページ目）
![testpage1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544192/f397888e-c3ed-56d3-6a9d-093ad6693f31.png)

### Step 2: ソケット(DCM:Dual Chip Module)の描画
make_canvas関数でmydcmという名前のキャンバス（実体はOrderedDict）を作成し、そこに対してadd_canvas関数で既存キャンバス（mychip）を2個貼り付けている。

以下、mydcm 可視化結果（test.pdf 2ページ目）
![testpage2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544192/f4a6e3c2-6195-53d9-0e5f-0b8e0e3a5e8d.png)

### Step 3: サーバーの描画
make_canvas関数でmyserverという名前のキャンバス（実体はOrderedDict）を作成し、そこに対してadd_canvas関数で既存キャンバス（mydcm）を2個、横並びで貼り付けている。

以下、myserver 可視化結果（test.pdf 3ページ目）
![testpage3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544192/09fcb163-61a9-f21a-fe79-f4b0464f602d.png)

### Step 4: サーバーの描画（コネクタあり）
make_canvas関数でmyserver_connという名前のキャンバス（実体はOrderedDict）を作成し、そこに対してadd_canvas関数で既存キャンバス（mydcm）を2個、横並びで貼り付けている。  
さらにadd_connector関数で"dcm0"の中の"chip0"の右辺と、"dcm1"の中の"chip0"の左辺をコネクタで結んでいる。このコネクタのIDは"conn0"としている。同様に"conn1"から"conn7"を作成している。  
ブロック（orキャンバス）の1辺に複数のコネクタが接地する場合、接地箇所が等間隔になるよう自動調整される。

以下、myserver_conn 可視化結果（test.pdf 4ページ目）
![testpage4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544192/9cd5aa8b-0530-65d1-d297-353e37143e8d.png)

### Step 5: サーバーの描画（コネクタあり、縦バージョン）
make_canvas関数でmyserver_conn_verticalという名前のキャンバス（実体はOrderedDict）を作成し、そこに対してadd_canvas関数で既存キャンバス（mydcm）を2個、今度は縦並びで貼り付けている。  
さらにadd_connector関数で"dcm0"の中の"chip0"の左辺と、"dcm1"の中の"chip0"の左辺をコネクタで結んでいる。この場合直線だと困るので、引数curveに適当なカーブ度合いを設定してコネクタをカーブさせている。このコネクタのIDは"conn0"としている。同様に"conn1"から"conn7"を作成している。
以下、myserver_conn_vertical 可視化結果（test.pdf 5ページ目）
![testpage5.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/544192/7023b6cd-c8f4-6508-771e-32474fb7ee03.png)

以上。
この程度であればパワポでも十分描けそうだが、さらに大量のブロックやコネクタがあって、かつ機械的な繰り返しが多い図を作成したい場合には便利なツールかもしれない。
今後少しずつ改良していく予定らしい。