11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

BlenderAdvent Calendar 2019

Day 6

Blenderのデータ構造を辿り、fbxから頂点座標を抽出する

Last updated at Posted at 2019-12-05

 Blenderを使って、FBXデータから頂点座標のみを抜き出し配列化するスクリプトを作成したので、解説がてら紹介します。

Blenderのデータ構造

 まずは見慣れたこの図(初期状態)について。

image.png

 さて、Blenderの3Dビューは以下のような形で管理されています。

image.png

 Pythonスクリプトで言うと

bpy.context.scene.collection

 にあたるところです。

image.png

 GUIでいうとここですね。

 大元にシーン → コレクションがあり、コレクションの中にオブジェクトがあります。シーンとコレクションの違いはよくわかりません。ただ、オブジェクト(いわゆるキューブやカメラ等)はデータとして存在するものを、リンクという紐づけによってコレクションに呼んでいます。つまり、ビューに現れるのはリンクされたオブジェクトだけで、見えなくともデータとして存在するオブジェクトもありえる、ということです。こうすることで同じデータを使いまわせたりするようです。操作する方としてはややこしいですけど。

 そしてオブジェクトと同様に、それに付随するメッシュやマテリアルにもデータリストがあって、それをリンクすることによって必要な分だけ使う、ということになります。

 例えばここで、

bpy.data.objects.new("hoge", bpy.data.meshes.new("fuga"))

 という命令をすると、見かけ上のシーンには何も変化が起きませんが、

for i in bpy.data.objects:
    print(i)

 で、

Output
<bpy_struct, Object("Camera")>
<bpy_struct, Object("Cube")>
<bpy_struct, Object("hoge")>
<bpy_struct, Object("Light")>

 が返ってくるので、hogeという謎のオブジェクトが追加されていることがわかります。また、

bpy.data.objects['hoge'].to_mesh()

 で、

Output
bpy.data.meshes['fuga']

 と返ってくるので、このオブジェクトはfugaというメッシュにリンクされていることがわかります。もちろんこれはただ名前をつけただけの空データですので、今ここでhogeをコレクションにリンクしても、何も頂点を持たない名前だけの無が召喚されます。

 メッシュについて詳しく見ています。メッシュの頂点情報は以下のような形で管理されています。

image.png

 オブジェクトがメッシュをリンクしています。

image.png

 GUIでいうとここですね。

 メッシュデータが更に頂点データを格納しているので、頂点の情報が欲しい場合はそこにアクセスすることになります。今度はちゃんと形のあるキューブを対象にやっていきましょう。

bpy.data.objects['Cube'].to_mesh().vertices

 これは上の絵をほぼそのままなぞったものです。

Output
bpy.data.meshes['Cube'].vertices

 この中身を見ていくと、

for i in bpy.data.meshes['Cube'].vertices:
    print(i)
Output
<bpy_struct, MeshVertex at 0x0000024A9FA28038>
<bpy_struct, MeshVertex at 0x0000024A9FA2804C>
<bpy_struct, MeshVertex at 0x0000024A9FA28060>
<bpy_struct, MeshVertex at 0x0000024A9FA28074>
<bpy_struct, MeshVertex at 0x0000024A9FA28088>
<bpy_struct, MeshVertex at 0x0000024A9FA2809C>
<bpy_struct, MeshVertex at 0x0000024A9FA280B0>
<bpy_struct, MeshVertex at 0x0000024A9FA280C4>

 とひたすらバイナリデータを返されます。ちゃんと8個あるのでキューブで間違いなさそうですが、できれば数値を返してほしいところです。リファレンスを見るとcoという属性で数値が得られることがわかります。

for i in bpy.data.meshes['Cube'].vertices:
    print(i.co)
Output
<Vector (1.0000, 1.0000, 1.0000)>
<Vector (1.0000, 1.0000, -1.0000)>
<Vector (1.0000, -1.0000, 1.0000)>
<Vector (1.0000, -1.0000, -1.0000)>
<Vector (-1.0000, 1.0000, 1.0000)>
<Vector (-1.0000, 1.0000, -1.0000)>
<Vector (-1.0000, -1.0000, 1.0000)>
<Vector (-1.0000, -1.0000, -1.0000)>

 これでやっと頂点座標の配列が得られました。

#fbxに適用

 インポートしたfbxについても同じことをやっていきます。

image.png

 原理的には、すべてのメッシュデータに対して頂点を取り出せば上と同じ形が得られるはずです。

for i in bpy.data.meshes:
    print(i)
Output
<bpy_struct, Mesh("Cube")>
<bpy_struct, Mesh("Mesh")>
<bpy_struct, Mesh("Mesh.001")>
<bpy_struct, Mesh("Mesh.002")>
<bpy_struct, Mesh("Mesh.003")>
<bpy_struct, Mesh("Mesh.004")>
<bpy_struct, Mesh("Mesh.005")>
<bpy_struct, Mesh("Mesh.006")>
<bpy_struct, Mesh("Mesh.007")>

 ……。謎のCubeがあります。
 そう、Blenderを立ち上げた時に最初に存在するキューブ。あれはシーンから削除(正確にはアンリンク)しても中のデータは消えないのです。このままではキューブの頂点まで拾ってしまいます。消えてもらいましょう。

bpy.data.meshes.remove(bpy.data.meshes['Cube'])
for i in bpy.data.meshes:
    print(i)
Output
<bpy_struct, Mesh("Mesh")>
<bpy_struct, Mesh("Mesh.001")>
<bpy_struct, Mesh("Mesh.002")>
<bpy_struct, Mesh("Mesh.003")>
<bpy_struct, Mesh("Mesh.004")>
<bpy_struct, Mesh("Mesh.005")>
<bpy_struct, Mesh("Mesh.006")>
<bpy_struct, Mesh("Mesh.007")>

 
 消えました。
 後はこれらのメッシュデータすべてに対し頂点座標を抽出するようなスクリプトを書きます。

import bpy
import numpy as np

vts = []

for m in bpy.data.meshes:
    for v in m.vertices:
        vts.append(v.co)

vts = np.array(vts)

 coで得られる数字がVectorという型なので、扱いやすくするためnp.arrayで変換しています。

 ここで得られた配列が、本当に元の形を表しているのかを検証します。頂点が3次元情報を正確に保持しているなら、単に散布図にするだけで元の形を表してくれるはずです。

 先ほど得られた配列vts

np.savetxt(r'任意のパス\vts.txt', vts)

 でエクスポートします。別のPythonファイルで(Blenderの内蔵Pythonにはmatplotlibがないため……)散布図を描画します。

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

data = np.loadtxt('vts.txt')

X = data[:,0]
Y = data[:,1]
Z = data[:,2]

fig = plt.figure()
ax = Axes3D(fig)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
ax.plot(X,Y,Z,marker="o",linestyle='None',ms=0.2)

plt.show()

image.png

 元の3Dデータの頂点を正確に配列化できていることがわかりました。

11
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
11
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?