1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Power QueryでSTLファイルを解析して3Dプリント見積もりまで自動化する

Last updated at Posted at 2025-08-30

English Summary

Automated 3D Print Estimation from STL Files Using Power Query with copilot-san

今回の筆者(copilot君)について

以前のわたしの記事の感想をcopilot(gpt-5)君に聞いたところ、
ニッチだし実用性がないというまっとうな意見が得られました。

じゃあ、君ならどんな記事書くねん(半ギレ)と
問い詰めた意見を伺ったところ、
『3Dプリンタユーザーに向けて、表面積や体積、サイズ、コストを出せば
実用性があるからいいんじゃない(意訳)』と、
記事の文例とM言語のサンプルまでつけて返事してくれました。

思ったよりちゃんとしたものが返ってきたので、ちょっと修正して載せることにしました。
(関数に関して若干の修正は必要でした)

注意事項

  • 体積計算で誤差が予想されます。ご注意ください
  • そのため、体積が関係する重量やコスト等もあくまで目安になります

はじめに

前回の記事では、Power Queryを使ってSTLファイル(3Dモデル)の座標データを読み込む方法を紹介しました。 今回はその応用として、3Dプリントに必要な主要パラメータ(寸法・表面積・体積・重量・コスト・プリンタ適合可否)を自動算出し、Power BIで可視化する仕組みを作ります。

ゴール

  • STLファイルを読み込むだけで以下を自動計算
  • Width / Length / Height(造形サイズ)
  • Surface Area(表面積)
  • Volume(体積)
  • 材料重量(g)
  • 推定コスト(円)
  • Power BIでダッシュボード化し、複数モデルを一括管理

STL解析関数"STLReader"(Mコード)

(STLBinary as binary) =>
let
    DataStart = Binary.Range(STLBinary, 84),
    TriangleFormat = BinaryFormat.Record([
        NormalX = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        NormalY = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        NormalZ = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V1X = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V1Y = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V1Z = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V2X = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V2Y = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V2Z = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V3X = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V3Y = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        V3Z = BinaryFormat.ByteOrder(BinaryFormat.Single, ByteOrder.LittleEndian),
        AttributeByteCount = BinaryFormat.ByteOrder(BinaryFormat.SignedInteger16, ByteOrder.LittleEndian)
    ]),
    TriangleList = BinaryFormat.List(TriangleFormat)(DataStart),

    AllPoints = List.Combine(
        List.Transform(TriangleList, each {
            {[V1X],[V1Y],[V1Z]},
            {[V2X],[V2Y],[V2Z]},
            {[V3X],[V3Y],[V3Z]}
        })
    ),
    Xs = List.Transform(AllPoints, each _{0}),
    Ys = List.Transform(AllPoints, each _{1}),
    Zs = List.Transform(AllPoints, each _{2}),
    MinX = List.Min(Xs), MaxX = List.Max(Xs),
    MinY = List.Min(Ys), MaxY = List.Max(Ys),
    MinZ = List.Min(Zs), MaxZ = List.Max(Zs),
    Width  = MaxX - MinX,
    Length = MaxY - MinY,
    Height = MaxZ - MinZ,

    // 全頂点を抽出して重心を計算
    G = {
        List.Average(Xs),
        List.Average(Ys),
        List.Average(Zs)
    },
    // 三角形ごとの面積と体積(重心基準)を計算
    AddCalc = List.Transform(TriangleList, each
        let
            A = {[V1X],[V1Y],[V1Z]},
            B = {[V2X],[V2Y],[V2Z]},
            C = {[V3X],[V3Y],[V3Z]},
            // 相対座標
            Arel = {A{0}-G{0}, A{1}-G{1}, A{2}-G{2}},
            Brel = {B{0}-G{0}, B{1}-G{1}, B{2}-G{2}},
            Crel = {C{0}-G{0}, C{1}-G{1}, C{2}-G{2}},
            // 面積
            AB = {B{0}-A{0}, B{1}-A{1}, B{2}-A{2}},
            AC = {C{0}-A{0}, C{1}-A{1}, C{2}-A{2}},
            Cross = {
                AB{1}*AC{2} - AB{2}*AC{1},
                AB{2}*AC{0} - AB{0}*AC{2},
                AB{0}*AC{1} - AB{1}*AC{0}
            },
            Area = 0.5 * Number.Sqrt(Cross{0}*Cross{0} + Cross{1}*Cross{1} + Cross{2}*Cross{2}),
            // 体積(重心基準)
            Volume = (Arel{0}*(Brel{1}*Crel{2} - Brel{2}*Crel{1})
                    - Arel{1}*(Brel{0}*Crel{2} - Brel{2}*Crel{0})
                    + Arel{2}*(Brel{0}*Crel{1} - Brel{1}*Crel{0})) / 6
        in
            [Area=Area, Volume=Volume]
    ),
    TableCalc = Table.FromList(AddCalc, Splitter.SplitByNothing(), {"Record"}),
    Expanded = Table.ExpandRecordColumn(TableCalc, "Record", {"Area","Volume"}),
    TotalArea = List.Sum(Expanded[Area]),
    TotalVolume = Number.Abs(List.Sum(Expanded[Volume])),
    Weight = (TotalVolume / 1000) * 1.24,
    Cost = Weight * 3,
    Result = #table(
        {"Width[mm]","Length[mm]","Height[mm]","SurfaceArea[mm^2]","Volume[mm^3]","Weight[g]","Cost[yen]"},
        {{Width as number, Length as number, Height as number, TotalArea as number, TotalVolume as number,Weight as number,Cost as number}}
    )
in
    Result

呼び出しの例(Mコード)

Kドライブのフォルダ"stl"内にstlファイルが格納されている前提

let
    source           = Folder.Files("K:\stl"),
    row_selected     = Table.SelectRows(source, each [Extension] = ".stl"),
    column_added     = Table.AddColumn(row_selected, "stl", each STLReader([Content])),
    column_selected  = Table.SelectColumns(column_added,{"Name", "stl"}),
    column_names     = Table.ColumnNames(column_selected[stl]{0}),
    column_expanded  = Table.ExpandTableColumn(column_selected, "stl", column_names, column_names)
in
    column_expanded

Power BIでの可視化例

  • 推定コスト(重量 × 単価)
  • 体積(Volume)
  • 表面積(SurfaceArea)
  • 材料重量(Volume × 材料密度)
  • 寸法(Width / Length / Height)

table.png

  • 放射状ゲージグラフの最大値は3DプリンタのMAX造形サイズとしてください

応用アイデア

  • 材料別コスト比較(PLA / ABS / PETG)
  • プリント時間予測(Volume ÷ 造形速度)
  • 断面ヒートマップ表示
  • 複数モデルの一括解析

まとめ

Power Queryは表形式データだけでなく、バイナリ解析を通じて3Dモデルの幾何情報も扱えることが分かりました。 この仕組みを使えば、3Dプリントの前工程(造形可否チェック※・材料見積もり・コスト計算)を大幅に効率化できます。
※サイズ確認

補足

  • 体積計算:メッシュ(三角形)と原点を結ぶ四面体の総和
  • 面積計算:メッシュ(三角形)の面積の総和
    で求めているようです。
    計算はできてそうなので、時間ができたときに検算します。
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?