アストラルプリズム

PC、スマホ、ゲームなどの備忘録と日記

Blender python なるべくbpy.opsを使わない方法

bpy.ops系は遅いのですよ・・・。
あとどれがアクティブでセレクトなのかとか後で改造するときに調べるの大変なのでなるべくbpy.data系を使っていきたい今日この頃・・・

拡大縮小

import bpy


#bpy.ops系(ローカル座標基準の場合のみ)
bpy.ops.transform.resize(value=(a, b, c), orient_type='LOCAL')

#data系
obj = bpy.context.object
obj.scale=[obj.scale[0] * a , obj.scale[1] * b , obj.scale[2] * c]

#あるいは
s = [1,2,3]
obj.scale = [x * y for (x, y) in zip(obj.scale, s)]

#bpy.ops系(ローカル座標と位置指定つき)
bpy.ops.transform.resize(
            value=val, 
            orient_type='LOCAL', 
            center_override = zero_p)

#data系
from mathutils import Vector
obj = bpy.context.object
sca = obj.scale
bpy.context.object.scale = [sc*va for sc,va in zip(sca,val)]
dist = (obj.location - zero_p)
obj.location=Vector([di*tr for di,tr in zip(dist,val)]) + zero_p  


#obj.scaleはmatrix_worldでも取得できる
obj.matrix_world.decompose()[2]

移動

import bpy

a = 1
b = 2
c = 3

#bpy.ops系
#グローバル
bpy.ops.transform.translate(value=(a, b, c))
#ローカル座標の方向に移動かつ移動量はグローバル値
bpy.ops.transform.translate(value=va,
                                orient_type='LOCAL',
                                orient_matrix_type='GLOBAL')

#ノーマルの方向に移動
bpy.ops.transform.translate(value=(0, 0, length_adj), orient_type='NORMAL')

#data系
#グローバル
obj = bpy.context.object
obj.location = [obj.location[0] + a , obj.location[1] + b , obj.location[2] + c]

#別件でimport mathutils してれば
va = mathutils.Vectol(a,b,c)
obj.location = obj.location + va

#あるいは
loc = [a,b,c]
obj.location = [x + l for (x, l) in zip(obj.location, loc)]


#ローカル座標の方向に移動かつ移動量はグローバル値
msh = obj.data
for v in msh.vertices:
        if v.select:
            v.co = [v.co.x+a/obj.scale[0], v.co.y+b/obj.scale[1], v.co.z+c/obj.scale[2]]

#ローカル座標の方向に移動し移動量はグローバル値、原点も一緒に移動

a = 0
b = 0
c = 1

val = [a,b,c]

msh = obj.data
va = [d/s for d,s in zip(val,obj.scale)]
mat = obj.matrix_world
obj.location = mat @ Vector(va)


#ノーマルの方向に移動(編集モード・面の場合のみ)
#オブジェクトモードで使用

import bpy
a = 0
b = 0
c = 5
import mathutils
obj = bpy.context.object
msh = obj.data
va = mathutils.Vector((a,b,c))
mat = obj.matrix_world
m = mat.copy()
for p in msh.polygons:
        if p.select:
            norm = p.normal
            mx_inv = mat.inverted()
            mx_norm = mx_inv.transposed().to_3x3()
            world_norm = mx_norm @ norm
            world_norm.normalize()
            m[0][2] = world_norm[0]
            m[1][2] = world_norm[1]
            m[2][2] = world_norm[2]
            gro = m @ va
            lo = mx_inv @ gro
            for id in p.vertices: 
                  msh.vertices[id].co = msh.vertices[id].co + lo

#obj.locationはmatrix_worldでも取得できる
obj.matrix_world.decompose()[0]

トランスフォームの適用

そこまでやらんでもいい気がするけど

#bpy.ops系(全適用)
import bpy
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)

#data系
obj = bpy.context.object
mw = obj.matrix_world
msh = bpy.data.meshes[obj.data.name]
for v in msh.vertices:
    v.co = mw @ v.co

obj.location = [0,0,0]
obj.rotation = [0,0,0]
obj.scale = [1,1,1]

