Python
Blender
Mesh

Blender PythonのMeshデータアクセスのチートシート

Blender Pythonによるメッシュデータへのアクセスの方法をまとめて行きます。

調べるのはそんなに大変ではないのですが、あまりまとまった情報がないので、チートシート的に一覧できるようにまとめていきます。ここに記載している方法が速度的に最適な方法である保証はありません。他にもっと良い手段があれば是非教えてください。

以下では、cubeを作成し、それらにUV, Vertex Color, Shape key, Skin Weight(Vertex Group)を設定するサンプルコードをまとめたいと思います。また、それらに設定されたデータをprintするコードとログも記載しています。

追記(2019/04/03):

BlenderのVersionが2.80になり、APIが一部変更され動かなくなっていたので、修正しました。

基本的には、下記の4点。

1. Object, Meshなどのデータをnewする際、引数のnameキーワードが省略できないようになったので、明記するようにしました。あと、object_dataも。

2. collectionの概念が増えたため、bpy.context.scene.objectsbpy.context.scene.collection.objectsに修正しました。

3. mesh.uv_texturesが使えなくなったので、uv_layersに変更しました。

4. colorクラスの初期化でalphaが省略できなくなっていたので、alphaまで明記するように修正しました。

下記のソースコードを修正し、これまでのコードはコメントアウトで残しています。


メッシュの作成

メッシュの作成には、編集モードに入りBMeshとして作成する方法もありますが、オブジェクトモードをで作成する方法をまとめます。


create_mesh.py

import bpy

verts = [[-1.0, -1.0, 1.0], [1.0, -1.0, 1.0], [1.0, 1.0, 1.0], [-1.0, 1.0, 1.0],
[-1.0, -1.0, -1.0], [1.0, -1.0, -1.0], [1.0, 1.0, -1.0], [-1.0, 1.0, -1.0], ]
faces = [[0,1,2,3], [0,4,5,1], [1,5,6,2], [2,6,7,3], [0,3,7,4], [4,7,6,5]]

msh = bpy.data.meshes.new(name="cubemesh")
#msh = bpy.data.meshes.new("cubemesh")
msh.from_pydata(verts, [], faces)
msh.update()
obj = bpy.data.objects.new(name="cube", object_data=msh)
#obj = bpy.data.objects.new("cube", msh)
scene = bpy.context.scene
#scene.objects.link(obj)
scene.collection.objects.link(obj)


流れとしては、

1. meshes.newでMeshデータのインスタンスを作成

2. from_pydataで頂点座標と各面の頂点Indexの配列をで登録しメッシュを作成

3. msh.update()でメッシュの変更を反映。from_pydataを呼んでいる場合は必要ないかも。

4. objects.newでObjectを作成するが、その際に先ほど作ったメッシュデータを引数に渡す。

5. 最後にscene.objects.linkで作成したオブジェクトをシーンにリンクを忘れずに。

from_pydataの中央の引数の[]部分はedge情報を設定します。なければFaceの情報から自動計算されますので、面が生成されるメッシュを作る場合には空の配列で問題ありません。Blenderは面のないメッシュも作れますので、その場合はedgeを指定して、FaceのIndexの指定を空にすることができます。面のないメッシュはリグのコントローラ用のメッシュなどそれなりにスクリプトで作成したくなる場合はあると思います。


頂点データへのアクセス


  • verticesプロパティ


    • index : 頂点のIndex

    • co : 頂点の座標

    • normal : 頂点法線



# 頂点情報の列挙

print("num of vertices:", len(msh.vertices))
for vt in msh.vertices:
print("vertex index:{0:2} co:{1} normal:{2}".format(vt.index, vt.co, vt.normal))


出力

num of vertices: 8

vertex index: 0 co:<Vector (-1.0000, -1.0000, 1.0000)> normal:<Vector (-0.5773, -0.5773, 0.5773)>
vertex index: 1 co:<Vector (1.0000, -1.0000, 1.0000)> normal:<Vector (0.5773, -0.5773, 0.5773)>
vertex index: 2 co:<Vector (1.0000, 1.0000, 1.0000)> normal:<Vector (0.5773, 0.5773, 0.5773)>
vertex index: 3 co:<Vector (-1.0000, 1.0000, 1.0000)> normal:<Vector (-0.5773, 0.5773, 0.5773)>
vertex index: 4 co:<Vector (-1.0000, -1.0000, -1.0000)> normal:<Vector (-0.5773, -0.5773, -0.5773)>
vertex index: 5 co:<Vector (1.0000, -1.0000, -1.0000)> normal:<Vector (0.5773, -0.5773, -0.5773)>
vertex index: 6 co:<Vector (1.0000, 1.0000, -1.0000)> normal:<Vector (0.5773, 0.5773, -0.5773)>
vertex index: 7 co:<Vector (-1.0000, 1.0000, -1.0000)> normal:<Vector (-0.5773, 0.5773, -0.5773)>


