bpy.ops.transform による変換【第 7 回 Python × Blender】

今回はbpy.ops.transformによる変換についての記事です。



まずは以下のut.pyという Python スクリプトBlender で実行してみてください。このスクリプトif __name__ == "__main__"以下の部分はbpy.ops.transformを説明するためのスクリプト例です。

import bpy
import math


# 全てのオブジェクトを選択.
def select_all():
    bpy.ops.object.select_all(action='SELECT')

# 全てのオブジェクトの選択を解除.
def deselect_all():
    bpy.ops.object.select_all(action='DESELECT')

# 名前 obj_name を持つオブジェクトを選択. deselect==False の時には, 以前に選択したオブジェクトの選択は保たれる.
def select(obj_name, deselect=True):
    if deselect:
        deselect_all()
    bpy.data.objects[obj_name].select = True

# 名前 obj_name を持つオブジェクトのアクティブ化.
def activate(obj_name):
    bpy.context.scene.objects.active = bpy.data.objects[obj_name]


class selected:
    ''' 選択された全てのオブジェクトに対する操作関数クラス. '''

    #(変換)vector 分だけ移動.
    def translate(vector):
        bpy.ops.transform.translate(value=vector, constraint_axis=(True,True,True))

    #(変換)ratios 分だけ x,y,z 軸方向に拡大.
    def scale(ratios):
        bpy.ops.transform.resize(value=ratios, constraint_axis=(True,True,True))

    #(変換)axis_vector 回転軸に angle だけ回転.
    def rotate(axis_vector, angle):
        bpy.ops.transform.rotate(value=angle, axis=axis_vector)

    #(変換)x 軸を回転軸に angle だけ回転.
    def x_rotate(angle):
        bpy.ops.transform.rotate(value=angle, axis=(1,0,0))

    #(変換)y 軸を回転軸に angle だけ回転.
    def y_rotate(angle):
        bpy.ops.transform.rotate(value=angle, axis=(0,1,0))

    #(変換)z 軸を回転軸に angle だけ回転.
    def z_rotate(angle):
        bpy.ops.transform.rotate(value=angle, axis=(0,0,1))


class activated:
    ''' アクティブ化しているオブジェクトに対する操作関数クラス. '''

    #(宣言)location に位置を変更.
    def locate(location):
        bpy.context.object.location = location

    #(宣言)ratios 分だけ x,y,z 軸方向に拡大.
    def scale(ratios):
        bpy.context.object.scale = ratios

    #(宣言)オイラー角 euler_angles 分回転.
    def rotate(euler_angles):
        bpy.context.object.rotation_euler = euler_angles

    #(宣言)名前の変更.
    def rename(new_name):
        bpy.context.object.name = new_name


class specified:
    ''' specified されたオブジェクトに対する操作関数クラス. '''

    #(宣言)obj_name を名前に持つオブジェクトの位置を location に変更.
    def locate(obj_name, location):
        bpy.data.objects[obj_name].location = location

    #(宣言)obj_name を名前に持つオブジェクトを ratios 分だけ x,y,z 軸方向に拡大.
    def scale(obj_name, ratios):
        bpy.data.objects[obj_name].scale = ratios

    #(宣言)obj_name を名前に持つオブジェクトの位置をオイラー角 euler_angles 分回転.
    def rotate(obj_name, euler_angles):
        bpy.data.objects[obj_name].rotation_euler = euler_angles


class create:
    ''' オブジェクトの生成関数クラス. '''

    # 立方体を配置.
    def cube(new_name, new_radius=0.5, new_location=(0,0,0)):
        bpy.ops.mesh.primitive_cube_add(radius=new_radius, location=new_location)
        activated.rename(new_name)

    # 球を配置.
    def sphere(new_name, new_size=0.5, new_location=(0,0,0)):
        bpy.ops.mesh.primitive_uv_sphere_add(size=new_size, location=new_location)
        activated.rename(new_name)

    # 円錐を配置.
    def cone(new_name, new_radius1=0.5, new_depth=1, new_location=(0,0,0)):
        bpy.ops.mesh.primitive_cone_add(radius1=new_radius1, depth=new_depth, location=new_location)
        activated.rename(new_name)

    # 円柱を配置.
    def cylinder(new_name, new_radius=0.5, new_depth=1, new_location=(0,0,0)):
        bpy.ops.mesh.primitive_cylinder_add(radius=new_radius, depth=new_depth, location=new_location)
        activated.rename(new_name)


