Blender 2.6 の Export plug-in を作る (2) データアクセス

Blender の API は bpy で始まり、データアクセス手段は
bpy.data 以下に集約されています。

例えばシーン内のオブジェクトは bpy.data.objects で取れます。
下記のように Python Console で確認できます。
(太字がキー入力です)

>>> bpy.data.objects['Cube']
bpy.data.objects['Cube']

>>> bpy.data.objects.keys()
['Camera', 'Cube', 'Lamp']

>>> bpy.data.objects.values()
[bpy.data.objects['Camera'], bpy.data.objects['Cube'], bpy.dta.objects['Lamp']]

↑Python の辞書(連想配列)のように見えますがちょっと違います。

>>> bpy.data.objects


>>> bpy.data.objects[0]
bpy.data.objects['Camera']

>>> bpy.data.objects.find('Cube')
1

>>> bpy.data.objects[1:]
[bpy.data.objects['Cube'], bpy.dta.objects['Lamp']]

↑数値 index でもアクセス可能で要素の順番が固定。
find() を使うと index を取ることが可能です。
特に for in では要素が列挙され、配列のような振る舞いをします。

>>> for a in bpy.data.objects: print( a )



key を持つがシーケンスとして扱える特殊な collection となっています。
python に慣れてないせいもありますが、
辞書のつもりで script を読んでいると少々混乱します。

● Object Type

Object の種類は type プロパティで確認できます。

>>> for a in bpy.data.objects: print( a.type )
CAMERA
MESH
LAMP

bpy.types にデータの種類一覧があります。

Types (bpy.types)

この bpy.types が Maya でいう Node に相当するもののようです。
例えば ‘Cube’ は bpy.types.Mesh で、独自のインスタンスととデータは
Object.data からアクセスします。

>>> bpy.data.objects['Cube'].data.vertices[1].co
Vector((1.0, -1.0, -1.0))

Object 自体が階層構造を持てるので DagNode を兼ねています。
Maya ではインスタンスである Object とメソッドでである Function が
分離していましたが、Blender はそのようなことがなくシンプルです。

Object の階層は下記のようにたどることができます。
Maya のように Transform Node が別だったりしません。

def tree( obj ):
  print( obj )
  print( obj.matrix_local )
  for child in obj.children:
    tree( child )

def root():
  for obj in bpy.data.objects:
    if not obj.parent:
      tree( obj )

root()

root の object だけ得るには
[obj for obj in bpy.data.objects if not obj.parent]
で出来ます。

bpy.data.objects はすべてのオブジェクトを列挙しますが、
同時に data type ごとの collection も持っているようです。

>>> bpy.data.objects.keys()
['Camera', 'Cube', 'Lamp', 'Sphere', 'Suzanne']

>>> bpy.data.meshes.keys()
['Cube', 'Sphere', 'Suzanne']

bpy.data.meshes に入っているのはデータそのもの、Object.data です。
こちらは階層などの Node 情報を含んでいません。

>>> bpy.data.meshes['Cube'].vertices[1].co
Vector((1.0, -1.0, -1.0))

Object を削除しても Mesh は残っているようです。
この辺の Blender の内部挙動はまだ私の方がきちんと理解しておりません。

matrix_local はドキュメント Object(ID) では float array
と書かれてますが mathutils.Matrix を返すようです。

● Mesh のアクセス方法

Mesh には形状データが入っています。
頂点データ自体は Mesh.vertices ですが、下記ページの解説にあるように
edge, loop, polygon と 3種類のインデックスがあります。

Mesh(ID)

・MeshVertex : 頂点データそのもの
・MeshEdge : 2頂点の index
・MeshLoop : 1頂点 + edge の index
・MeshPolygon : n個の loop の index

mesh= Suzanne




mesh= Cube




mesh= Sphere




Mesh 内の MeshPolygon (Face) の列挙は下記の通り。

def shape( obj ):
  mesh= obj.data
  for poly in mesh.polygons:
    print( poly )

MeshPolygon に含まれる頂点 (Face Vertex) を得るには下記の通り。

# Loop を使った場合
def shape( obj ):
  mesh= obj.data
  for poly in mesh.polygons:
    for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):
      print( mesh.verticies[ mesh.loops[ id ].vertex_index ].co )