エッジへのアクセス


  • edgesプロパティ


    • index : エッジのIndex

    • vertices : 隣接頂点のIndex



print("num of edges:", len(msh.edges))

for ed in msh.edges:
print("edge index:{0: 2} v0:{0} v1:{1}".format(ed.index, ed.vertices[0], ed.vertices[1]))


出力

num of edges: 12

edge index: 0 v0: 4 v1: 5
edge index: 1 v0: 3 v1: 7
edge index: 2 v0: 6 v1: 7
edge index: 3 v0: 2 v1: 6
edge index: 4 v0: 5 v1: 6
edge index: 5 v0: 0 v1: 1
edge index: 6 v0: 4 v1: 7
edge index: 7 v0: 1 v1: 2
edge index: 8 v0: 1 v1: 5
edge index: 9 v0: 2 v1: 3
edge index:10 v0: 0 v1: 3
edge index:11 v0: 0 v1: 4


面のアクセス

facesではなく、polygonsプロパティです。facesというプロパティ名のソフトの方が多く、私はついfacesでアクセスしようとしてあれっ?となるので強調してます。


  • polygonsプロパティ


    • index : ポリゴンのIndex

    • vertices : 隣接頂点のIndex

    • loop_start : このポリゴンのエッジや頂点をiterateするための開始loopデータのIndex(Meshがloopsプロパティを持っており、そこへのIndex)

    • loop_total : ループの数、実質、ポリゴンの頂点数

    • loop_indices : ループのindexのリスト




取得

print("num of polygons:", len(msh.polygons))

for pl in msh.polygons:
print("polygon index:{0:2} ".format(pl.index), end="")
print("vertices:", end="")
for vi in pl.vertices:
print("{0:2}, ".format(vi), end="")
print("")

for pl in msh.polygons:
print("polygon index:{0:2} ".format(pl.index))
print(" > loops:", end="")
print(" total:", pl.loop_total, end="")
print(" start:", pl.loop_start, end="")
print(" indices:", end="")
for lp in pl.loop_indices:
print("{0:2}, ".format(lp), end="")
print("")



出力

num of polygons: 6

polygon index: 0 vertices: 0, 1, 2, 3,
polygon index: 1 vertices: 0, 4, 5, 1,
polygon index: 2 vertices: 1, 5, 6, 2,
polygon index: 3 vertices: 2, 6, 7, 3,
polygon index: 4 vertices: 0, 3, 7, 4,
polygon index: 5 vertices: 4, 7, 6, 5,
polygon index: 0
> loops: total: 4 start: 0 indices: 0, 1, 2, 3,
polygon index: 1
> loops: total: 4 start: 4 indices: 4, 5, 6, 7,
polygon index: 2
> loops: total: 4 start: 8 indices: 8, 9, 10, 11,
polygon index: 3
> loops: total: 4 start: 12 indices:12, 13, 14, 15,
polygon index: 4
> loops: total: 4 start: 16 indices:16, 17, 18, 19,
polygon index: 5
> loops: total: 4 start: 20 indices:20, 21, 22, 23,


Loopについて

頂点とエッジのIndexを管理するデータです。ポリゴンの周囲のエッジをたどりたい場合に一応使えます。ですが、きちんと頂点、エッジ、ポリゴンの隣接関係をたどりたいなら、Editモードになり、Bmeshを使う方がよいので、こんなプロパティがあるのだなぁ、ぐらいでよいと思います。

print("num of loops:", len(msh.loops))

for lp in msh.loops:
print("loop index:{0:2} vertex index:{1:2} edge index:{2:2}".format(lp.index, lp.vertex_index, lp.edge_index))


出力

num of loops: 24

