スクリプトで楽しむBlender：3Dデータを簡単に作り出すテクニック

## はじめに

3Dプリンターで色々作って遊んでます。造形するデータは3DCGソフトのBlenderを使っています。
Blenderで3Dデータを作る人は珍しくないと思いますが、私のようにスクリプトで3Dデータを作る人は少ないかもと思い、その方法を紹介することにしました。

（私が3Dプリンターで作るものは留め具や固定台だったりとカクカクしたものが多いです。そーいうものを色々作っていくうちに、独特なスクリプトになりました。）


## スクリプトを使う利点とは

私が3Dデータの作成にプログラムを使う理由は**便利だから**。

例えば、大量の穴が開いた板を作るときにプログラムのforループを使うことができます。
穴の大きさや位置を変えたい場合は、プログラムのパラメータを変更し再実行するだけです。

これはスクリプト実行時の様子です。プログラムを実行するだけで穴あきの板ができます。プログラムさえ書いてしまえば、実行するだけでデータが完成します（楽！）

https://www.youtube.com/watch?v=K2nLRPWj7lM&t=25s

余談ですが、この穴あき板はレゴと互換性のあるラズベリーパイの固定板として作ったものです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/a1f4334f-07d6-d113-560c-e84ed50a8c69.png)

## Blenderでスクリプトを使う方法

Blenderを起動し、メニュー一覧にある`Scripting`をクリックするとスクリプト用のウィンドウが開きます。`+New`ボタンをクリックするとスクリプトを記載するウィンドウが開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/21e04763-28d2-bd7b-b0c1-acfc55fe203a.png)

スクリプトを記載するウィンドウに今回紹介したスクリプトなどを張り付けて実行すると3Dデータが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/1461e1ff-e10b-ce3b-dc6c-be93627df3f8.png)

## スクリプトについて

ここからスクリプトの紹介です。

### ソースはこちら

スクリプトのソースはここにあります。順次公開していってます。

https://github.com/zgw426/Blender_Python-script

スクリプトは改善を繰り返しており、ファイル名の数値が大きいほど新しい改善が進んでいます。ここでは、現時点の最新である`07_MDFボードの留め具.txt`のスクリプトをベースに解説します。

## スクリプトの構成

このスクリプトは独特な書き方になっているので、まずは構成を紹介します。
このスクリプトは大きく3つの区分にわかれます。区分（１）と（３）は基本的に変更しません。作りたいオブジェクトの情報をは区分（２）に記載していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/348be6cd-8cfc-8f33-4d33-510b33bb86ab.png)


|区分|区分の説明|
|---|---|
|区分（１）|関数群。さわらない|
|区分（２）|3DCGデータ作成。<BR>オブジェクトの作成/移動/複製/回転/くり抜き など設定|
|区分（３）|関数`creObjFlow`,`editFlow`を実行。さわらない|

## スクリプトの使い方

スクリプトの基本的な使い方を紹介します。

### （１）オブジェクトを作成する

例えば直方体を作る場合はこのようにします。ここでは直方体を作成せず変数`olist`に作成したいオブジェクトの情報を登録だけしています。

```python
ScaleX = 30.0
ScaleY = 30.0
ScaleZ = 5.0
LocateX = -11.0
LocateY = -10.0
LocateZ = -17.0
olist.append( {"OBJ": "CUBE", "name": "Bottom01", "location": (LocateX,LocateY,LocateZ), "size": (ScaleX,ScaleY,ScaleZ), "rotation": (0.0,0.0,0.0), "material": mlist[40],} )
```

実際にオブジェクトを作成するのは区分（３）の`creObjFlow`関数です。

```python
## オブジェクト作成
creObjFlow(olist)
```

円柱を作成する場合はこのようになります。`1.5708`とは90度回転しています。

```python
radiusTumaTate = 2.4
radiusTumaYoko = 2.4
depthTuma = 6.0
LocateZ = -11.5

olist.append( {"OBJ": "CYLINDER","name": "TOTU-01","location": (-1, -18.5, 0),"radius": radiusTumaYoko,"depth": depthTuma,"rotation": (0,1.5708,0),"material": mlist[30],} )
```

このスクリプトで作れるオブジェクトの種類は以下です。

直方体

```python
olist.append( {"OBJ": "CUBE", "name": "SIDE01", "location": ( 18.5, 0, -2.5),   "size": (8, 4, 17.9), "rotation": (0,0,0), "material": mlist[12],} )
```

円柱

