1. KTakahiro1729

    No comment

    KTakahiro1729
Changes in body
Source | HTML | Preview
@@ -1,227 +1,227 @@
# シーン内の全てのメッシュオブジェクトの原点を変更する
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`に伝えて実行すること」を指します。
-Autocompleteだとわかりませんが、`bpy.ops`内の全ての関数は実は「第1引数にcontextを受けることが出来る」のです。ここでいうcontextとは、`bpy.context.copy()`で取得できるような辞書のことを指します。
+`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を削除しても処理が
+私にはよく理解できなかったのですが、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
"""
```