追加しました。
その他の表はこちらから
●Bluetooth
前回の記事では未対応と書きましたが、その直後 2013/02/02 以降の ROM で
Bluetooth が使えるようになっています。
・ubuntu wiki: Using the Device
給電しながら外部キーボードやマウスが使えるようになりました。
●Nexus 7 に Android 端末をつなぐ
SDK を install できるわけではありませんが adb は使えます。
sudo apt-get install android-tools-adb
USB Host に他の Android 端末をつないで adb で接続できます。
端末によっては充電しようとするので、セルフパワー(ACアダプタ付き) USB ハブを
経由した方が良いと思います。

↑HTC J butterfly HTL21 をつないで “adb devices” や “adb shell” 等が可能です。
Host の Nexus 7 (RAM 1GB A9 1.2GHz) よりも butterfly (RAM 2GB Krait 1.5GHz)
の方が性能が高いので本当は逆にしたいところです。
●3D / OpenGL ES 2.0
OpenGL でないため GLX は動作しないものの、
描画には OpenGL ES v2.0 + EGL が使われています。
↑上の NVIDIA サイトのドキュメントにあるように mesa-utils-extra を入れると
OpenGL ES 2.0 を使ったアプリの動作を確認できます。
sudo apt-get install mesa-utils-extra /usr/bin/es2gears

