プログマのプログラマ日記

技術メモや自社サービスに関する記事を書いていく予定です。

ifcの注記もglTFに変換しようとして、今度こそうまくいった話

ifcOpenShellを使ってifcの注記をglTFに変換してみる

この話は以下の記事の続きです。 前回、FME + 独自Pythonスクリプトでifcの注釈テキストをglTF化することができました。 しかし、FMEで取得したifcAnnotationのテキスト位置がおかしいという課題が残りました。

rhikos-prgm.hatenablog.com

FMEの内部ロジックには手が出せないため、今回はアプローチ方法を変えてFMEの代わりにifcOpenShellを使ってみることにします。

結論

ifcOpenShellを使うことでifcAnnotationをglTFに変換できました。
以下のリポジトリの「annotation_text_extract.py」がifcファイルからテキスト情報(文字列や位置)を抜き出すスクリプトで、「annotation_text_to_gltf.py」が抜き出したテキスト情報をもとにglTFを作成するスクリプトです。

github.com

途中経過

以下は結論に至るまでの話です。興味のある方お読みください。

ifcOpenShellでifcAnnotationの内容をダンプしてみる

まずはifcOpenShellで正しい座標が取得できるのかを確認することにしました。
ifcOpenShellでもFMEと同様に誤った座標取れてきてしまう場合、この方向でアプローチすること自体無意味になってしまうので。
FMEのinspectorで確認した下図①のテキストに注目して調べてみます。
ifcAnnotationのglobalIdは「0OL3LCjon8C8eMGVs718Tk」で、①のテキストのifc_instance_nameは「75793」です。

誤った位置に表示されているテキストの属性(FME)

ちなみに、元ファイルは以下のようなテキスト形式(※)で、

DATA;
#1= IFCPERSON($,'Nicht definiert',$,$,$,$,$,$);
#3= IFCORGANIZATION($,'Nicht definiert',$,$,$);
#7= IFCPERSONANDORGANIZATION(#1,#3,$);
#10= IFCORGANIZATION('GS','GRAPHISOFT','GRAPHISOFT',$,$);
#11= IFCAPPLICATION(#10,'20.0.0','ARCHICAD-64','IFC2x3 add-on version: 4009 GER FULL');
#12= IFCOWNERHISTORY(#7,#11,$,.ADDED.,$,$,$,1482339244);
#13= IFCSIUNIT(*,.LENGTHUNIT.,$,.METRE.);
#14= IFCSIUNIT(*,.AREAUNIT.,$,.SQUARE_METRE.);
#15= IFCSIUNIT(*,.VOLUMEUNIT.,$,.CUBIC_METRE.);
#16= IFCSIUNIT(*,.PLANEANGLEUNIT.,$,.RADIAN.);
#17= IFCMEASUREWITHUNIT(IFCPLANEANGLEMEASURE(0.0174532925199),#16);
#18= IFCDIMENSIONALEXPONENTS(0,0,0,0,0,0,0);
#19= IFCCONVERSIONBASEDUNIT(#18,.PLANEANGLEUNIT.,'DEGREE',#17);
...

大雑把に、

#[ID] = [IFCタイプ名](カンマ区切りの属性。#[ID]みたいになっているのは他の子要素を参照している)

のような構造になっているのがわかります。
※)ifcと一口に言っても物理フォーマットはいろいろあるみたいですね。今回扱っているのは最も普及しているSPFというフォーマットです。

technical.buildingsmart.org

頑張れば人力でもglobalId, ifc_instance_nameから以下のようにレコードを辿れます。
(このような方向に頑張るのは正しいプログラマの態度とは言えませんが、最初にデータを目視してざっとイメージをつかんでおくのは大事かなと思います。)

#76112= IFCANNOTATION('0OL3LCjon8C8eMGVs718Tk',#12,$,$,$,#75786,#76109);
 -> #75786= IFCLOCALPLACEMENT(#35064,#75785);
  -> ...
 -> #76109= IFCPRODUCTDEFINITIONSHAPE($,$,(#76107));
  -> ...
...

#76107= IFCSHAPEREPRESENTATION(#15265,'Annotation','Annotation2D',(#75793,#75806,#75819,#75832,#75845,#75858,#75871,#75882,#75893,#75904,#75915,#75926,#75937,#75948,#75959,#75970,#75981,#75992,#76003,#76014,#76025,#76036,#76047,#76058,#76069,#76080,#76091,#76102));
 -> #75793= IFCTEXTLITERALWITHEXTENT('50',#75792,.LEFT.,#75787,'bottom-left');
  -> #75792= IFCAXIS2PLACEMENT2D(#75790,#75788);
   -> #75790= IFCCARTESIANPOINT((13.5399999976,10.620000015));
   -> #75788= IFCDIRECTION((0.,1.));
  -> #75787= IFCPLANAREXTENT(0.549829,0.4);
...

元ファイルではid = 75793のIFCTEXTLITERALWITHEXTENTのIFCCARTESIANPOINTは(13.5399999976,10.620000015)となっていました。
これはIFCANNOTATIONの座標からのローカル座標だと思うのでまだ結論は出せないですが、この時点でFMEで取れてきた座標(13.5399999976, 24.1600000126, 0)と食い違って見えます。