MeshPolygon に含まれる MeshLoop をたどって MeshLoop.vertex_index を
参照しています。
なお、Loop を使わなくても MeshPolygon.vertices で直接頂点 index を
取れることがわかりました。

def shape( obj ):
  mesh= obj.data
  for poly in mesh.polygons:
    for ver in poly.vertices:
      print( mesh.vertices[ ver ].co )

ただし、mesh.loops の参照用 index 値 (「Loop を使った場合」の id ) は
uv など他のデータアクセスにも使います。
loop は Face Vertex 単位のデータとなり、Cube の場合 24 個あります。
一見複雑ですが「Loop を使った場合」の方が都合が良いようです。

● 法線

頂点データ MeshVertex に直接頂点法線が格納されています。
頂点座標が共有される場合法線も共有されており、この値は
ハードエッジになりません。

頂点とは別に MeshPolygon に面法線が格納されており、プロパティ
Mesh.use_smooth の値によってこの両者を使い分ける必要があるようです。

● UV / 頂点カラー

Mesh.uv_layers に格納されます。値そのものは MeshUVLoop.uv です。
uv_layers が MeshUVLoopLayer で data に MeshUVLoop が頂点分 (Loop 数分)
格納されます。

>>> len(bpy.data.objects['Cube'].data.uv_layers)
1

>>> len(bpy.data.objects['Cube'].data.uv_layers[0].data)
24

>>> bpy.data.objects['Cube'].data.uv_layers[0].data[0].uv
Vector((0.0), 0.0))

>>> bpy.data.objects['Cube'].data.uv_layers.active.data[0].uv
Vector((0.0), 0.0))

頂点カラーも全く同じで Mesh.vertex_colors に入ります。
今までの情報を取り出すコードがこれです。

def shape( obj ):
  print( 'mesh=', obj.name )
  mesh= obj.data
  uv_array= mesh.uv_layers.active.data if len(mesh.uv_layers) else None
  vcolor_array= mesh.vertex_colors.active.data if len(mesh.vertex_colors) else None
  for poly in mesh.polygons:
    for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):
      vertex= mesh.vertices[ mesh.loops[ id ].vertex_index ]
      position= vertex.co
      normal= vertex.normal
      if not poly.use_smooth:
        normal= poly.normal
      print( 'pos=', position )
      print( 'normal=', normal )
      if uv_array:
        uv= uv_array[id].uv
        print( 'uv=', uv )
      if vcolor_array:
        vcolor= vcolor_array[id].color
        print( 'vcolor=', vcolor )


def tree( obj ):
  if obj.type == 'MESH':
      shape( obj )
  #print( 'matrix=', obj.matrix_local )
  for child in obj.children:
    tree( child )

def root():
  for obj in bpy.data.objects:
    if not obj.parent:
      tree( obj )

root()

それぞれ最初の(active な) UV set と VertexColor しか参照していないので注意してください。
uv_array, vcolor_array の参照に Loop と同じ id を使っていることがわかります。
Matrix 同様、ドキュメントではただの array ですが実際に返す値は
mathutils.Vector, mathutils.Color となっています。

続きます

関連エントリ
Blender 2.6 の Export plug-in を作る (1)

Blender 2.6 の Export plug-in を作る (2) データアクセス」への2件のフィードバック

  1. satoren

    BlenderのAPI仕様は頻繁に変わるので、COLLADAなどで出力したものを変換する方が良いのではないかと思ってます。

    2.4系の頃に作ったプラグインが2.6で動かなくなったときは仕方ないかと
    思いましたが、2.6.3で作ったものが2.6.4で動かなくなった時は愕然としました。たしかUVのメンバの位置が変わってたりしました。
    頻繁な変更はプラグイン含めてオープンソースだから出来ることなんでしょうが、公開しないプラグインでこれについて行くのは煩わしかったです。
    出力前にBlenderのAPIで色々変換できたりメリットもあるんですが…

  2. oga 投稿作成者

    すみませんコメント見ていませんでした。
    そうですか、、仕様が頻繁に変わるのはたいへんですね。
    今ツール選定中なので、blender はいったんとめておきます。
    satoren さんありがとうございました。

コメントは停止中です。