LoginSignup
10
8

More than 5 years have passed since last update.

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

Last updated at Posted at 2018-11-05

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

Pythonを使ってBlenderでできる事をまとめています。解説もあります。
はじめに
レシピ一覧

Pythonの難易度: ★★★★☆

動機

原点、ずれますよね?
Ctrl-Shift-Alt-C押しにくいですよね。
まとめて変更しましょう。

細かい仕様

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

サンプルコード

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内蔵のテキストエディタに貼り付けた後、Alt-Pを実行してください。

解説

bpy.opsの使用とcontextのオーバーライド

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

なお、今回説明するcontextのoverrideは公式ドキュメントでも紹介されていますが、ドキュメントが少なく、APIとして未完成な点が多いと感じています。

contextとは

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

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

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

{'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を参照して実行されています。
例えば、Gキーでオブジェクトを移動することは、bpy.ops.transform.translateを実行することと同じです。
例えば、以下のコードを実行したとします。

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

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

選択外のオブジェクトを操作する

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

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

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()で取得できるような辞書のことを指します。

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

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移動のサンプルでは

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について深く知りたいなら、公式ドキュメントにちょろっと書いてありますので参考にしてください。
私にはよく理解できなかったのですが、Objectのインスタンスを格納するもので、作業中にObjectを削除しても処理が重くならないようにしてくれるもの、だと思ってます。

便利ツール

context_overrideを便利に使うContextEditorというクラスを作りましたので、ぜひお使いください。

使用例

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
"""
10
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
8