Blenderの謎な部分がなんとなく分かった!

Object Propertiesパネルに表示される位置、回転、スケールの値ですが、何気にこれ、ややこしいです。

Mayaを使い慣れてると、親子付けした子のトランスフォーム値に関して、ここには親座標系(親の基点からの相対値)での値が表示されていることを期待するのですが、Blenderに関してはここには親座標系での数値あったり、そうでないよく分からない値が表示されたりします。

以下、実験と謎解きです。

手順A

Emptyの子にCubeを配置しEmptyを移動します。

  1. Cubeを作成、Y方向へ3.0移動(x=0.0, y=3.0, z=0.0にCubeを作成)
  2. Emptyを作成、原点のまま
  3. CubeをEmptyの子にする。
  4. EmpyをZ方向へ3.0移動(もちろん子であるCubeもついてきます)

手順をPythonで書くなら以下

import bpy
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
#create cube
bpy.ops.mesh.primitive_cube_add()
bpy.context.active_object.name = 'my_cube'
bpy.data.objects['my_cube'].location = (0.0, 3.0, 0.0)
#cretate empty(locator)
bpy.ops.object.empty_add()
bpy.context.active_object.name = 'my_empty'

bpy.data.objects['my_cube'].select_set(True)
bpy.data.objects['my_empty'].select_set(True)

bpy.ops.object.parent_set()
#move empty
bpy.data.objects['my_empty'].location = (0.0, 0.0, 3.0)
手順A

「手順A」を終えたときのCubeのTransform.locationはx=0.0, y=3.0, z=0.0が表示されています。Emptyをzへ3.0移動してこの表示であることを考えるとここには親からの相対位置が表示されていると思えます

手順B

Aは親子付けしてからEmptyをZ=3.0移動しましたが、今度はCube,Empty両方ともZ=3.0に移動した後で親子付けします。

  1. Cubeを作成、Y/Z方向へそれぞれ3.0移動(x=0.0, y=3.0, z=3.0にCubeを作成)
  2. Emptyを作成、Z方向へ3.0移動(x=0.0, y=0.0, z=3.0にEmptyを作成)
  3. CubeをEmptyの子にする。
import bpy
bpy.context.scene.cursor.location = (0.0, 0.0, 0.0)
#create cube
bpy.ops.mesh.primitive_cube_add()
bpy.context.active_object.name = 'my_cube'
bpy.data.objects['my_cube'].location = (0.0, 3.0, 3.0)
#cretate empty(locator)
bpy.ops.object.empty_add()
bpy.context.active_object.name = 'my_empty'
bpy.data.objects['my_empty'].location = (0.0, 0.0, 3.0)

bpy.data.objects['my_cube'].select_set(True)
bpy.data.objects['my_empty'].select_set(True)

bpy.ops.object.parent_set()
手順B、見た目変化はありませんが…

手順Aと同じ位置にEmptyもCubeもあります、親子の関係も同じです。が、CubeのTransform.locationプロパティはx=0.0, y=3.0, z=3.0となり、まるでワールド座標のようなものが表示されています。

どーして?

Transformプロパティに表示される値は親を持たないオブジェクトの場合はもちろんワールド座標系での値が表示されていますが問題は親子付け。子になった瞬間にTransformプロパティは親との相対値を再計算されることは無く、子になってもそれまでに自分が居た座標系での値を維持(表示)し続け、以降の移動などの操作はその値へ加/減算されていきます。

ややこしいですね。親座標系での数値が表示されていると思い込んでますから、子のlocationを(0,0,0)とすると親の基点に移動するはずですが、何故かワールド原点に移動したりします(親を動かしてなければ)。

さらにどーして?

ここからは幾何のオハナシ。あまり自信ありませんが。

Blenderのオブジェクトの変換についてはスクリプトで4つの変換行列を読み取れます。Pythonコンソールで「C.active_object.matrix」まで入力してTabキーを押すと候補として以下が表示されます。

  • matrix_basis
  • matrix_parent_inverse
  • matrix_local
  • matrix_world

聞きなれないのは「matrix_basis」、これこそがオブジェクトのTransformプロパティに表示されている値の元になる行列です。Delta Transfromを設定していない限り、以下で移動/回転/スケールが読み取れて、プロパティ表示と一致する事が確認できます。(回転はクォータニオンで出力されます。)

bpy.active_object.matrix_basis.decompose()

そしてもう一つは「matrix_parent_inverse」これは親子付けをした直後、親の逆変換行列がコピーされたものです。matrix_basisは親子付けをしても親の変換に応じて再計算されませんが、親子付け時にこのmatrix_parent_inverseを変更する事で子の正しい位置保ちます。関係は以下。「@」は行列を合成する演算子です。

matrix_local = matrix_basis @ matrix_parent_inverse
matrix_world = PARENT.matrix_world @ matrix_local

matrix_localをTransformプロパティへ表示してくれればいいんですけどね。どうしてそうなってないんでしょうね。誤差の問題でもあるのかな。

修正してみる

検証はあまりしていませんが、以下で無理やりmatrix_basisをmatrix_localに、つまりプロパティ表示を期待するような親座標系にする事が出来ます。アニメーションしているオブジェクトには使えません。

import bpy
obj = bpy.context.active_object
obj.matrix_basis = obj.matrix_local #basis = local
obj.matrix_parent_inverse.identity() #parent_inverseを単位行列に

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です