PowerQueryで3Dファイルを読み込む

## はじめに
最近、こんな投稿を見つけ驚愕しました。
https://community.powerbi.com/t5/Community-Blog/PowerQuery-Decompress-zip-Files/ba-p/1326035
クエリ内でZipを強引に解凍しとる…!!

このテクニックを使えば、実質ZipのPowerpointやwordのコンテンツも集計できますね。
Web.Page関数内でjavascriptを呼べば正規表現も使えるので、文書検索ツールも簡単に構築できてしまいそうです。

…PowerQueryはじまったな（上記は2020の記事であり筆者が無知なだけです）。


## 本題
Zip解凍に感動したので、STL(3Dモデル)読み込み関数を作ってみました。
3Dデータのノード座標読み取り用カスタム関数です。

Zipの3Dモデルといえば3MFですが、今回扱うのはSTLです。3MFを期待した人にはすいません。

```C#:STLReader  (STLFileはBinary型)
(STLFile) => 
let
    Vertex = BinaryFormat.Record([
        NX1 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        NY1 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        NZ1 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        X1 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        Y1 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        Z1 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        X2 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        Y2 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        Z2 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        X3 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        Y3 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        Z3 = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        Blank = BinaryFormat.ByteOrder(BinaryFormat.SignedInteger16, ByteOrder.LittleEndian)
    ]),
    GetData = BinaryFormat.List(Vertex),
    Entries = List.Transform(GetData(Binary.Range(STLFile,84)),
                            (e) => 
                                {
                                    [I=1,X=e[X1],Y=e[Y1],Z=e[Z1]],
                                    [I=2,X=e[X2],Y=e[Y2],Z=e[Z2]],
                                    [I=3,X=e[X3],Y=e[Y3],Z=e[Z3]]
                                }
                            ),
    Expanded = Table.ExpandListColumn(Table.FromList(Entries, Splitter.SplitByNothing(), null, null, ExtraValues.Error),"Column1"),
    Expanded2 = Table.ExpandRecordColumn(Expanded, "Column1", {"I", "X", "Y", "Z"}, {"I", "X", "Y", "Z"}),
    Transformed = Table.TransformColumnTypes(Expanded2,{{"I", Int64.Type}, {"X", type number}, {"Y", type number}, {"Z", type number}})
in
    Transformed
```
## 簡単な解説
STL(バイナリ型)の読み込み関数です。

STLファイルの構造として、先頭80バイトは気にする必要はありません。
次の4バイトは構成される三角形の数(4バイト整数型)ですが、
ここも今回は必要ありません。

なので、最初にBinary.Range関数で最初の84バイトを飛ばします。
ここから50バイト毎にデータが続くので、Vertexとして定義をします。
~~構造体みたいすね~~
12 :面の法線ベクトル(XYZ 3×4バイト[単精度])
36 :三角メッシュを構成する座標データ(XYZ 3×4バイト[単精度]×3)
2  :ブランクデータ

法線データはいらないので、List.Transformで座標データだけ指定しています。
また、集計結果に対してTable.TransformColumnTypesで型を指定しています。
後でわざわざ数値型にするのは面倒くさいですからね。

## 使い方
下の感じで読み込めます。
例では直接読み込んでますが、カスタム列の追加で、
STLReader(File.Contents([フルパス列名]))とかSTLReader[バイナリ列名]とかでOKです。

```C#:MainTable(Filepath=*.stl)
let
    source =STLReader(File.Contents(Filepath))
in
    source
```

試しにトリケラトプスの化石の3DファイルをPowerBIに取り込み表示してみます。

（Z座標：全範囲）
![trk1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225394/014d88ef-2d7b-d85e-6179-702e45ce361f.png)
（Z座標：フィルタ後）
![trk3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1225394/1a8769f2-f4ac-f525-f1f3-7204ffc91201.png)