#↓拡大縮小だけ適用する場合
#meshの頂点座標はローカル座標
#もっとシンプルな方法ありそうな気がする
for v in msh.vertices:
    verts=[]
    for s,vv in zip(obj.scale,v.co):
        verts.append(s*vv)
    v.co=verts
obj.scale = [1,1,1]

複製

思ったより面倒だった。
2.8系でコレクション機能が追加されたので注意。

import bpy

#bpy.ops系
bpy.ops.object.duplicate()

#data系
#指定した名前と同じ名前のオブジェクトが既にあると
#aaa.001という感じの名前になる
obj = bpy.context.object
co=bpy.context.collection

msh = bpy.data.meshes[obj.data.name].copy()
o = bpy.data.objects.new(name='aaa', object_data=msh)

co.objects.link(o)

#以下のようにするとコピー元のコレクションの外に
#コピペされてしまうので要注意
scene = bpy.context.scene
scene.collection.objects.link(o)

複製&移動

注意点としてbpy.ops系は選択(アクティブなモノではない)されたものに作用し新しく増やしたものが選択&アクティブになる。
data系は増えたものはアクティブ&選択にならない
ループで増やすときはどのオブジェクトがアクティブなのか気を付けなくてはならない

import bpy

a=1
b=2
c=3
bpy.ops.object.duplicate_move(TRANSFORM_OT_translate={"value":(a, b, c)})

#data系
src_obj = bpy.context.object
co = bpy.context.collection

new_obj  = src_obj.copy()
new_obj.data = src_obj.data.copy()

new_obj.location=[src_obj.location[0] + a , src_obj.location[1] + b , src_obj.location[2] + c]
co.objects.link(new_obj)
src_obj.select_set(False)
new_obj.select_set(True)
bpy.context.view_layer.objects.active=new_obj

全選択、全選択解除

複製ほどじゃないが2倍ほどdata系のほうが早い

import bpy

#bpy.ops系
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.select_all(action='DESELECT')

#data系
for obj in bpy.context.collection.objects:
        obj.select_set(True)
        
for obj in bpy.context.collection.objects:
        obj.select_set(False)

ペアレンツ

早い早くない以前に最後に思いついてペアレンツすることが多いので、選択状態にするのが面倒なときに使える

import bpy
#bpy.ops系 最後に選択したもの(アクティブなオブジェクト)が親になる
bpy.ops.object.parent_set(type='OBJECT')

#data系
#o.parent = bpy.context.active_objectだけだと子のオブジェクトが移動しちゃうから
selected_objects = bpy.context.selected_objects.copy()
for o in selected_objects:
   if o !=  bpy.context.active_object:
        o.parent = bpy.context.active_object
        o.matrix_parent_inverse = bpy.context.active_object.matrix_world.inverted() 

エンプティー生成

そうたくさん画面に増やすものじゃないがopsで1000個だすと12秒、data系だと0.059秒で200倍・・・
倍数マジックかもしないがとにかくopsは遅いんじゃ。

import bpy
#bpy.ops系
bpy.ops.object.empty_add(type='PLAIN_AXES', location=(0, 0, 0))

#data系
co=bpy.context.collection
mt = bpy.data.objects.new('empty', None)
co.objects.link(mt)
mt.empty_display_size = 2
#エンプティを矢印に変えたければ以下
mt.empty_display_type = 'ARROWS'  

せん断

せん断はそんなに遅くない
bpy.opsで1000回やって0.15秒、dataで0.01秒だった
複製(bpy.ops.object.duplicate)は100回やって0.4秒だったことを考えると複製だけがやけに遅いだけなのかも。

#bpy.ops系
shear_va=0.5
axis='X'
bpy.ops.transform.shear(value=shear_va,
                            orient_axis=axis,
                            orient_axis_ortho='Y',
                            orient_type='LOCAL',
                            orient_matrix_type='GLOBAL')

#data系
import mathutils
obj = bpy.context.object
mat_s = mathutils.Matrix.Shear('XY',4,(0,0.5/obj.scale[1]))

msh = bpy.context.object.data
for v in msh.vertices:
      if v.select == True:
           v.co = mat_s@v.co


↓bpy.opsがどのぐらい遅いかはこっち(実際に時間を測ってみた)
katsumi3.hatenablog.com