Blender Python APIを使ってブーリアン演算をバッチ処理してみよう

# 概要
- Blender Python APIを使って2つのstlファイルのブーリアン演算をバッチ処理するよ
- stlを読み込んだ後はbpy.data.objectsを使って該当のオブジェクトを呼び出すよ
- ブーリアン処理はmodifierを使うよ

# Blender Python API is 何？
Blenderという3Dモデリングソフトウェア上で出来ることの大半をPythonで実行するためのAPIです。ここではWindows上でバージョン2.93.5を使用します。

- 公式
  - [blender.org - Home of the Blender project - Free and Open 3D Creation Software](https://www.blender.org/)
- API Documentation
  - [Blender 2.93.5 Release Candidate Python API Documentation — Blender Python API](https://docs.blender.org/api/current/)
     - これを読んでるときは2.93.5のリファレンスじゃないかも

# 前提
**「foo.stlとbar.stlがあって、2つのstlを結合してunion.stlを作りたいよ！」**という前提で作業します。
ファイルの初期構成は下記の様になってると思ってください

```
./
├ bar.stl
├ foo.stl
└ union.py
```

実行後は

```
./
├ bar.stl
├ foo.stl
├ union.py
└ union.stl
```

のようになります。

# ソースコード
```python:union.py
import bpy  # blenderモジュール
from os import path

# 既存のオブジェクトをすべて削除
for obj in bpy.data.objects:
    obj.select_set(True)
bpy.ops.object.delete()

# fooとbarのstl読込
CURRENT_PATH = path.dirname(path.abspath(__file__)) + "\\"
bpy.ops.import_mesh.stl(filepath=CURRENT_PATH + 'foo.stl')
bpy.ops.import_mesh.stl(filepath=CURRENT_PATH + 'bar.stl')

# fooとbarのオブジェクト呼び出し
foo_index = bpy.data.objects.find('foo')
foo_object = bpy.data.objects[foo_index]
bar_index = bpy.data.objects.find('bar')
bar_object = bpy.data.objects[bar_index]

# fooにbarを結合する
modifier = foo_object.modifiers.new(type='BOOLEAN', name='union')
modifier.object = bar_object
modifier.operation = 'UNION'

# 結合したfooを書き出す
foo_object.select_set(True)
bar_object.select_set(False)
bpy.ops.export_mesh.stl(filepath=CURRENT_PATH+'union.stl', use_selection=True)
```

# 実行方法
`blender.exe`に`--background`と`--python`オプションをつけて最後にスクリプト名を指定して実行します。
デフォルトの場所にインストールしたのであれば下記の様な`.bat`ファイルを作ると実行が楽になります。

```cmd:union.bat
"C:\Program Files\Blender Foundation\Blender 2.93\blender.exe" --background --python union.py
```

# 実際にスクリプトを書く時のTIPS
## インポートできるファイルの種類
Blender上のインポートメニューにあるように、`import_mesh`では`.ply`、`.stl`が、import_sceneでは`.fbx`、`.gltf`、`.obj`、`x3d`が読み込めるようです。

- 参考
   - [Import Mesh Operators — Blender Python API](https://docs.blender.org/api/current/bpy.ops.import_mesh.html)
   - [Import Scene Operators — Blender Python API](https://docs.blender.org/api/current/bpy.ops.import_scene.html)

## オブジェクトの存在確認
スクリプトを組んだ時、命名規則に沿ってつけたファイル名でループを回したり、もしかすると抜けがある前提があったりしたときに実際にそのファイルが存在するか、あるいは読み込まれたかどうかが分からない場合があるときに使用します。

```python
# ファイルの存在確認
if os.path.isfile(filepath):
    print(`OK`)
else:
    print(`NG`)

# オブジェクトが読み込まれているかどうかの確認
foo_index = bpy.data.objects.find('foo')
foo_object = None
if 0 <= foo_index: # indexが0以上なら存在する
    foo_object = bpy.data.objects[foo_index]
```

- 参考
   - [bpy_prop_collection — Blender Python API](https://docs.blender.org/api/current/bpy.types.bpy_prop_collection.html#bpy.types.bpy_prop_collection)
   - [BlendDataObjects(bpy_struct) — Blender Python API](https://docs.blender.org/api/current/bpy.types.BlendDataObjects.html#bpy.types.BlendDataObjects)
   - [Object(ID) — Blender Python API](https://docs.blender.org/api/current/bpy.types.Object.html#bpy.types.Object)

## 新規に空オブジェクトを作ってそっちに結合したい
これは少し複雑です。

```python
mesh = bpy.data.meshes.new(name='union')
union_object = bpy.data.objects.new('union', object_data=mesh)
view_layer = bpy.context.view_layer
view_layer.active_layer_collection.collection.objects.link(union_object)

modifier = union_object.modifiers.new(type='BOOLEAN', name='union1')
modifier.object = foo_object
modifier.operation = 'UNION'

modifier = union_object.modifiers.new(type='BOOLEAN', name='union2')
modifier.object = bar_object
modifier.operation = 'UNION'
```

`bpy.data.object.new`で新規にオブジェクトを作るだけではだめで、事前に`mesh`を作って渡さないといけなかったり、`view_layer`に登録する必要があったりして知らないと大変です。

- 参考
  - [BlendDataMeshes(bpy_struct) — Blender Python API](https://docs.blender.org/api/current/bpy.types.BlendDataMeshes.html#bpy.types.BlendDataMeshes)
  - [ViewLayer(bpy_struct) — Blender Python API](https://docs.blender.org/api/current/bpy.types.ViewLayer.html#bpy.types.ViewLayer)



## ブール演算の種類
Blender上でもあるように交差(`INTERSECT`)、結合(`UNION`)、差分(`DIFFERENCE`)の3つの操作があります。`modifier.operator`に渡す引数で操作を変更できます。

- 参考
   - [Modifier(bpy_struct) — Blender Python API](https://docs.blender.org/api/current/bpy.types.Modifier.html#bpy.types.Modifier)
   - [BooleanModifier(Modifier) — Blender Python API](https://docs.blender.org/api/current/bpy.types.BooleanModifier.html#bpy.types.BooleanModifier)

## 結合した後に滑らかにしたい(Subdivision Surface)
ここでは説明しませんがSubdivision Surfaceを使うとメッシュを滑らかにできます。
`modifier.new`のパラメータで`type='SUBSURF'`を指定してください。

- 参考
   - [SubsurfModifier(Modifier) — Blender Python API](https://docs.blender.org/api/current/bpy.types.SubsurfModifier.html#bpy.types.SubsurfModifier)


## 続きの作業をBlenderで行いたい
現在のシーンを保存する`bpy.ops.wm.save_as_mainfile`メソッドがあります。

```python
bpy.ops.wm.save_as_mainfile(CURRENT_PATH + 'export.blend')
```

:::note warn
既に同じ名前のファイルがある場合は、古いファイルが<b>export.blend1</b>のような形で残ります。
:::

- 参考
   - [Wm Operators — Blender Python API](https://docs.blender.org/api/current/bpy.ops.wm.html#bpy.ops.wm.save_as_mainfile)

# まとめ
ブーリアン演算を自力で実装するのは結構大変だと思います。
外部ツールとしてBlenderを使うことで楽になることもあるのではないかと思います。