# 名前 delete_name を持つオブジェクトを削除.
def delete(delete_name):
    selected_name_list = [obj.name for obj in bpy.context.selected_objects if not obj.name == delete_name]
    select(obj_name=delete_name, deselect=True)
    bpy.ops.object.delete(use_global=False)
    for selected_name in selected_name_list:
        select(selected_name, False)


# 全てのオブジェクトを削除.
def delete_all():
    if len(bpy.data.objects) > 0:
        select_all()
        bpy.ops.object.delete(use_global=False)


if __name__ == "__main__":
    ''' import ut では以降のスクリプトは実行されない. '''

    # 初期化
    delete_all()

    # 立方体を 5×5×5 で配置.
    for i in range(5):
        x = -2 + i
        for j in range(5):
            y = -2 + j
            for k in range(5):
                z = -2 + k
                # 立方体の位置.
                cube_location = (x, y, z)
                # 立方体の名前.
                cube_name = "Cube." + str(i) + str(j) + str(k)
                # 立方体を配置.
                create.cube(cube_name)
                # 立方体(アクティブ化されている)の名前の確認.
                print(bpy.context.object.name)
                # 立方体(アクティブ化されている)の位置を設定(宣言).
                activated.locate(cube_location)

    # 全ての立方体を選択.
    select_all()
    # 選択されている全ての立方体を (1, 1, 1) 中心に 60° 回転(変換).
    selected.rotate((1, 1, 1), math.pi / 3)
    # 選択されている全ての立方体を x 軸方向に 1.5 倍, y 軸方向に 3 倍(変換).
    selected.scale((1.5, 3, 1))

実行結果は以下のようになります。
f:id:tamaki_py:20190530161101p:plain
ターミナルは以下のようになっているでしょう。

Cube.000
Cube.001
Cube.002
Cube.003
Cube.004
Cube.010
Cube.011
Cube.012
Cube.013
Cube.014
Cube.020
Cube.021
Cube.022
Cube.023
Cube.024
Cube.030
Cube.031
Cube.032
Cube.033
Cube.034
Cube.040
Cube.041
Cube.042
Cube.043
Cube.044
Cube.100
Cube.101
Cube.102
Cube.103
Cube.104
Cube.110
Cube.111
Cube.112
Cube.113
Cube.114
Cube.120
Cube.121
Cube.122
Cube.123
Cube.124
Cube.130
Cube.131
Cube.132
Cube.133
Cube.134
Cube.140
Cube.141
Cube.142
Cube.143
Cube.144
Cube.200
Cube.201
Cube.202
Cube.203
Cube.204
Cube.210
Cube.211
Cube.212
Cube.213
Cube.214
Cube.220
Cube.221
Cube.222
Cube.223
Cube.224
Cube.230
Cube.231
Cube.232
Cube.233
Cube.234
Cube.240
Cube.241
Cube.242
Cube.243
Cube.244
Cube.300
Cube.301
Cube.302
Cube.303
Cube.304
Cube.310
Cube.311
Cube.312
Cube.313
Cube.314
Cube.320
Cube.321
Cube.322
Cube.323
Cube.324
Cube.330
Cube.331
Cube.332
Cube.333
Cube.334
Cube.340
Cube.341
Cube.342
Cube.343
Cube.344
Cube.400
Cube.401
Cube.402
Cube.403
Cube.404
Cube.410
Cube.411
Cube.412
Cube.413
Cube.414
Cube.420
Cube.421
Cube.422
Cube.423
Cube.424
Cube.430
Cube.431
Cube.432
Cube.433
Cube.434
Cube.440
Cube.441
Cube.442
Cube.443
Cube.444