次にifcOpenShellを使って階層構造をダンプするツールを作成してみました。
githubの「ifc_dump.py」)
結果は以下の通り。

重要な箇所を抜粋すると以下の通りになります:

(IfcAnnotation#76112): obj_place=(0.0, 0.0, 2.7), GlobalId=0OL3LCjon8C8eMGVs718Tk, Name=None, Description=None, ObjectType=None
 (中略)
 ObjectPlacement(IfcLocalPlacement#75786): 
  PlacementRelTo(IfcLocalPlacement#35064): 
   PlacementRelTo(IfcLocalPlacement#432): 
    PlacementRelTo(IfcLocalPlacement#115): PlacementRelTo=None
     RelativePlacement(IfcAxis2Placement3D#114): 
     (中略: こんな感じで入れ子状態のIfcLocalPlacementの定義が続く。)
     ※ifcopenshellの「ifcopenshell.util.placement.get_local_placement」メソッドを使えばObjectPlacement配下の要素をもとにIfcAnnotationの変換行列を取得できる。
     ※上記「obj_place=(0.0, 0.0, 2.7)」の部分はget_local_placementで取得した行列の4列目の平行移動成分(tx, ty, tz)を出力している。
 Representation(IfcProductDefinitionShape#76109): Name=None, Description=None
  Representation[0](IfcShapeRepresentation#76107): RepresentationIdentifier=Annotation, RepresentationType=Annotation2D
   ContextOfItems(IfcGeometricRepresentationSubContext#15265): ContextIdentifier=Annotation, ContextType=Plan, CoordinateSpaceDimension=None, Precision=None, WorldCoordinateSystem=None, TrueNorth=None, TargetScale=0.01, TargetView=PLAN_VIEW, UserDefinedTargetView=None
    ParentContext(IfcGeometricRepresentationContext#374): ContextIdentifier=None, ContextType=Plan, CoordinateSpaceDimension=3, Precision=1e-05
     WorldCoordinateSystem(IfcAxis2Placement3D#371): 
      Location(IfcCartesianPoint#369): Coordinates=(0.0, 0.0, 0.0)
      Axis(IfcDirection#367): DirectionRatios=(0.0, 0.0, 1.0)
      RefDirection(IfcDirection#365): DirectionRatios=(1.0, 0.0, 0.0)
     TrueNorth(IfcDirection#372): DirectionRatios=(0.766044443119, 0.642787609687)
   Item[0](IfcTextLiteralWithExtent#75793): Literal=50, Path=LEFT, BoxAlignment=bottom-left
    Placement(IfcAxis2Placement2D#75792): 
     Location(IfcCartesianPoint#75790): Coordinates=(13.5399999976, 10.620000015)
     RefDirection(IfcDirection#75788): DirectionRatios=(0.0, 1.0)
    Extent(IfcPlanarExtent#75787): SizeInX=0.549829, SizeInY=0.4
   Item[1](IfcTextLiteralWithExtent#75806): Literal=30, Path=LEFT, BoxAlignment=bottom-left
    Placement(IfcAxis2Placement2D#75805): 
     Location(IfcCartesianPoint#75803): Coordinates=(13.5399999976, 9.13017100882)
     RefDirection(IfcDirection#75801): DirectionRatios=(0.0, 1.0)
    Extent(IfcPlanarExtent#75800): SizeInX=0.549829, SizeInY=0.4
   (中略: Items[n]にテキストや引き出し線の定義が続く。テキストはIfcTextLiteralWithExtent, 引き出し線はIfcGeometricCurveSet)
   Item[6](IfcGeometricCurveSet#75871): 
    Element[0](IfcPolyline#75869): Points=(#75865=IfcCartesianPoint((13.5,-0.68)), #75867=IfcCartesianPoint((13.5,-0.5)))
   Item[7](IfcGeometricCurveSet#75882): 
    Element[0](IfcPolyline#75880): Points=(#75876=IfcCartesianPoint((13.5,-0.5)), #75878=IfcCartesianPoint((13.5,0.)))
   (以下略)

①のIFMETextの部分だけ抜き出すとこのようになります:

IfcAnnotation(globalId = 0OL3LCjon8C8eMGVs718Tk) ... 座標(0.0, 0.0, 2.7)
  +-- fcTextLiteralWithExtent(#75793) ... 座標(13.5399999976, 10.620000015)

#75793のテキスト位置は親IfcAnnotation要素(0.0, 0.0, 2.7)からの相対位置(13.5399999976, 10.620000015)で、 (13.5399999976, 10.620000015, 2.7)ということになりそうです。
ifcOpenShellの座標はなんとなく正しそう!

ちなみに正しい座標x(13.5399999976)とy(10.620000015)の値を足すとFMEで取れる誤った座標y(24.1600000126)になりますね。
FME側では、何らかの条件でIFMETextのy座標にx座標が加算されてしまうバグでもあるのでしょうか?。。

実装

早速実装してみます。
実装コードはgithubのannotation_text_to_gltf.pyです。
glTFファイル生成部分は前回同様trimeshを利用しました。

結果

(こんどこそ)やったぜ!