```python
olist.append( {"OBJ": "CYLINDER","name": "HOLE01", "location": (18.1, 0, 0),"radius": 1.6,"depth": 6.0,"rotation": (1.5708,0,0),"material": mlist[89],} )
```

円錐

```python
olist.append( {"OBJ": "CONE","name": "CONE01","location": (0,0,0),"vertices": 32, "radius1": 20, "radius2": 0, "depth": 30,"rotation": (0,0,0),"material": mlist[1],} )
olist.append( {"OBJ": "CONE","name": "CONE01","location": (40,0,0),"vertices": 32, "radius1": 10, "radius2": 5, "depth": 20,"rotation": (0,0,0),"material": mlist[2],} )
```

円錐はこのようになります。`radius1`は底面の半径で、`radius2`は上面の半径です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/4cf7e3c6-b47c-3f57-b954-68258e184ba3.png)


### （２）オブジェクトを他のオブジェクトでくり抜く

2つのオブジェクトを作成し、くり抜きをする例を紹介します。
まずは2つオブジェクトを作成します。オブジェクトの`name`が重要なのでスクリプト無いで一意の値を設定します。例では、`Guide01`という直方体と`TOTU-95`という円柱の2つのオブジェクトを作成します。

```python
# 板に穴をあけるガイド
ScaleX = 30.0
ScaleY = 30.0
ScaleZ = 4.0
LocateX = -20.0
LocateY = -19.0
LocateZ = -28.0
olist.append( {"OBJ": "CUBE", "name": "Guide01", "location": (LocateX,LocateY,LocateZ), "size": (ScaleX,ScaleY,ScaleZ), "rotation": (0.0,0.0,0.0), "material": mlist[50],} )

# 四隅の支柱に穴をあける
radiusTumaTate = 1.45
radiusTumaYoko = 1.45
depthTuma = 140.0
LocateZ = -8

olist.append( {"OBJ": "CYLINDER","name": "TOTU-95","location": (-18.5, -17.5, LocateZ),"radius": radiusTumaYoko,"depth": depthTuma,"rotation": (0,0,0),"material": mlist[34],} )
```


直方体`Guide01`を、円柱`TOTU-95`でくり抜く場合は以下のようにします。

```python
alist.append({"function": "booleanFnc", "args": ["Guide01","TOTU-95"]})
```

くり抜きのために作った円柱`TOTU-95`は不要になったので削除します。

```python
alist.append({"function": "deleteFnc", "args": ["TOTU-95"]})
```

これらの処理を実際に行うのは区分（３）の`editFlow`関数です。

```python
## オブジェクト加工
editFlow(alist)
```

このスクリプトで作成されるデータはこのようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/3e0e79fb-9c79-1764-4bf0-1eb450b10894.png)

### （３）オブジェクトを移動する

作成したオブジェクトを移動します。
以下は`SIDE01`という名前のオブジェクトを座標(18, 0, 0)に移動します。

```python
alist.append({"function": "moveFnc2", "args": ["SIDE01", 18, 0, 0]})
```

（２）で紹介したくり抜きと、この移動を繰り返すと無数の穴が開いた板を作る場合に便利です。

### （４）オブジェクトを回転する

オブジェクトを回転します。
以下は`KADO-B-01`という名前のオブジェクトをY軸を基準に 60度 回転します。

```python
alist.append({"function": "rotateFnc", "args": ["KADO-B-01",0,60,0]})
```

### （５）オブジェクトを複製する

オブジェクトを複製します。
以下は`FUTA-M-01`という名前のオブジェクト複製し、`FUTA-M-11`という名前のオブジェクトを新規に作成します。また、作成したオブジェクトは`FUTA-M-01`の位置からX軸方向に、-50 移動したところに配置します。

```python
alist.append({"function": "copyFnc", "args": ["FUTA-M-01", "FUTA-M-11",-50,  0, 0]})
```

これは、板に無数の突起を作りたいときに便利です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/24425f85-e30c-ef75-b7fb-308bc9280089.png)

### （６）オブジェクトの角を丸める

オブジェクトの角を丸めます。
以下は`CUBE02`という名前のオブジェクトの角を丸めます。

```python
# 角を丸めない
olist.append( {"OBJ": "CUBE", "name": "CUBE01", "location": (30,0,1), "size": (40, 50, 60), "rotation": (0,0,0), "material": mlist[20],} )

# 角を丸める
olist.append( {"OBJ": "CUBE", "name": "CUBE02", "location": (-30,0,1), "size": (40, 50, 60), "rotation": (0,0,0), "material": mlist[10],} )
alist.append({"function": "bevelFnc", "args": ["CUBE02", 15, 0.1]})
```