loop index: 0 vertex index: 0 edge index: 5
loop index: 1 vertex index: 1 edge index: 7
loop index: 2 vertex index: 2 edge index: 9
loop index: 3 vertex index: 3 edge index:10
loop index: 4 vertex index: 0 edge index:11
loop index: 5 vertex index: 4 edge index: 0
loop index: 6 vertex index: 5 edge index: 8
loop index: 7 vertex index: 1 edge index: 5
loop index: 8 vertex index: 1 edge index: 8
loop index: 9 vertex index: 5 edge index: 4
loop index:10 vertex index: 6 edge index: 3
loop index:11 vertex index: 2 edge index: 7
loop index:12 vertex index: 2 edge index: 3
loop index:13 vertex index: 6 edge index: 2
loop index:14 vertex index: 7 edge index: 1
loop index:15 vertex index: 3 edge index: 9
loop index:16 vertex index: 0 edge index:10
loop index:17 vertex index: 3 edge index: 1
loop index:18 vertex index: 7 edge index: 6
loop index:19 vertex index: 4 edge index:11
loop index:20 vertex index: 4 edge index: 6
loop index:21 vertex index: 7 edge index: 2
loop index:22 vertex index: 6 edge index: 4
loop index:23 vertex index: 5 edge index: 0


UVの設定

UVの設定、取得はシンプルで、UVのチャンネルを追加し、そのチャンネルをキーにUVの配列にアクセスするだけです。注意するところは、GUIのUV Mapsに表示されるチャンネルは、Meshのuv_texturesというプロパティで管理されているが、実際のUVの座標情報はuv_layersというプロパティで保持している点ぐらいです。

3dsMaxのようにUVのチャンネル毎に異なるトポロジーは保持しません。なので、各Layerに保持されているUV座標の数は、常にすべてのポリゴンの頂点数の合計となります。Blenderは、weld、溶接といった概念はデータの中では存在しないようです。

>>> len(msh.uv_layers[channel_name].data.items())

24

# UV設定

tmp = [[0.0, 0.0], [0.5, 0.0], [0.5, 0.5], [0.0, 0.5]]
uvs = tmp * 6 # 設定するUV座標の初期化
channel_name = "uv0" # UVのチャンネル名
msh.uv_layers.new(name=channel_name) # UV Channelの作成
#msh.uv_textures.new(channel_name) # UV Channelの作成
for idx, dat in enumerate(msh.uv_layers[channel_name].data): # uv layerの配列でiterate
dat.uv = uvs[idx]


データの確認

print("num of uv layers:", len(msh.uv_layers))

#print("num of uv layers:", len(msh.uv_textures))
for ly in msh.uv_layerss:
#for ly in msh.uv_textures:
print(ly.name)
for idx, dat in enumerate(msh.uv_layers[ly.name].data):
print(" {0}:{1}".format(idx, dat.uv))
print("")


出力

uv0

0:<Vector (0.0000, 0.0000)>
1:<Vector (0.5000, 0.0000)>
2:<Vector (0.5000, 0.5000)>
3:<Vector (0.0000, 0.5000)>
4:<Vector (0.0000, 0.0000)>
5:<Vector (0.5000, 0.0000)>
6:<Vector (0.5000, 0.5000)>
7:<Vector (0.0000, 0.5000)>
8:<Vector (0.0000, 0.0000)>
9:<Vector (0.5000, 0.0000)>
10:<Vector (0.5000, 0.5000)>
11:<Vector (0.0000, 0.5000)>
12:<Vector (0.0000, 0.0000)>
13:<Vector (0.5000, 0.0000)>
14:<Vector (0.5000, 0.5000)>
15:<Vector (0.0000, 0.5000)>
16:<Vector (0.0000, 0.0000)>
17:<Vector (0.5000, 0.0000)>
18:<Vector (0.5000, 0.5000)>
19:<Vector (0.0000, 0.5000)>
20:<Vector (0.0000, 0.0000)>
21:<Vector (0.5000, 0.0000)>
22:<Vector (0.5000, 0.5000)>
23:<Vector (0.0000, 0.5000)>


Vertex Color

vertex_colorsプロパティ経由で簡単にアクセスできます。


設定

# 6面をRed, Green, Blue, Cyan, Magenta, Yellowで塗ってみる

