3D アプリケーション間でデータを交換するためのファイルフォーマットの一つに COLLADA (COLLAborative Design Activity) があります。コンピュータグラフィックスのレンダリングに必要な情報およびその他付随する情報を格納できます。COLLADA の仕様にしたがった XML スキーマファイルの拡張子は通常 .dae
(digital asset exchange) です。3D アプリケーションの例としては以下のようなものがあります。
COLLADA ファイルをプログラミング言語から扱うためのライブラリには以下のようなものがあります。
- pycollada (Python)
- collada-dom (C++)
以下に Debian9 における FreeCAD および pycollada を利用した、COLLADA ファイル (dae ファイル) の簡単な扱い方を記載します。その際、COLLADA のバージョンは 1.5 とします。
インストール
pycollada
v1.5 対応版をインストールするためには以下のようにします。setuptools の setup.pyを利用します。
git clone https://github.com/pycollada/pycollada.git
cd pycollada/
git checkout collada15spec
sudo python setup.py install
以下のような場所にインストールされます。
ls /usr/local/lib/python2.7/dist-packages/pycollada-0.4.1-py2.7.egg/
ipython
import collada
collada.__path__
collada.__file__
アンインストール方法は以下のようになります。
sudo python setup.py install --record files.txt
cat files.txt | xargs -I{} sudo rm -rf {}
FreeCAD
sudo apt install freecad
meshlab も同様に apt で提供されています。
sudo apt install meshlab
dae ファイルを読み込むための pycollada 設定
FreeCAD は dae ファイルを操作するために、内部的に pycollada を利用します。
上記手順で既にインストール済みであれば dae ファイルの読み込みおよび書き出しが可能です。
pycollada サンプルコード
読み込み
duck_triangles.dae を読み込んでみます。おおよそ以下のような構造をしています。
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<asset></asset>
<library_cameras></library_cameras>
<library_lights></library_lights>
<library_images></library_images>
<library_materials></library_materials>
<library_effects></library_effects>
<library_geometries>
<geometry id="LOD3spShape-lib" name="LOD3spShape">
<mesh>
<source id="LOD3spShape-lib-positions" name="position"></source>
<source id="LOD3spShape-lib-normals" name="normal"></source>
<source id="LOD3spShape-lib-map1" name="map1"></source>
<vertices id="LOD3spShape-lib-vertices">
<input semantic="POSITION" source="#LOD3spShape-lib-positions"/>
</vertices>
<triangles count="4212" material="blinn3SG">
<input offset="0" semantic="VERTEX" source="#LOD3spShape-lib-vertices"/>
<input offset="1" semantic="NORMAL" source="#LOD3spShape-lib-normals"/>
<input offset="2" semantic="TEXCOORD" source="#LOD3spShape-lib-map1" set="0"/>
<p></p>
</triangles>
</mesh>
</geometry>
</library_geometries>
<library_visual_scenes>
<visual_scene id="VisualSceneNode" name="untitled"></visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#VisualSceneNode"/>
</scene>
</COLLADA>
バージョンは 1.4.1 であることが確認できます。これを読み込む際に指定します。また、pycollada は内部的に NumPyを利用しています。更に、参照できないオブジェクト ID が指定されていたり、pycollada がサポートしていない機能が存在する場合にエラーではなく無視するように設定しています。
from collada import Collada, DaeBrokenRefError, DaeUnsupportedError, set_collada_version, set_number_dtype
import numpy as np
set_collada_version('1.4.1')
set_number_dtype(np.float64)
mesh = Collada('duck_triangles.dae', ignore=[DaeBrokenRefError, DaeUnsupportedError])
ipython で読み込んだ場合の例です。形状 geometries 情報が一つ格納されている DAE ファイルであることが分かります。
In [2]: mesh
Out[2]: <Collada geometries=1, articulated_systems=0, kinematics_models=0>
形状 geometry は三角形となっています。フェイスとして三角形が利用されている meshということになります。
In [3]: mesh.geometries
Out[3]: [<Geometry id=LOD3spShape-lib, 1 primitives>]
In [6]: mesh.geometries[0].primitives
Out[6]: [<TriangleSet length=4212>]
In [7]: triset = mesh.geometries[0].primitives[0]
情報の取得
ファイル作成日時や作者、単位、軸の向き
mesh.filename #=> 'duck_triangles.dae'
mesh.assetInfo.contributors[0].author
mesh.assetInfo.contributors[0].authoring_tool
mesh.assetInfo.created #=> datetime.datetime(2006, 8, 23, 22, 29, 59, tzinfo=tzutc())
mesh.assetInfo.modified
mesh.assetInfo.unitname #=> 'centimeter'
mesh.assetInfo.unitmeter #=> 0.01
mesh.assetInfo.upaxis #=> 'Y_UP'
library_xxx
へのアクセス
mesh.cameras
mesh.lights
mesh.images[0] #=> <CImage id=file2 path=./duckCM.tga>
mesh.materials
mesh.effects
mesh.geometries
mesh.scenes[0] # == mesh.scene
形状 geometries から三角形フェイスの set を取得
mesh.geometries #=> [<Geometry id=LOD3spShape-lib, 1 primitives>]
mesh.geometries[0].primitives #=> [<TriangleSet length=4212>]
triset = mesh.geometries[0].primitives[0]
len(triset) #=> 4212
三角形フェイスの集合は、頂点座標や頂点法線の情報をソースとして持ちます。
triset.sources['VERTEX'] #=> [(0, 'VERTEX', '#LOD3spShape-lib-positions', None, <FloatSource size=2108>)]
triset.sources['NORMAL'] #=> [(1, 'NORMAL', '#LOD3spShape-lib-normals', None, <FloatSource size=2290>)]
len(triset.vertex) #=> 2108
len(triset.normal) #=> 2290
それらソースは一つ以上の三角形フェイスから参照されます。各フェイスが参照する頂点座標や頂点法線の情報を格納したインデックスが以下の二つです。
len(triset) #=> 4212
len(triset.vertex_index) #=> 4212
len(triset.normal_index) #=> 4212
インデックスを用いて、各三角形フェイスが利用する三つの頂点座標、および各頂点の頂点法線ベクトルを取得できます。
triset.vertex[triset.vertex_index][0]
array([[-23.9364, 11.5353, 30.6125], #=> (x,y,z)
[-18.7264, 10.108 , 26.6814], #=> (x,y,z)
[-15.6992, 11.4278, 34.2321]]) #=> (x,y,z)
triset.normal[triset.normal_index][0]
array([[-0.192109, -0.934569, 0.299458], #=> 長さ1
[-0.06315 , -0.993623, 0.093407], #=> 長さ1
[-0.11695 , -0.921313, 0.370816]]) #=> 長さ1
立方体の作成サンプル
立方体の各面は三角形フェイス二つで表現できるため、立方体の表現に必要なフェイス数は 2 x 6 で 12 です。
import collada
import numpy as np
# 空のメッシュを作成
mesh = collada.Collada()
##### mesh.geometries
# 立方体の 8 頂点 (x,y,z) → 8 x 3 = 24
vert_floats = [-50,50,50,50,50,50,-50,-50,50,50,-50,50,-50,50,-50,50,50,-50,-50,-50,-50,50,-50,-50]
# 頂点法線ベクトル (x,y,z) を 8 頂点それぞれに対して向き 3 つ分用意 → 3 x 8 x 3 = 72
normal_floats = [0,0,1,0,0,1,0,0,1,0,0,1,0,1,0,0,1,0,0,1,0,0,1,0,0,-1,0,0,-1,0,0,-1,0,0,-1,0,-1,0,0,-1,0,0,-1,0,0,-1,0,0,1,0,0,1,0,0,1,0,0,1,0,0,0,0,-1,0,0,-1,0,0,-1,0,0,-1]
# 頂点座標と頂点法線をソースとして登録
vert_src = collada.source.FloatSource("cubeverts-array", np.array(vert_floats), ('X','Y', 'Z'))
normal_src = collada.source.FloatSource("cubenormals-array", np.array(normal_floats), ('X', 'Y', 'Z'))
# id `geometry0`, name `mycube` の geometry をソース情報を含めて新規作成
geom = collada.geometry.Geometry(mesh, "geometry0", "mycube", [vert_src, normal_src])
# 作成した geometry をもとにフェイス情報 triset を新規作成
input_list = collada.source.InputList()
input_list.addInput(0, 'VERTEX', "#cubeverts-array")
input_list.addInput(1, 'NORMAL', "#cubenormals-array")
indices = np.array([0,0,2,1,3,2,0,0,3,2,1,3,0,4,1,5,5,6,0,4,5,6,4,7,6,8,7,9,3,10,6,8,3,10,2,11,0,12,4,13,6,14,0,12,6,14,2,15,3,16,7,17,5,18,3,16,5,18,1,19,5,20,7,21,6,22,5,20,6,22,4,23])
triset = geom.createTriangleSet(indices, input_list, "materialref")
# geometry にフェイス情報 triset を追加
geom.primitives.append(triset)
# mesh に geometry を追加
mesh.geometries.append(geom)
##### mesh.effects, mesh.materials
# 簡単のため Phong の反射モデルを利用するように設定
effect = collada.material.Effect("effect0", [], "phong", diffuse=(1,0,0), specular=(0,1,0))
# id `material0`, name `mymaterial` として material を新規作成
mat = collada.material.Material("material0", "mymaterial", effect)
# mesh に effect と material を追加
mesh.effects.append(effect)
mesh.materials.append(mat)
##### scene の設定
# scene を作成
matnode = collada.scene.MaterialNode("materialref", mat, inputs=[])
geomnode = collada.scene.GeometryNode(geom, [matnode])
node = collada.scene.Node("node0", children=[geomnode])
myscene = collada.scene.Scene("myscene", [node])
# scene リストへの追加および既定値の設定
mesh.scenes.append(myscene)
mesh.scene = myscene
# ファイルへの書き出し
mesh.write('/tmp/test.dae')
作成結果例 test.dae
<COLLADA xmlns="http://www.collada.org/2005/11/COLLADASchema" version="1.4.1">
<asset>
<created>2018-10-21T00:20:01.203446</created>
<modified>2018-10-21T00:20:01.203468</modified>
<up_axis>Y_UP</up_axis>
</asset>
<library_effects>
<effect name="effect0" id="effect0">
<profile_COMMON>
<technique sid="common">
<phong>
<emission>
<color>0 0 0 1</color>
</emission>
<ambient>
<color>0 0 0 1</color>
</ambient>
<diffuse>
<color>1 0 0 1</color>
</diffuse>
<specular>
<color>0 1 0 1</color>
</specular>
<shininess>
<float>0</float>
</shininess>
<reflective>
<color>0 0 0 1</color>
</reflective>
<reflectivity>
<float>0</float>
</reflectivity>
<transparent>
<color>0 0 0 1</color>
</transparent>
<transparency>
<float>1</float>
</transparency>
</phong>
</technique>
<extra>
<technique profile="GOOGLEEARTH">
<double_sided>0</double_sided>
</technique>
</extra>
</profile_COMMON>
</effect>
</library_effects>
<library_geometries>
<geometry id="geometry0" name="mycube">
<mesh>
<source id="cubenormals-array">
<float_array count="72" id="cubenormals-array-array">0 0 1 0 0 1 0 0 1 0 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 -1 0 0 -1 0 0 -1 0 0 -1 0 -1 0 0 -1 0 0 -1 0 0 -1 0 0 1 0 0 1 0 0 1 0 0 1 0 0 0 0 -1 0 0 -1 0 0 -1 0 0 -1</float_array>
<technique_common>
<accessor count="24" source="#cubenormals-array-array" stride="3">
<param type="float" name="X"/>
<param type="float" name="Y"/>
<param type="float" name="Z"/>
</accessor>
</technique_common>
</source>
<source id="cubeverts-array">
<float_array count="24" id="cubeverts-array-array">-50 50 50 50 50 50 -50 -50 50 50 -50 50 -50 50 -50 50 50 -50 -50 -50 -50 50 -50 -50</float_array>
<technique_common>
<accessor count="8" source="#cubeverts-array-array" stride="3">
<param type="float" name="X"/>
<param type="float" name="Y"/>
<param type="float" name="Z"/>
</accessor>
</technique_common>
</source>
<vertices id="cubeverts-array-vertices">
<input source="#cubeverts-array" semantic="POSITION"/>
</vertices>
<triangles count="12" material="materialref">
<input source="#cubenormals-array" semantic="NORMAL" offset="1"/>
<input source="#cubeverts-array-vertices" semantic="VERTEX" offset="0"/>
<p>0 0 2 1 3 2 0 0 3 2 1 3 0 4 1 5 5 6 0 4 5 6 4 7 6 8 7 9 3 10 6 8 3 10 2 11 0 12 4 13 6 14 0 12 6 14 2 15 3 16 7 17 5 18 3 16 5 18 1 19 5 20 7 21 6 22 5 20 6 22 4 23</p>
</triangles>
</mesh>
</geometry>
</library_geometries>
<library_materials>
<material name="mymaterial" id="material0">
<instance_effect url="#effect0"/>
</material>
</library_materials>
<library_visual_scenes>
<visual_scene id="myscene">
<node id="node0">
<instance_geometry url="#geometry0">
<bind_material>
<technique_common>
<instance_material symbol="materialref" target="#material0"/>
</technique_common>
</bind_material>
</instance_geometry>
</node>
</visual_scene>
</library_visual_scenes>
<scene>
<instance_visual_scene url="#myscene"/>
</scene>
</COLLADA>
関連記事
- Python コードスニペット (条件分岐)if-elif-else sample.py #!/usr/bin/python # -*- coding: utf-8 -*- # コメント内であっても、ASCII外の文字が含まれる場合はエンコーディング情報が必須 x = 1 # 一行スタイル if x==0: print 'a' # 参考: and,or,notが使用可能 (&&,||はエラー) elif x==1: p...
- Python コードスニペット (リスト、タプル、ディクショナリ)リスト range 「0から10まで」といった範囲をリスト形式で生成します。 sample.py print range(10) # for(int i=0; i<10; ++i) ← C言語などのfor文と比較 print range(5,10) # for(int i=5; i<10; ++i) print range(5,10,2) # for(int i=5; i<10;...
- ZeroMQ (zmq) の Python サンプルコードZeroMQ を Python から利用する場合のサンプルコードを記載します。 Fixing the World To fix the world, we needed to do two things. One, to solve the general problem of "how to connect any code to any code, anywhere". Two, to wra...
- Matplotlib/SciPy/pandas/NumPy サンプルコードPython で数学的なことを試すときに利用される Matplotlib/SciPy/pandas/NumPy についてサンプルコードを記載します。 Matplotlib SciPy pandas [NumPy](https://www.numpy
- pytest の基本的な使い方pytest の基本的な使い方を記載します。 適宜参照するための公式ドキュメントページ Full pytest documentation API Reference インストール 適当なパッケージ