シーン内の全てのメッシュオブジェクトの原点を変更する (Python for Blender クックブック)

# シーン内の全てのメッシュオブジェクトの原点を変更する
Pythonを使ってBlenderでできる事をまとめています。解説もあります。
[はじめに](https://qiita.com/KTakahiro1729/items/5de5f60982af37d23aa0)
[レシピ一覧](https://qiita.com/KTakahiro1729/items/57f4e1f5495c5e54d431)

Pythonの難易度: ★★★★☆

### 動機
原点、ずれますよね？
<kbd>Ctrl</kbd>-<kbd>Shift</kbd>-<kbd>Alt</kbd>-<kbd>C</kbd>押しにくいですよね。
まとめて変更しましょう。

### 細かい仕様
- オブジェクトの原点（Origin）をGeometryの中心に移動してください。
- 原点の変更はメッシュオブジェクトだけにしてください。
- 全てのオブジェクトを選択しているとは限りません。
- オブジェクトによっては隠れていたり、別レイヤーにあったりするかもしれません。
- 現在の選択状態、アクティブ状態などは変更しないでください。

## サンプルコード


```python
import bpy
objs = [obj for obj in bpy.data.objects
      if obj.type == "MESH"]

override = bpy.context.copy()
override["selected_editable_objects"] = objs
bpy.ops.object.origin_set(override, type='ORIGIN_GEOMETRY')
```


上記サンプルコードを、Blender内蔵のテキストエディタに貼り付けた後、<kbd>Alt</kbd>-<kbd>P</kbd>を実行してください。

## 解説
### `bpy.ops`の使用とcontextのオーバーライド
`bpy`はBlenderのpython APIのライブラリーですが、その中の`bpy.ops`は「Blenderを編集するための関数」を格納しています。
この`bpy.ops`内の関数を使うときに欠かせない考え方が、contextとcontextのoverrideです。

なお、今回説明するcontextのoverrideは[公式ドキュメント](https://docs.blender.org/api/2.79/bpy.ops.html#overriding-context)でも紹介されていますが、ドキュメントが少なく、APIとして未完成な点が多いと感じています。

#### contextとは
同じく`bpy`に含まれる、`bpy.context`は現在のcontextを保持しています。
contextは英語で「文脈」を指しますが、blenderにおけるcontext（=文脈）とは一体何でしょうか？
それを確かめるために以下のコードを実行してみましょう。

```python
import bpy
from pprint import pprint # 辞書を綺麗に表示するために使っています
context = bpy.context.copy()
pprint(context)
```

以下は出力結果の一部です。

```python
{'active_base': bpy.data.scenes['Scene']...ObjectBase,
 'active_object': bpy.data.objects['Cube'],
 # 中略
 'active_object': bpy.data.objects['Cube'],
 'active_operator': None,
 # 中略
 'mode': 'OBJECT',
 'object': bpy.data.objects['Cube'],
 # 中略
 'scene': bpy.data.scenes['Scene'],
 'screen': bpy.data.screens['Scripting'],
 # 中略
 'selectable_objects': [bpy.data.objects['Cube'],
                        bpy.data.objects['Lamp'],
                        bpy.data.objects['Camera']],
 'selected_bases': [bpy.data.scenes['Scene']...ObjectBase],
 # 中略
 'selected_objects': [bpy.data.objects['Cube']],
 # 中略
 'window': bpy.data.scenes['Scene']...Window,
 'window_manager': bpy.data.window_managers['WinMan']}
```

このように、contextの中には「選択されたオブジェクトたち`"selected_objects"`」や「3Dビューのモード`"mode"`」などの情報が格納されています。色々試して、どういう項目がどういう時に変わるのか試してみて下さい。

#### `bpy.ops`とcontextの関係
`bpy.ops`内の関数は全て、このcontextを参照して実行されています。
例えば、<kbd>G</kbd>キーでオブジェクトを移動することは、`bpy.ops.transform.translate`を実行することと同じです。
例えば、以下のコードを実行したとします。

```python
bpy.ops.transform.translate(value = (1,1,1))
```

すると、その時に移動するのは選択されているオブジェクトです。
これはつまり、`bpy.ops.transform.translate`が`bpy.context`から、選択中のオブジェクトの情報を得ていることを示しています。

#### 選択外のオブジェクトを操作する
選択中のオブジェクト以外を対象に`bpy.ops`の関数を実行したい時があります。
例として、`bpy.data.objects["Lamp"]`以外の何かを選択している状態で、`bpy.data.objects["Lamp"]`を移動させたいと仮定します。

一番わかりやすい手法は以下の通りです。

```python
import bpy

# 実行前の選択を保持
old_selected_objects = [obj for obj in bpy.data.objects if obj.select]

# 選択解除
bpy.ops.object.select_all(action='DESELECT')

# Lampだけ選択
bpy.data.objects["Lamp"].select = True

# 移動
bpy.ops.transform.translate(value = (1,1,1))

# 元に戻す
bpy.data.objects["Lamp"].select = False
for obj in old_selected_objects:
    obj.select = True
```

ここで重要なのは「`bpy.ops`の関数などを駆使して、contextを直接、変更している」ことです。

これには2つ問題があります。
1. コードがわかりにくい
  様々な手法を*駆使*するため、それぞれ手法の役割が把握しにくくなります。
2. contextを元に戻せる確証がない
  先ほどのコードでは、実はアクティブなオブジェクトが変わっている可能性があります。
  このように、一度変えてしまったcontextは簡単には戻せません。

ここで出てくる概念がoverride contextです。

#### override context
"override context"とは「bpy.contextとは異なるcontextを`bpy.ops`に伝えて実行すること」を指します。
`bpy.ops`内の全ての関数は実は「第1引数にcontextを受けることが出来る」のです。ここでいうcontextとは、`bpy.context.copy()`で取得できるような辞書のことを指します。

以下は、ランプを移動するコードを書き換えたものです。

```python
import bpy

# {Object.name : ObjectBase} の対応を作る 
obj2base = {obj_b.object.name : obj_b for obj_b in 
    bpy.data.scenes[0].object_bases}

# コンテクストをコピー
override = bpy.context.copy()

# コンテキストを書き換える
t_name = "Lamp"
obj = bpy.data.objects[t_name]
obj_b = obj2base[t_name]

# 以下のコメントアウトは一旦無視してください

# override["active_object"] = obj
# override["acitve_base"] = obj_b

# override["selectable_objects"] += [obj]
# override["selected_objects"] = [obj]
# override["selected_editable_objects"] = [obj]

# override["selectable_bases"] += [obj_b]
override["selected_bases"] = [obj_b]
# override["selected_editable_bases"] = [obj_b]


# 実行
bpy.ops.transform.translate(override, value=(1,1,1))
```

上のコードでは、

1. bpy.contextから現在のcontextをディープコピーする
2. コピーしたcontext(`override_context`)を編集する
3. 変更したcontextを`bpy.ops`の関数の第1引数に渡した上で実行する

という流れを踏んだものです。
この`override_context`はただの辞書ですので、何をどうしようと元のcontextに影響しません。なんなら元の`context.copy`を使わずに0から作っても、必要なキーが揃っていればcontextとして正常に作動します。

#### override contextの問題点
ここでクセモノなのが「必要なキー」です。どのcontextを書き換えるべきかは、実はよくわかりませんし、ドキュメントもありません。
たとえば`bpy.ops.transform.translate`の場合、`selected_bases`ですが、`bpy.ops.object.origin_set`なら`selected_editable_objects`を書き換える必要があります。別の関数なら別のキーです。Objectへの操作なら大概、コメントアウトされているキーのどれかです。

この問題については、私が使った便利ツールを記事の最後に載せておきますので良ければお使いください。

以下、`selected_bases`について説明します。

#### Object Base
Lamp移動のサンプルでは

```python
override["selected_bases"] = [base]
```

のように、`"selected_bases"`を変更しています。
これは、`bpy.types`で定義されている`ObjectBase`のインスタンス（のリスト）です。
この`ObjectBase`は本来、blender内部実装深くの住人ですのでその正体に深入りはしません。
次の事項について把握しておけば問題ないでしょう。

- `ObjectBase`はオブジェクトを格納しており、`ObjectBase.object`からアクセスできる
- `ObjectBase`一覧にアクセスする方法は多分`bpy.data.scenes[シーン名／インデックス].object_bases`だけ
- そのオブジェクトを格納している`object_base`を書き換えればよく、「`object`も書き換えて整合性を取る」というのは要らない
- `bpy.context`はcontextと同じだが、`ObjectBase`のプロパティーは持たない

もっと`ObjectBase`について深く知りたいなら、[公式ドキュメント](https://en.blender.org/index.php/Dev:Source/Architecture/Overview#Data_structure)にちょろっと書いてありますので参考にしてください。
私にはよく理解できなかったのですが、Objectのインスタンスを格納するもので、作業中にObjectを削除しても処理が重くならないようにしてくれるもの、だと思ってます。

### 便利ツール
context_overrideを便利に使う[`ContextEditor`というクラス](https://gist.github.com/KTakahiro1729/cd476845b275a01d343318edb0fab5db)を作りましたので、ぜひお使いください。


### 使用例

```python
c = ContextEditor()
c["selected_objects"] = []
c["object"] = bpy.data.objects["Cube.001"]
bpy.ops.object.origin_set(c.rc,type='ORIGIN_GEOMETRY')

"""
- The user instantiates the ContextEditor and edit it via dict-like access.
- c["selected_objects"] = [] will clear not only selected_objects but also bases and other related ones.
- c["object"] = Object will also set keys like active_object and selected_editable_bases.
- To pass ContextEditor as an override_context, the user uses the rc prop
"""
```