はじめに
IFCファイルを読み込む手段の一つにIfcOpenShell
があります。形状情報も取得することができるのですが、取得してもPythonでは表示する方法がありません。いえ、pythonocc
を使用すれば表示できるそうなのですが、pythonocc
を使用するにはconda
を使用するか、ソースコードからビルドする必要があるようです。宗教上の理由で(?)conda
が使えない人もいると思いますし、ビルドは難易度が高いです。
そんなわけでもっと手軽にIFCのモデルを表示したいので、IfcOpenShell
で読み込んだ形状情報をplotly
で表示します。
できました
これで表示できるよ!やったね!!
import plotly.graph_objects as go
import numpy as np
import ifcopenshell.geom
# 世界座標系で取得する設定
settings = ifcopenshell.geom.settings()
settings.set(settings.USE_WORLD_COORDS, True)
# IFCファイル読み込み
model = ifcopenshell.open(path)
# すべての形状情報を取得する
meshes = []
for element in model.by_type("IfcProduct"):
# 部屋の情報などや壁の開口部などは除く
if element.is_a('IfcOpeningElement') or element.is_a('IfcSpace'):
continue
try:
# 形状の取得
shape = ifcopenshell.geom.create_shape(settings, element)
except:
continue
# 頂点と面の情報を取得
verts = shape.geometry.verts
faces = shape.geometry.faces
verts = np.array(verts).reshape(-1, 3)
faces = np.array(faces).reshape(-1, 3)
# 色情報の取得
materials = shape.geometry.materials
material_ids = shape.geometry.material_ids
if len(materials) == 0:
facecolors = ['lightgray'] * len(faces)
else:
colors = [np.array(material.diffuse) for material in materials]
transparencies = [1 - material.transparency for material in materials]
colors = np.hstack([np.array(colors), np.array(transparencies).reshape(-1, 1)])
facecolors = [colors[material_id] for material_id in material_ids]
# plotlyのメッシュを作成
mesh = go.Mesh3d(
x=verts[:, 0],
y=verts[:, 1],
z=verts[:, 2],
i=faces[:, 0],
j=faces[:, 1],
k=faces[:, 2],
facecolor=facecolors,
flatshading=True,
lighting=dict(
ambient=1,
diffuse=0,
),
)
meshes.append(mesh)
# plotlyで表示
fig = go.Figure(data=meshes)
# 軸を表示しない設定
noaxis = dict(
showbackground=False,
showgrid=False,
showline=False,
showticklabels=False,
ticks="",
title="",
zeroline=False,
)
# レイアウト設定
fig.update_layout(
scene=dict(
xaxis = noaxis,
yaxis = noaxis,
zaxis = noaxis,
),
scene_aspectmode='data',
)
# HTMLファイルに保存
fig.write_html(
"file.html",
include_plotlyjs='cdn',
full_html=True,
)
解説
コードの解説します。
世界座標系の設定
まず最初の設定の部分。
settings = ifcopenshell.geom.settings()
settings.set(settings.USE_WORLD_COORDS, True)
IFCのデータは形状の情報(Representation
)と位置情報(ObjectPlacement
)が別れています(IfcProduct
のドキュメント参照)。そのため、特に設定を行わずに形状情報を取得すると、位置情報がないので以下のように原点付近にメッシュが表示されます。
そのため、正しい位置を取得するには、世界座標系で形状を取得するように設定するか、別途位置情報を取得して変換をかける必要があります。
今回は、こちらの記事を参考にして世界座標系で取得しました。
形状を持つデータの取得
すべての形状データの取得を行います。
for element in model.by_type("IfcProduct"):
# 部屋の情報などや壁の開口部などは除く
if element.is_a('IfcOpeningElement') or element.is_a('IfcSpace'):
continue
try:
# 形状の取得
shape = ifcopenshell.geom.create_shape(settings, element)
except:
continue
IFCで形状情報を持つのはIfcProduct
(を継承しているエンティティ)です。そのため IfcProduct
を取得すればすべての形状を持つデータを取得できるのですが、IfcSpace
(部屋の情報など)や IfcOpeningElement
(壁の開口部など)も含まれてしまいます。そのため、それらのエンティティは除いて取得する必要があります。
ちなみに除かないで取得すると以下のようになります(わかりやすくするために色を変えています)。
また、IfcProduct
のドキュメントを見てみるとわかるのですが、形状情報はオプションです。つまり形状のないデータもあります。さらには形状情報があってもIfcAnnotation
のようにIfcOpenShell
が形状取得に対応していないものもあります。そのため、形状取得するときにエラーが発生しても例外処理で無視するようにしています。
頂点と面と色の取得
メッシュを作成するための、頂点、面、色の情報を取得します。
# 頂点と面の情報を取得
verts = shape.geometry.verts
faces = shape.geometry.faces
verts = np.array(verts).reshape(-1, 3)
faces = np.array(faces).reshape(-1, 3)
# 色情報の取得
materials = shape.geometry.materials
material_ids = shape.geometry.material_ids
if len(materials) == 0:
facecolors = ['lightgray'] * len(faces)
else:
colors = [np.array(material.diffuse) for material in materials]
transparencies = [1 - material.transparency for material in materials]
colors = np.hstack([np.array(colors), np.array(transparencies).reshape(-1, 1)])
facecolors = [colors[material_id] for material_id in material_ids]
頂点と面は公式ドキュメントのコードそのままです(numpyを使ってはいますが)。
色情報についてはplotly
で処理するために RGBA の形式にしています。このときmaterial.transparency
が透明度なのですが、IFCでは 1=透明, 0=不透明 のなので逆転させています。また、色情報がないデータもあるので、その場合はlightgrayとしました。
plotlyのメッシュ作成
plotlyでの3Dメッシュを作成します。
mesh = go.Mesh3d(
x=verts[:, 0],
y=verts[:, 1],
z=verts[:, 2],
i=faces[:, 0],
j=faces[:, 1],
k=faces[:, 2],
facecolor=facecolors,
flatshading=True,
lighting=dict(
ambient=1,
diffuse=0,
),
)
メッシュ作成時はflatshading
とlighting
を指定して光による表現を消して、単純に色を表示するようにします。この設定がないと以下のように変な影が表示されます。
なぜこうなるのかはわかりません。ただ、そもそもplotly
はグラフを描画するライブラリであって、3Dモデルを表示するものではない(可能ではあるけれども)ので、光の処理はそこまで得意ではないのかもしれません。
メッシュの表示
作成したメッシュをplotlyで表示します。
fig = go.Figure(data=meshes)
# 軸を表示しない設定
noaxis = dict(
showbackground=False,
showgrid=False,
showline=False,
showticklabels=False,
ticks="",
title="",
zeroline=False,
)
# レイアウト設定
fig.update_layout(
scene=dict(
xaxis = noaxis,
yaxis = noaxis,
zaxis = noaxis,
),
scene_aspectmode='data',
)
レイアウトの設定でのscene_aspectmode='data'
の指定はアスペクト比の設定です。これがないと見た目が歪みます。また、noaxis
の指定をすることで、軸の表示を消してモデルのみ表示させることができます。
できあがり
最終的に表示されたものが以下です。
まとめ
IFCのモデルをIfcOpenShell
で読み込んでplotly
で表示しました。plotly
だとHTMLファイルに出力することもできるので、出力したHTMLをブラウザで開くだけで手軽にモデルを表示することができます。
ただ、かなり表示が重いです。数十MB程度のIFCファイルから作成したHTMLでも開くのに数分かかりました。大きめのモデルは素直にIFCを表示するソフトウェアやIFC.jsなどを使用しましょう。
参考
今回使用したモデル
IfcOpenShell
でのジオメトリ取得方法
plotlyで3Dメッシュの表示
web-ifc
での開口部などを除く処理のソースコード
pythonoccを使用しての描画