glxinfo の代わりに es2_info コマンドでドライバの確認ができるようです。
WebGL も試しましたが、Firefox/Chromium とも動作しませんでした。
フラグ自体は有効に出来ます。
●software
Software center の Installed tab が開きませんが、
Unity の dash 画面でアプリケーションの uninstall ができます。
Launcher 一番上のボタンで dash 画面を開いてタブを切り替え、
Installed の中でアイコンを右クリックです。
今までテストに使用してきたプログラムが安定して動作しないため
Adreno 320 では速度の計測ができていませんでした。
LG Optimus G LGL21 を借りることができたのでもう一度テストしてみました。
・Mobile GPU ベンチ OpenGL ES 2.0
・CPU benchmark
・OpenGL ES Extension (Mobile GPU)
GPU bench の (1) 以外は 60fps に達するため数値が出ていません。
GPU 性能が上がったため、もはやこのテストは計測に向いていないことになります。
また Adreno 320 は OpenGL ES 3.0 世代の GPU ですが、
現在の OS から使う限り API は OpenGL ES 2.0 になります。
GPU 性能をまだ引き出せていない可能性があります。
テストプログラムのエラーの原因は特定のケースでシェーダーのコンパイルが
エラーになるもので、回避方法はわかったもののまだ詳しい条件が
特定できていません。
VFP test では HTC J HTL21 よりも良い結果が出ています。
(1) (2) (3) (4) (5) (6)
iPad3 Touch5 EVO 3D iPad4 Butterfly OptimusG
A5X A5 8660 A6X 8064 8064
C-A9 C-A9 Scorpion Swift Krait Krait
----------------------------------------------------------------
a:mat44 neon_AQ 4.784 5.979 2.879 1.204 1.919 1.354
b:mat44 neon_BQ 2.408 3.008 1.146 1.266 1.273 0.930
c:mat44 neon_AD 4.781 5.974 3.079 1.554 2.453 1.862
d:mat44 neon_BD 2.406 3.007 1.440 1.344 2.041 1.521
e:fadds A 4.010 5.013 3.460 2.882 3.791 2.831
f:fmuls A 4.010 5.012 4.361 2.953 3.671 2.793
g:fmacs A 4.012 5.011 4.034 5.763 7.334 5.599
h:vfma.f32 A ----- ----- ----- 5.765 3.725 2.743
i:vadd.f32 D A 4.111 5.136 3.493 2.877 3.706 2.724
j:vmul.f32 D A 4.110 5.136 3.502 2.950 3.667 2.767
k:vmla.f32 D A 4.512 5.650 3.638 2.951 7.557 5.462
l:vadd.f32 Q A 8.023 10.036 3.408 2.878 3.677 2.717
m:vmul.f32 Q A 8.022 10.028 3.427 2.952 3.647 2.741
n:vmla.f32 Q A 8.025 10.028 3.400 2.955 7.362 5.446
o:vfma.f32 D A ----- ----- ----- 2.494 3.676 2.709
p:fadds B 4.014 5.013 5.972 5.757 4.664 3.388
q:fmuls B 5.013 6.265 5.960 5.760 4.583 3.384
r:fmacs B 8.023 10.024 8.573 11.521 8.266 6.246
s:vfma.f32 B ----- ----- ----- 11.519 4.611 3.622
t:vadd.f32 D B 4.113 5.137 5.945 2.881 4.746 3.406
u:vmul.f32 D B 4.118 5.145 5.098 2.951 4.680 3.454
v:vmla.f32 D B 9.027 11.278 8.498 5.757 8.361 6.140
w:vadd.f32 Q B 8.021 10.023 5.950 2.879 4.702 3.481
x:vmul.f32 Q B 8.029 10.023 5.095 2.951 4.595 3.412
y:vmla.f32 Q B 9.026 11.277 8.497 5.762 8.464 6.249
z:vfma.f32 D B ----- ----- ----- 5.759 4.660 3.413
---------------------------------------------------------------
↑数値は実行時間(秒) 数値が小さい方が速い
(1)=Apple iPad 3 A5X ARM Cortex-A9 x2 1.0GHz
(2)=Apple iPod touch 5 A5 ARM Cortex-A9 x2 0.8GHz
(3)=HTC EVO 3D ISW12HT MSM8660 Scorpion x2 1.2GHz
(4)=Apple iPad 4 A6X A6 (swift) x2 ?GHz
(5)=HTC J butterfly HTL21 APQ8064 Krait x4 1.5GHz
(6)=LG Optimus G LGL21 APQ8064 Krait x4 1.5GHz
Krait の FP 性能は Swift に並ぶ新 core であることがよくわかります。
前回のテスト時はクロックが上限まで上がっていなかった可能性があります。
HTC J butterfly が修理から戻ってきたらもう一度計測してみたいと思っています。
スキニング用 weight/group は頂点(MeshVertex)への追加情報でした。
同じように MeshPolygon (Face) にはマテリアル情報が含まれています。
Table Table の IDが含まれる
------------------------------------------------------------
group Object.vertex_groups MeshVertex.groups[n].group
material Mesh.materials MeshPolygon.material_index
Maya では Object Material と Face Material の両方を参照する必要が
ありますが、Blender は Face Material だけで情報が取れます。
その代わり Blender には Material 無しのデータが存在できるようです。
この場合でも MeshPolygon.material_index には有効な値 (0) が
入ってしまうのでこれだけでは判定できません。
事前に Mesh.materials のデータ数を調べておき、Material が無い場合は
適当なデフォルトデータを設定しておく必要があります。
def decode_material( obj ):
mesh= obj.data
if not mesh.materials:
primt( 'Empty' )
else:
for material in mesh.materials:
print( material.name )
print( material.ambient )
print( material.diffuse_color )
print( material.specular_color )
decode_material( bpy.data.objects['Cube'] )
Texture は Material.texture_slots からたどることができます。
画像かつ外部ファイルと限定すると下記のように取れます。
def decode_material( obj ):
mesh= obj.data
if not mesh.materials:
primt( 'Empty' )
else:
for material in mesh.materials:
print( material.name )
print( material.ambient )
print( material.diffuse_color )
print( material.specular_color )
for slotid, slot in enumerate( material.texture_slots ):
if slot and material.use_textures[ slotid ] and slot.use:
if slot.texture.type == 'IMAGE':
image= slot.texture.image
if image.source == 'FILE':
print( 'Texture=', image.filepath )
decode_material( bpy.data.objects['Cube'] )
実際の描画時は Material 単位に行うので、
ポリゴンの描画データは Material 単位に分類しておく必要があります。
最終的には Shader によって必要となるデータが変わるので、
事前に Material やデータ構造を調べて Shader の機能を分類し、
必要となる情報を export する流れとなります。
実際に頂点データを出力してみます。
Primitive は material で分割した Mesh です。
この中に頂点と index を格納していきます。
index 化のために辞書を使っているので、出力時は index 順に
並べ替える必要があります。
Primitive.getVertex() で配列を作り直しています。
MeshPolygon は 4角形以上の可能性があるので decode_shape() で
3角形に変換しています。
まだ Fan なのであまり良い変換ではありません。
法線以外の頂点要素や Material はまだ含まれていません。
# io_export_flatlib_test_a6.py
import bpy
import math
from bpy_extras.io_utils import ExportHelper
from bpy.props import BoolProperty
from mathutils import Matrix, Vector, Color
bl_info= {
"name": "Export Test flatlib a6",
"author": "Hiroyuki Ogasawara",
"version": (1, 0, 0),
"blender": (2, 6, 5),
"location": "File > Export > flatlib (.a6)",
"category": "Import-Export",
}
class Vertex:
def __init__( self, pos ):
self.pos= Vector( pos )
self.vbid= 0
def setNormal( self, normal ):
self.normal= normal
def setVBID( self, id ):
self.vbid= id
def getVBID( self ):
return self.vbid
def __hash__( self ):
return int( math.frexp( self.pos.dot( self.normal ) )[0] * 0x7fffffff )
def __eq__( self, ver ):
return self.pos == ver.pos and self.normal == ver.normal
class Primitive:
def __init__( self, material_id ):
self._vertexBuffer= {}
self._indexBuffer= []
self._material_id= material_id
def addVertex( self, vertex ):
if vertex in self._vertexBuffer:
return vertex.getVBID()
vertex.setVBID( len(self._vertexBuffer) )
self._vertexBuffer[ vertex ]= vertex
return vertex.getVBID()
def addIndex( self, index ):
self._indexBuffer.append( index )
def getVertexBuffer( self ):
mapbuf= [ None ] * len( self._vertexBuffer )
for vertex in self._vertexBuffer:
mapbuf[ vertex.getVBID() ]= vertex
return mapbuf
def getIndexBuffer( self ):
return self._indexBuffer
class Node:
def __init__( self, obj ):
self._obj= obj
def getName( self ):
return self._obj.name
def getParent( self ):
return self._obj.parent.name if self._obj.parent else None
def getMatrix( self ):
return self._obj.matrix_local
def isMesh( self ):
return False
class Shape(Node):
def __init__( self, obj ):
self._obj= obj
self._primitive= {}
def getMesh( self ):
return self._obj.data
def createPrimitive( self, material_id ):
self._primitive[material_id]= Primitive( material_id )
def getPrimitive( self, material_id ):
return self._primitive[material_id]
def getPrimitiveList( self ):
return self._primitive.values()
def isMesh( self ):
return True
class A6Export:
def __init__( self, options ):
self.options= options
def decode_tree( self, tree, obj ):
tree.append( Shape( obj ) if obj.type == 'MESH' else Node( obj ) )
for child in obj.children:
self.decode_tree( tree, child )
def decode_node( self ):
tree= []
for obj in bpy.data.objects:
if not obj.parent:
if obj.type == 'MESH' or obj.type == 'EMPTY' or obj.type == 'ARMATURE':
self.decode_tree( tree, obj )
return tree
def decode_material( self, tree ):
for node in tree:
mesh= node.getMesh()
materiallist= {}
if not mesh.materials:
node.createPrimitive( 0 )
else:
for poly in mesh.polygons:
materiallist[ poly.material_index ]= poly.material_index
for material_id in materiallist.values():
node.createPrimitive( material_id )
def decode_shape( self, tree ):
ver_index= [ None, None, None ]
for node in tree:
mesh= node.getMesh()
for poly in mesh.polygons:
prim= node.getPrimitive( poly.material_index )
triangle= 0
for id in range( poly.loop_start, poly.loop_start + poly.loop_total ):
vertex= mesh.vertices[ mesh.loops[id].vertex_index ]
ver= Vertex( vertex.co )
ver.setNormal( vertex.normal if poly.use_smooth else poly.normal )
ver_index[triangle]= ver
triangle+= 1
if triangle >= 3:
prim.addIndex( prim.addVertex( ver_index[0] ) )
prim.addIndex( prim.addVertex( ver_index[1] ) )
prim.addIndex( prim.addVertex( ver_index[2] ) )
triangle= 2
ver_index[1]= ver_index[2]
def output_geometry( self, fo, tree ):
fo.write( "#\nT \"Geometry\"\n" )
for gid,node in enumerate(tree):
fo.write( "N \"%s\"\n" % node.getName() )
if node.getParent():
fo.write( "S \"Parent\" \"%s\"\n" % node.getParent() )
m= node.getMatrix()
fo.write( "I \"ID\" %d\n" % gid )
fo.write( "F \"Matrix\"\n" )
fo.write( "F %f %f %f %f\n" % (m[0][0],m[1][0],m[2][0],m[3][0]) )
fo.write( "F %f %f %f %f\n" % (m[0][1],m[1][1],m[2][1],m[3][1]) )
fo.write( "F %f %f %f %f\n" % (m[0][2],m[1][2],m[2][2],m[3][2]) )
fo.write( "F %f %f %f %f\n" % (m[0][3],m[1][3],m[2][3],m[3][3]) )
def output_vertex( self, fo, tree ):
fo.write( "#\nT \"Vertex\"\n" )
for nodeid, node in enumerate( tree ):
for primid, prim in enumerate( node.getPrimitiveList() ):
fo.write( "N \"_vb%03d_%03d\"\n" % (nodeid, primid) )
buffer= prim.getVertexBuffer()
stride= 24
fo.write( "I \"Size\" %d %d\n" % (len(buffer), stride) )
fo.write( "M \"v\"\n" )
for vertex in buffer:
fo.write( "F %f %f %f\n" % (vertex.pos.x, vertex.pos.y, vertex.pos.z) )
fo.write( "F %f %f %f\n" % (vertex.normal.x, vertex.normal.y, vertex.normal.z) )
def output_index( self, fo, tree ):
fo.write( "#\nT \"Index\"\n" )
for nodeid, node in enumerate( tree ):
for primid, prim in enumerate( node.getPrimitiveList() ):
fo.write( "N \"_ib%03d_%03d\"\n" % (nodeid, primid) )
fo.write( "I \"Size\" %d\n" % len(prim.getIndexBuffer()) )
fo.write( "I \"i\"\n" )
indexlist= prim.getIndexBuffer()
for tid in range( 0, len(indexlist), 3 ):
fo.write( "I %d %d %d\n" % (indexlist[tid], indexlist[tid+1], indexlist[tid+2] ) )
def output_shape( self, fo, tree ):
fo.write( "#\nT \"Command\"\n" )
for nodeid, node in enumerate( tree ):
for primid, prim in enumerate( node.getPrimitiveList() ):
fo.write( "N \"_pr%03d_%03d\"\n" % (nodeid, primid) )
fo.write( "S \"Geometry\" \"%s\"\n" % node.getName() )
fo.write( "S \"Vertex\" \"_vb%03d_%03d\"\n" % (nodeid, primid) )
fo.write( "S \"Index\" \"_ib%03d_%03d\"\n" % (nodeid, primid) )
fo.write( "S \"Material\" \"unknown\"\n" )
def output_material( self, fo, tree ):
pass
def export( self, filename ):
print( self.options )
tree= self.decode_node()
meshlist= [ node for node in tree if node.isMesh()]
self.decode_material( meshlist )
self.decode_shape( meshlist )
#
fo= open( filename, 'w' )
self.output_material( fo, meshlist )
self.output_geometry( fo, tree )
self.output_vertex( fo, meshlist )
self.output_index( fo, meshlist )
self.output_shape( fo, meshlist )
fo.close()
class ExportTestA6( bpy.types.Operator, ExportHelper ):
bl_label= 'export flatlib a6'
bl_idname= 'export.flatlib_a6'
filename_ext= '.a6'
flag_selected= BoolProperty( name= "Selected Objects",
description= "Export selected objects",
default= False )
flag_lefthand= BoolProperty( name= "Left-Hand (DirectX)",
description= "Lfef-Hand (DirectX)",
default= True )
def execute( self, context ):
A6Export( self ).export( self.filepath )
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator( ExportTestA6.bl_idname, text="flatlib a6 (.a6)" )
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_export.remove(menu_func)
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_export.append(menu_func)
今頃気が付きましたが 3ds Max のように Z-up なので、出力時に
Y-up に変換しておいた方がよさそうです。
関連エントリ
・Blender 2.6 の Export plug-in を作る (3) 出力と mathutils
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)
スキニング用のジオメトリ情報を調べます。
def decode_shape2( obj ):
mesh= obj.data
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 ]
for vgroup in vertex.groups:
print( vgroup.group, vgroup.weight )
shape( bpy.data.objects['Cube'] )
頂点自体に、影響を受けるジオメトリの番号 (vertex group)と重みのセットが
必要個数含まれています。
vertex group の番号が実際に指しているジオメトリの情報は
Object に入っています。
def decode_shape1( obj ):
if len(obj.vertex_groups):
for vgroup in obj.vertex_groups:
print( vgroup.index, ':', vgroup.name )
Object ローカルに影響を受けるジオメトリセットがあり、
その index 番号 (group 番号) が頂点に格納されています。
ゲームなどリアルタイム描画時の構造と同じなので、非常にわかりやすく
扱いやすい形式になっているようです。
実際に影響を与えるジオメトリは Armature と呼ばれているようです。
Modifier の扱いとなっており Subdivision Surface 等と同じです。
Object から Modifier を列挙できるため、その中から ‘ARMATURE’ を
取り出すことができます。
def decode_shape3( obj ):
for modifier in obj.modifiers
if modifier.type == 'ARMATURE':
armature_object= mod.object
通常の Node と同じだろうと思っていたのですが構造が少々違うようです。
Bone 周りは Blender の機能やデータ構造を知らないといけないので
正直よく理解していません。
なので後回しにします。
●ファイル出力
実際に addon としてデータを出力してみます。
# io_export_test_a6.py
import bpy
from bpy_extras.io_utils import ExportHelper
from bpy.props import BoolProperty
from mathutils import Vector, Matrix, Color
bl_info= {
"name": "Export flatlib a6",
"category": "Import-Export",
}
class ExportTestA6( bpy.types.Operator, ExportHelper ):
bl_label= 'export flatlib a6'
bl_idname= 'export.flatlib_a6'
filename_ext= '.a6'
def execute( self, context ):
A6Export( self ).export( self.filepath )
return {'FINISHED'}
def menu_func(self, context):
self.layout.operator( ExportTestA6.bl_idname, text="Export flatlib a6 (.a6)" )
def unregister():
bpy.utils.unregister_module(__name__)
bpy.types.INFO_MT_file_export.remove(menu_func)
def register():
bpy.utils.register_module(__name__)
bpy.types.INFO_MT_file_export.append(menu_func)
exporter 本体を A6Export() として別に作成し呼び出す形に。
a6 は独自フォーマットの名称で CPU とかは無関係です。
class A6Export:
def __init__( self, options ):
self.options= options
def decode_tree( self, tree, obj ):
tree.append( Shape( obj ) )
for child in obj.children:
self.decode_tree( tree, child )
def decode_node( self ):
tree= []
for obj in bpy.data.objects:
if not obj.parent:
if obj.type == 'MESH' or obj.type == 'EMPTY' or obj.type == 'ARMATURE':
self.decode_tree( tree, obj )
return tree
def decode_shape( self, tree ):
pass
def output_geometry( self, fo, tree ):
fo.write( "T \"Geometry\"\n" )
for gid,node in enumerate(tree):
fo.write( "N \"%s\"\n" % node.getName() )
if node.getParent():
fo.write( "S \"Parent\" \"%s\"\n" % node.getParent() )
m= node.getMatrix()
fo.write( "I \"ID\" %d\n" % gid )
fo.write( "F \"Matrix\"\n" )
fo.write( "F %f %f %f %f\n" % (m[0][0],m[1][0],m[2][0],m[3][0]) )
fo.write( "F %f %f %f %f\n" % (m[0][1],m[1][1],m[2][1],m[3][1]) )
fo.write( "F %f %f %f %f\n" % (m[0][2],m[1][2],m[2][2],m[3][2]) )
fo.write( "F %f %f %f %f\n" % (m[0][3],m[1][3],m[2][3],m[3][3]) )
def export( self, filename ):
print( self.options )
tree= self.decode_node()
self.decode_shape( tree )
fo= open( filename, 'w' )
self.output_geometry( fo, tree )
fo.close()
まずは階層構造の解析と matrix の出力のみ。
読みだしたあとの Node を格納するコンテナが Shape です。
class Shape:
def __init__( self, obj ):
self._obj= obj
def getMatrix( self ):
return self._obj.matrix_local
def getName( self ):
return self._obj.name
def getParent( self ):
return self._obj.parent.name if self._obj.parent else None
●コマンドとして呼び出す
addon として上記 io_export_test_a6.py を読み込むと
File → Export のメニューに組み込まれますが、これをコマンドとして
呼び出すこともできます。
>>> bpy.ops.export.flatlib_a6( filepath="output.a6" )
Python Console で実行でき、またダイアログを経由しないので便利です。
● mathutils
頂点情報や Matrix には mathutils が使われています。
今まで出てきたのは Matrix, Vector, Color の 3種類です。
・Math Types & Utilities (mathutils)
Vector は shader のような swizzle が可能で、上のページを見ると
xxxx ~ wwww まですべての組み合わせが定義されていることがわかります。
例えば dest.xyz= src.zyx などができるようです。
Color や Matrix には swizzle がないので color.rg のような記述はできません。
それぞれ配列のようにシーケンス型として扱うこともできます。
hlsl 等の shader の場合 swizzle も代入も同一です。
float3 dest, src; dest.xyz= src.xyz; // -- (1) dest= src; // -- (2) dest.x= src.x; // | dest.y= src.y; // |-- (3) dest.z= src.z; // |
上の (1), (2), (3) は全く同じもので値の代入です。
特に Shader Assembler では swizzle と mask の違いを明確にするため
あえて (1) の表記を用いることがあります。
Vector は python のオブジェクトなので、代入は参照であり
swizzle は copy になります。
from mathutils import Vector src= Vector((1,2,3)) dest= Vector((4,5,6)) dest= src // -- (4) (dest is src) == True dest.xyz= src.xyz // -- (5) (dest is src) == False dest.x= src.x // | dest.y= src.y // |-- (6) (dest is src) == False dest.z= src.z // |
(4) は dest に src の参照が入ります。
(5) は新 object を生成して copy します。
src.xyz の代わりに src.copy() や src[:] と記述した場合も同様です。
(6) は値代入なので dest と src は別物です。新たな object が作られません。
場合によっては (6) が欲しいのですが immutable なら (4) で十分で
むしろ効率が良くなります。
Vector 同士の比較には注意が必要です。
Vector((1,2,3)) >= Vector((3,2,1)) # = True -- (7) Vector((1,2,3)) > Vector((3,2,1)) # = False -- (8) Vector((1,2,3)) == Vector((3,2,1)) # = False -- (9)
(7),(8) の結果を見ると (9) が成り立つはずですが False になります。
一致判定は Vector 個々の値を比較しますが、
大小判定には Vecotr の長さが用いられているためです。
つまり (7),(8) は下記の (10),(11) と同じです。
Vector((1,2,3)).length >= Vector((3,2,1)).length # = True -- (10) Vector((1,2,3)).length > Vector((3,2,1)).length # = False -- (11)
(9) も .length をつければ True になります。
Vector((1,2,3)).length == Vector((3,2,1)).length # = True -- (12)
頂点の検索アルゴリズムを作る場合このあたり注意が必要です。
●頂点のコンテナ
頂点をマージして index を作るためにコンテナを作ります。
検索は極めて重要です。
数百万頂点など大きなモデルデータを export した場合に終わらなくなります。
class Vertex:
def __init__( self, pos, normal ):
self.pos= Vector( pos )
self.normal= Vector( normal )
def __hash__( self ):
return int( frexp( self.pos.dot( self.normal ) )[0] * 0x7fffffff )
def __eq__( self, ver ):
return self.pos == ver.pos and self.normal == ver.normal
hash はもっと改良できると思いますがとりあえず key として使えました。
比較に関しても最終的にはもっと考慮が必要で、例えば clamp 処理が入ります。
>>> a={ Vertex(Vector((1,2,3)),Vector((0,1,1))): 123, Vertex(Vector((3,4,5)),Vector((1,0,1))): 456 }
>>> a[ Vertex(Vector((1,2,3)),Vector((0,1,1))) ]
123
続きます
関連エントリ
・Blender 2.6 の Export plug-in を作る (2) データアクセス
・Blender 2.6 の Export plug-in を作る (1)