スクリプトの`["CUBE02", 15, 0.1]`について、15は丸めるときに面の解像度で数値を大きくするほど滑らかな丸みになります。0.1 は、丸める幅で数値を大きくすると丸める幅が大きくなります。

例を実行するとこのようになります。黄色が丸みなしです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/67727e84-5ef2-7512-0494-c0497a0bf987.png)


### （７）オブジェクトを削除する

既に紹介していますが、オブジェクトを削除します。
以下は`TOTU-95`という名前のオブジェクトを削除します。

```python
alist.append({"function": "deleteFnc", "args": ["TOTU-95"]})
```

くり抜き用に作ったオブジェクトなど不要になったものを削除するのに便利です。


### （８）オブジェクトの面を押し出す

オブジェクトの指定した面を押し出します。
以下は`CUBE02`という名前のオブジェクトの１つの面を押し出します。

```python
# 面を押し出さない
olist.append( {"OBJ": "CUBE", "name": "CUBE01", "location": (30,0,1), "size": (40, 50, 60), "rotation": (0,0,0), "material": mlist[20],} )

# 面を押し出す
olist.append( {"OBJ": "CUBE", "name": "CUBE02", "location": (-30,0,1), "size": (40, 50, 60), "rotation": (0,0,0), "material": mlist[10],} )
alist.append({"function": "moveMesh", "args": ["CUBE02", 1, 10,10,10]})
```

スクリプトの`["CUBE02", 1, 10,10,10]`について、1 は`CUBE02`の押し出す面の番号です。`CUBE02`は直方体なので、面は 0 ～ 5 の番号が割り振られています。`10,10,10` は、押し出す面の移動先の座標(x,y,z)です。

スクリプトを実行するとこのようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/17a147ad-3de4-afce-90fe-3f4d4c8a4190.png)

### （９）オブジェクトの面を拡大する

オブジェクトの指定した面を押し出します。
以下は`CUBE02`という名前のオブジェクトの１つの面を拡大します。

```python
olist.append( {"OBJ": "CUBE", "name": "CUBE02", "location": (-30,0,1), "size": (40, 50, 60), "rotation": (0,0,0), "material": mlist[23],} )
alist.append({"function": "scaleMesh", "args": ["CUBE02", 5, 2.5,2.5,2.5]})
```

スクリプトを実行するとこのようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/7f8d017c-84bc-4cf6-294f-e2d788c41199.png)

### （１０）オブジェクトをグループ化する

複数のオブジェクトをグループ化します。（オブジェクトのJoinです）
以下は、`A-CONE02`を`A-CONE01`にJoinします。
関数の引数`"args": ["A-CONE01","A-CONE02"]`の0番目`A-CONE01`がJoin元になります。

```python
olist.append( {"OBJ": "CONE","name": "A-CONE01","location": (0,0,0),"vertices": 20, "radius1": 23, "radius2": 21, "depth": 10,"rotation": (0,0,0),"material": mlist[22],} )
olist.append( {"OBJ": "CONE","name": "A-CONE02","location": (0,0,0),"vertices": 30, "radius1": 16, "radius2": 19, "depth": 12,"rotation": (0,0,0),"material": mlist[21],} )
alist.append({"function": "addJoin", "args": ["A-CONE01","A-CONE02"]})
```

複数のオブジェクトをまとめて移動やコピーしたいときに便利です。



### （１１）オブジェクト名を変更する

オブジェクト名を変更します。

```python
alist.append({"function": "renameFnc", "args": ["{変更前}","{変更後}"]})
```

オブジェクト名`A-CONE01`を`FUGA`に変更する場合はこのようになります。

```python
alist.append({"function": "renameFnc", "args": ["A-CONE01","FUGA"]})
```



### （１２）オブジェクトの中心を指定した座標に移動する

指定したオブジェクトの中心座標を指定した座標に移動します。
以下の場合、オブジェクト名`HAKO`の中心座標を`(0,0,100)`に移動します。

```python
alist.append({"function": "moveOriginFnc", "args": ["HAKO",0,0,100]})
```

### （１３）オブジェクトの中心座標を修正する

指定したオブジェクトの中心座標を更新します。
以下の場合、オブジェクト名`HAKO`の中心座標を`(0,0,30)`に更新します。