#colormaps = [[1.0,0.0,0.0]]*4+[[0.0,1.0,0.0]]*4+[[0.0,0.0,1.0]]*4+[[0.0,1.0,1.0]]*4+[[1.0,0.0,1.0]]*4+[[1.0,1.0,0.0]]*4
colormaps = [[1.0,0.0,0.0,1.0]]*4+[[0.0,1.0,0.0,1.0]]*4+[[0.0,0.0,1.0,1.0]]*4+[[0.0,1.0,1.0,1.0]]*4+[[1.0,0.0,1.0,1.0]]*4+[[1.0,1.0,0.0,1.0]]*4

print("colormaps:", colormaps)
msh.vertex_colors.new(name='col')
# msh.vertex_colors.new('col')
for idx, vc in enumerate(msh.vertex_colors['col'].data):
vc.color = colormaps[idx]



データの取得

# Vertex Colorの表示

print("num of vertex color layers:", len(msh.vertex_colors))
for ly in msh.vertex_colors:
print(ly.name)
for idx, vc in enumerate(msh.vertex_colors['col'].data):
print(" {0:2}:{1}".format(idx,vc.color))


出力

num of vertex color layers: 1

col
0:<Color (r=1.0000, g=0.0000, b=0.0000)>
1:<Color (r=1.0000, g=0.0000, b=0.0000)>
2:<Color (r=1.0000, g=0.0000, b=0.0000)>
3:<Color (r=1.0000, g=0.0000, b=0.0000)>
4:<Color (r=0.0000, g=1.0000, b=0.0000)>
5:<Color (r=0.0000, g=1.0000, b=0.0000)>
6:<Color (r=0.0000, g=1.0000, b=0.0000)>
7:<Color (r=0.0000, g=1.0000, b=0.0000)>
8:<Color (r=0.0000, g=0.0000, b=1.0000)>
9:<Color (r=0.0000, g=0.0000, b=1.0000)>
10:<Color (r=0.0000, g=0.0000, b=1.0000)>
11:<Color (r=0.0000, g=0.0000, b=1.0000)>
12:<Color (r=0.0000, g=1.0000, b=1.0000)>
13:<Color (r=0.0000, g=1.0000, b=1.0000)>
14:<Color (r=0.0000, g=1.0000, b=1.0000)>
15:<Color (r=0.0000, g=1.0000, b=1.0000)>
16:<Color (r=1.0000, g=0.0000, b=1.0000)>
17:<Color (r=1.0000, g=0.0000, b=1.0000)>
18:<Color (r=1.0000, g=0.0000, b=1.0000)>
19:<Color (r=1.0000, g=0.0000, b=1.0000)>
20:<Color (r=1.0000, g=1.0000, b=0.0000)>
21:<Color (r=1.0000, g=1.0000, b=0.0000)>
22:<Color (r=1.0000, g=1.0000, b=0.0000)>
23:<Color (r=1.0000, g=1.0000, b=0.0000)>


Shape Key

基本的に、UVの設定と同じです。UVとは異なり、Meshのshape_keysプロパティがShape Keyの管理と頂点データの管理の両方を担当しています。実際にデータを持っているのはshape_keysプロパティのkey_blocksというプロパティです。


設定


# Cubeの作成時に定義したverts変数に(0,0,1), (0,0,-1)を加算したものを、それぞれMoveUp, MoveDownキーとする
shapemaps = {'MoveUp':[[v[0],v[1],v[2]+1.0] for v in verts],
'MoveDown':[[v[0],v[1],v[2]-1.0] for v in verts],}

# 一つもShapeがない場合はBasisキーを作成
obj.shape_key_add()
msh.shape_keys.key_blocks[-1].name = "Basis"

for sname in shapemaps:
lst = shapemaps[sname]
obj.shape_key_add()
kb = msh.shape_keys.key_blocks[-1]
kb.name = sname
for idx, co in enumerate(lst):
kb.data[idx].co = co



データの取得

# Shape Keyの取得

print("num of Shape Keys:", len(msh.shape_keys.key_blocks))
for kb in msh.shape_keys.key_blocks:
print(" Key Block:", kb.name)
for idx, dat in enumerate(kb.data):
print(" {0}:{1}".format(idx, dat.co))



出力

num of Shape Keys: 3