Blender での Python スクリプトの動かし方は以下の記事を参考にしてください。
tamaki-py.hatenablog.com



今回説明したいのはbpy.ops.transformです。上記のコード中では、選択されたオブジェクトに対する操作関数クラスであるselectedクラスの部分です。

class selected:
    ''' 選択された全てのオブジェクトに対する操作関数クラス. '''

    #(変換)vector 分だけ移動.
    def translate(vector):
        bpy.ops.transform.translate(value=vector, constraint_axis=(True,True,True))

    #(変換)ratios 分だけ x,y,z 軸方向に拡大.
    def scale(ratios):
        bpy.ops.transform.resize(value=ratios, constraint_axis=(True,True,True))

    #(変換)axis_vector 回転軸に angle だけ回転.
    def rotate(axis_vector, angle):
        bpy.ops.transform.rotate(value=angle, axis=axis_vector)

    #(変換)x 軸を回転軸に angle だけ回転.
    def x_rotate(angle):
        bpy.ops.transform.rotate(value=angle, axis=(1,0,0))

    #(変換)y 軸を回転軸に angle だけ回転.
    def y_rotate(angle):
        bpy.ops.transform.rotate(value=angle, axis=(0,1,0))

    #(変換)z 軸を回転軸に angle だけ回転.
    def z_rotate(angle):
        bpy.ops.transform.rotate(value=angle, axis=(0,0,1))

このスクリプトからもわかるように、選択されている全てのオブジェクトの移動・拡大縮小・回転といった『変換型(differential)』の操作を行うことができるのがbpy.ops.transformクラスです。ドキュメンテーションは以下をご覧ください。
docs.blender.org



また注意していただきたいのは、アクティブ化されたオブジェクトに対する操作関数クラスactivated・specified されたオブジェクトに対する操作関数クラスspecifiedの関数は、選択されたオブジェクトに対する操作関数クラスであるselectedクラスと違って「宣言型(declarative)」の操作関数であるということです。

class activated:
    ''' アクティブ化しているオブジェクトに対する操作関数クラス. '''

    #(宣言)location に位置を変更.
    def locate(location):
        bpy.context.object.location = location

    #(宣言)ratios 分だけ x,y,z 軸方向に拡大.
    def scale(ratios):
        bpy.context.object.scale = ratios

    #(宣言)オイラー角 euler_angles 分回転.
    def rotate(euler_angles):
        bpy.context.object.rotation_euler = euler_angles

    #(宣言)名前の変更.
    def rename(new_name):
        bpy.context.object.name = new_name


class specified:
    ''' specified されたオブジェクトに対する操作関数クラス. '''

    #(宣言)obj_name を名前に持つオブジェクトの位置を location に変更.
    def locate(obj_name, location):
        bpy.data.objects[obj_name].location = location

    #(宣言)obj_name を名前に持つオブジェクトを ratios 分だけ x,y,z 軸方向に拡大.
    def scale(obj_name, ratios):
        bpy.data.objects[obj_name].scale = ratios

    #(宣言)obj_name を名前に持つオブジェクトの位置をオイラー角 euler_angles 分回転.
    def rotate(obj_name, euler_angles):
        bpy.data.objects[obj_name].rotation_euler = euler_angles

「変換」と「宣言」の違いは、

  • 変換は既にあるオブジェクトのデータを基準にオブジェクトを変形する。
  • 宣言はオブジェクトのデータを書き換える。

ということです。



例えばbpy.ops.transform.translate(value=vector, constraint_axis=(True,True,True))は選択された全てのオブジェクトを、そのオブジェクトの存在する位置から 3 次元 float 型配列ベクトルvector分だけ移動する「変換」なのに対し、



bpy.context.object.location = locationはアクティブ化されているオブジェクトの位置をそもそも書き換えてしまっている「宣言」型の操作です。



今回のこのut.pyはあくまで最も簡単なツールのモジュールとして定義しているだけではありますが、長いコードを関数で短くスッキリ書くことを可能にしていますので、これからもimportで用いていきます。