```python
alist.append({"function": "resetOriginFnc", "args": ["HAKO-2x2",0,0,30]})
```




## 利用例

スクリプトを組合せて使う例を紹介します。

### （１）円錐と押し出し

円錐と押し出しでビンのような形を作ります。
ネジ穴の作成に使えそうです。

```python
# ただの円錐
olist.append( {"OBJ": "CONE","name": "CONE01","location": (0,-40,0),"vertices": 32, "radius1": 10, "radius2": 5, "depth": 20,"rotation": (0,0,0),"material": mlist[9],} )

# 押し出しと組合せる円錐
olist.append( {"OBJ": "CONE","name": "CONE02","location": (0,0,0),"vertices": 32, "radius1": 10, "radius2": 5, "depth": 20,"rotation": (0,0,0),"material": mlist[3],} )
alist.append({"function": "moveMesh", "args": ["CONE02", 30, 0,0,20]})
alist.append({"function": "moveMesh", "args": ["CONE02", 33, 0,0,-30]})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/23e843bc-0644-c2c0-6dd1-4af1de47925a.png)


### （２）無数の穴を空ける

移動と、くり抜きをforループで繰り返し穴を空けます。
この繰り返し処理を使えるのがスクリプトで3Dデータを作るメリットと思います。

```python
# 穴を空ける板
olist.append( {"OBJ": "CUBE", "name": "CUBE01", "location": (0,0,0), "size": (40, 50, 10), "rotation": (0,0,0), "material": mlist[20],} )
# 空ける穴
olist.append( {"OBJ": "CYLINDER","name": "HOLE01", "location": (0, 20, 0),"radius": 3,"depth": 15.0,"rotation": (0,0,0),"material": mlist[89],} )

# 穴を空けるときの基準の位置
iniX=15
iniY=15
iniZ=0

# 穴を空ける位置
curX=0
curY=0
curZ=0

for i in range(3):
    for j in range(3):
        curX=iniX - 8*i  # X座標を設定
        curY=iniY - 8*j  # Y座標を設定
        # 穴を移動する
        alist.append({"function": "moveFnc2", "args": ["HOLE01", curX, curY, curZ]})
        # 穴を空ける
        alist.append({"function": "booleanFnc", "args": ["CUBE01","HOLE01"]})

# 不要になったオブジェクトを削除
alist.append({"function": "deleteFnc", "args": ["HOLE01"]})
```

ループの回数を変更すれば好きなだけ穴空きにできます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/a2b19f80-13d3-ef52-538d-4b186a458896.png)

### （３）加工したオブジェクトでくり抜く

利用例の（１）と（２）を組み合わせた例です。

```python
# くり抜きする板
olist.append( {"OBJ": "CUBE", "name": "CUBE01", "location": (0,0,0), "size": (200, 125, 20), "rotation": (0,0,0), "material": mlist[20],} )

# くり抜く型
olist.append( {"OBJ": "CONE","name": "HOLE01","location": (0,0,0),"vertices": 32, "radius1": 10, "radius2": 5, "depth": 20,"rotation": (0,0,0),"material": mlist[3],} )
alist.append({"function": "moveMesh", "args": ["HOLE01", 30, 0,0,20]})
alist.append({"function": "moveMesh", "args": ["HOLE01", 33, 0,0,-30]})

iniX=15
iniY=15
iniZ=0

curX=0
curY=0
curZ=0

for i in range(5):
    for j in range(5):
        curX=iniX - 20*i
        curY=iniY - 20*j
        if i % 2 == 0:
            # オブジェクト HOLE01 をY軸に対し180度回転
            alist.append({"function": "rotateFnc", "args": ["HOLE01",0,180,0]})
        else:
            alist.append({"function": "rotateFnc", "args": ["HOLE01",0,0,0]})

        # オブジェクト HOLE01 を移動
        alist.append({"function": "moveFnc2", "args": ["HOLE01", curX, curY, curZ]})
        # オブジェクト CUBE01 を HOLE01 でくり抜き
        alist.append({"function": "booleanFnc", "args": ["CUBE01","HOLE01"]})

# オブジェクト HOLE01 を削除
alist.append({"function": "deleteFnc", "args": ["HOLE01"]})
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274270/2166e672-12f0-2118-1c02-ef3cc4015d5e.png)

---

## Blender楽しい

Blenderのスクリプト活用により、より複雑なデザインやオブジェクトを簡単に作成できるようになりました。今後もさらなる改善や新機能の追加を目指していきます :)