Key Block: Basis
0:<Vector (-1.0000, -1.0000, 1.0000)>
1:<Vector (1.0000, -1.0000, 1.0000)>
2:<Vector (1.0000, 1.0000, 1.0000)>
3:<Vector (-1.0000, 1.0000, 1.0000)>
4:<Vector (-1.0000, -1.0000, -1.0000)>
5:<Vector (1.0000, -1.0000, -1.0000)>
6:<Vector (1.0000, 1.0000, -1.0000)>
7:<Vector (-1.0000, 1.0000, -1.0000)>
Key Block: MoveDown
0:<Vector (-1.0000, -1.0000, 0.0000)>
1:<Vector (1.0000, -1.0000, 0.0000)>
2:<Vector (1.0000, 1.0000, 0.0000)>
3:<Vector (-1.0000, 1.0000, 0.0000)>
4:<Vector (-1.0000, -1.0000, -2.0000)>
5:<Vector (1.0000, -1.0000, -2.0000)>
6:<Vector (1.0000, 1.0000, -2.0000)>
7:<Vector (-1.0000, 1.0000, -2.0000)>
Key Block: MoveUp
0:<Vector (-1.0000, -1.0000, 2.0000)>
1:<Vector (1.0000, -1.0000, 2.0000)>
2:<Vector (1.0000, 1.0000, 2.0000)>
3:<Vector (-1.0000, 1.0000, 2.0000)>
4:<Vector (-1.0000, -1.0000, 0.0000)>
5:<Vector (1.0000, -1.0000, 0.0000)>
6:<Vector (1.0000, 1.0000, 0.0000)>
7:<Vector (-1.0000, 1.0000, 0.0000)>


Vertex Group

一番単純なデータかと思いきや、一番面倒くさいデータ構造です。

Blenderは、Skin WeightをこのVertex Groupで保持します。

なので、頂点毎にVertex GroupのIndexをキー、Weightを値として持つ連想配列としてデータが保持されています。UVなどと近い感覚で設定できるように、Vertex Groupの構造体にVertex Weightを設定する関数が用意されています。処理速度が速くはないと思いますが。


設定

bone_list = ["bone1", "bone2"]

weight_map = {"bone1":[1.0]*4+[0.0]*4, # [1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0],
"bone2":[0.0]*4+[1.0]*4,
}

vg_list = [] # VertexGroupのリスト
# vertex groupの作成
for bname in bone_list:
obj.vertex_groups.new(name=bname)
#obj.vertex_groups.new(bname)
vg_list.append(obj.vertex_groups[-1])

# VertexGroup経由での設定
for vg in vg_list:
weights = weight_map[vg.name]
for vidx, w in enumerate(weights):
if w != 0.0:
vg.add([vidx], w, 'REPLACE')


データの取得は頂点のプロパティ経由で行ってみます。


データの取得

# Vertexのgroupsプロパティ経由のWeightの取得

msh = obj.data
for v in msh.vertices:
for vge in v.groups:
print("vindex:{0} group index:{1} weight:{2}".format(v.index, vge.group, vge.weight))


出力

vindex:0 group index:0 weight:1.0

vindex:1 group index:0 weight:1.0
vindex:2 group index:0 weight:1.0
vindex:3 group index:0 weight:1.0
vindex:4 group index:1 weight:1.0
vindex:5 group index:1 weight:1.0
vindex:6 group index:1 weight:1.0
vindex:7 group index:1 weight:1.0


おまけ:foreach_setについて

uv_layerのdataプロパティのようなcollectionデータは、foreach_setという関数を持っており、配列で一発で値を設定することができます。普通に配列をiterateして1つ1つアサインするより速いかどうかは未検証です。

この方法の場合は、上記の配列の配列ではなく、それをflatにした普通の配列を渡すことになる点に注意が必要です。

tmp = [0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.0, 0.5]

uvs = tmp * 6 # 設定するUV座標の初期化
channel_name = "uv0"
msh.uv_textures.new(channel_name)
msh.uv_layers[channel_name].data.foreach_set("uv", uvlist)

shapemaps = {'MoveUp':[[v[0],v[1],v[2]+1.0] for v in verts],

'MoveDown':[[v[0],v[1],v[2]-1.0] for v in verts],}
for sname in shapemaps:
lst = shapemaps[sname]

lst2 = list(chain.from_iterable(lst)) # 配列の配列をflatにする方法ではこれが高速らしい
obj.shape_key_add()
sk = msh.shape_keys.key_blocks[-1]
sk.name = sname
sk.data.foreach_set("co", lst2)