Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

C# Occt.NET で STEP ファイルから頂点座標を抽出してみる

0
Posted at

はじめに

Open CASCADE (OCCT) の C# ラッパーである Occt.NET を使用して STEP ファイルから頂点座標を抽出してみます。

改訂履歴

  • 2026/06/07 : 採用を見送る旨の警告を追加。
  • 2026/06/06 : 初版公開。

本文

警告
この Occt.NET ライブラリですが、提供元が中国の企業であり、また詳細な情報も見当たらないため、プロダクトへの採用を見送ることにしました。もし使用する場合は、自己責任でお願いします。

https://www.nuget.org/packages/Occt.NET/

1. 環境

  • .NET 8.0
  • FreeCAD 1.1.1
  • Occt.NET 7.9.0
  • Visual Studio Community 2022

2. 準備

2-1. STEP ファイルの作成

今回は FreeCAD のサンプルモデルをそのまま使用します。

エクスポートから、ファイルの種類に STEP with colors (*.step *stp) を選択して、保存することで STEP ファイルとして出力できます。

2-2. Visual Studio 側の準備

プロジェクトのビルド構成を x64 に設定します。

一度、プロジェクトをビルドします。出力ディレクトリに occt というフォルダが自動生成されます。

実行時にネイティブ DLL を参照できるようにするため、occt フォルダの中身を全て .exe と同じ階層に移動させます。

3. 実装

StepFileReader は STEP ファイルを読み込んで抽出した CAD モデルを返すクラスです。

StepFileReader.cs
using Occt;

namespace OpenCascadeExample;

/// <summary>
/// STEP ファイルの読込機能を提供します。
/// </summary>
public static class StepFileReader
{
    /// <summary>
    /// 指定されたファイルパスから STEP ファイルを読込し、<see cref="CadModel"/> オブジェクトを生成します。
    /// </summary>
    /// <param name="filePath">STEP ファイルのパス。</param>
    /// <param name="cadModel">生成された <see cref="CadModel"/> オブジェクト。</param>
    /// <returns>読込が成功した場合は true、それ以外の場合は false。</returns>
    public static bool TryRead(string filePath, out CadModel? cadModel)
    {
        cadModel = null;

        // ファイルパスが空でないことを確認する。
        if (string.IsNullOrEmpty(filePath))
        {
            return false;
        }

        // ファイルの拡張子を確認する。
        var ext = Path.GetExtension(filePath).ToLower();
        if (ext != ".stp" && ext != ".step")
        {
            // 実際は IGES や STL も使用できるが、今回は STEP のみに限定する。
            return false;
        }

        // STEP ファイルを読込する。
        STEPControl_Reader reader = new();
        IFSelect_ReturnStatus status = reader.ReadFile(filePath);
        if (status != IFSelect_ReturnStatus.IFSelect_RetDone)
        {
            return false;
        }

        // 形状データを抽出する。
        reader.TransferRoots();
        TopoDS_Shape? shape = reader.OneShape();

        // CAD モデルに変換する。
        cadModel = new CadModel(shape);
        return true;
    }
}

CadModel は CAD モデルの本体を扱うクラスです。OCCT 自体の複雑な処理をカプセル化し、C# 側で扱いやすいインターフェースを提供するイメージで実装しています。

CadModel.cs
using System.Numerics;
using Occt;

namespace OpenCascadeExample;

/// <summary>
/// CAD モデルを表します。
/// </summary>
public class CadModel
{
    private readonly TopoDS_Shape? _value;

    /// <summary>
    /// <see cref="CadModel"/> クラスの新しいインスタンスを初期化します。
    /// </summary>
    /// <param name="shape"><see cref="TopoDS_Shape"/> 型のオブジェクト。</param>
    public CadModel(TopoDS_Shape shape)
    {
        if (shape is null || shape.IsNull)
        {
            _value = null;
            return;
        }

        _value = shape;
    }

    /// <summary>
    /// 頂点座標を抽出します。
    /// </summary>
    /// <returns>頂点座標のリスト。</returns>
    public List<Vector3> ExtractVertices()
    {
        var pointList = new List<Vector3>();

        if (_value == null || _value.IsNull)
        {
            return pointList;
        }

        // 頂点情報 (VERTEX) を抽出する。
        TopExp.MapShapes(
            _value,
            TopAbs_ShapeEnum.TopAbs_VERTEX,
            out TopTools_IndexedMapOfShape vertexMap);

        var count = vertexMap.Extent;
        for (var i = 1; i <= count; i++)
        {
            // 頂点情報から、形状を取得する。
            TopoDS_Shape shape = vertexMap.FindKey(i);
            if (shape is TopoDS_Vertex vertex)
            {
                // 形状が頂点に変換できる場合、座標を取得する。
                gp_Pnt point = BRep_Tool.Pnt(vertex);

                // 座標を C# で扱いやすい Vector3 型に変換してリストに追加する。
                // ※ double → float 変換による、丸め誤差有り。
                Vector3 v3 = new((float)point.X, (float)point.Y, (float)point.Z);
                pointList.Add(v3);
            }
        }

        // 座標を小さい順にソートする。
        var sorted = pointList
            .OrderBy(v => v.X)
            .ThenBy(v => v.Y)
            .ThenBy(v => v.Z)
            .ToList();

        return sorted;
    }
}

4. 動作確認

テスト用に Windows Forms で画面を作りました。

インポートボタンで以下の処理が走ります。

MainForm.cs
private void ImportButton_Click(object sender, EventArgs e)
{
    if (!StepFileReader.TryRead(InputTextBox.Text, out CadModel? cadModel))
    {
        return;
    }

    List<Vector3>? vertices = cadModel?.ExtractVertices();
    if (vertices is null)
    {
        return;
    }
    
    ResultListBox.Items.Clear();
    for(var i = 0; i < vertices.Count; i++)
    {
        ResultListBox.Items.Add($"{i:0000} : X={vertices[i].X}, Y={vertices[i].Y}, Z={vertices[i].Z}");
    }
}

とりあえず、座標データが抽出されました。

OCCT.NET 実行結果.log
[0]: X=-55, Y=-20, Z=-100
[1]: X=-55, Y=-20, Z=0
[2]: X=-55, Y=20, Z=-100
[3]: X=-55, Y=20, Z=0
[4]: X=-45, Y=-10, Z=-15
[5]: X=-45, Y=-10, Z=0
[6]: X=-45, Y=10, Z=-15
[7]: X=-45, Y=10, Z=0
[8]: X=-40, Y=-20, Z=-10
[9]: X=-40, Y=-20, Z=0
[10]: X=-40, Y=-10, Z=-10
[11]: X=-40, Y=-10, Z=0
[12]: X=-24, Y=-20, Z=-10
[13]: X=-24, Y=-20, Z=0
[14]: X=-24, Y=-10, Z=-10
[15]: X=-24, Y=-10, Z=0
[16]: X=-8, Y=-20, Z=-10
[17]: X=-8, Y=-20, Z=0
[18]: X=-8, Y=-10, Z=-10
[19]: X=-8, Y=-10, Z=0
[20]: X=8, Y=-20, Z=-10
[21]: X=8, Y=-20, Z=0
[22]: X=8, Y=-10, Z=-10
[23]: X=8, Y=-10, Z=0
[24]: X=20.358963, Y=20, Z=-100
[25]: X=20.358963, Y=20, Z=0
[26]: X=24, Y=-20, Z=-10
[27]: X=24, Y=-20, Z=0
[28]: X=24, Y=-10, Z=-10
[29]: X=24, Y=-10, Z=0
[30]: X=30.505081, Y=10, Z=-15
[31]: X=30.505081, Y=10, Z=0
[32]: X=40, Y=-20, Z=-10
[33]: X=40, Y=-20, Z=0
[34]: X=40, Y=-10, Z=-10
[35]: X=40, Y=-10, Z=0
[36]: X=55, Y=-20, Z=-100
[37]: X=55, Y=-20, Z=0
[38]: X=55, Y=-10, Z=-15
[39]: X=55, Y=-10, Z=0
[40]: X=79.99998, Y=15, Z=-50
[41]: X=79.99998, Y=15, Z=0

5. 検証

Occt.NET の抽出結果が正しいか検証するため、FreeCAD の Python コンソールで以下のプログラムをペーストして実行します。

座標の小さい順で頂点座標を取得する.py
sel = Gui.Selection.getSelection()
if sel and hasattr(sel[0], "Shape"):
    obj = sel[0]
    sorted_vertexes = sorted(
        obj.Shape.Vertexes, key=lambda v: (v.Point.x, v.Point.y, v.Point.z))  
    print(f"--- {obj.Name} の頂点座標一覧(X > Y > Z軸の順) ---")
    for i, vertex in enumerate(sorted_vertexes):
        p = vertex.Point
        print(f"[{i}]: X={p.x:.3f}, Y={p.y:.3f}, Z={p.z:.3f}")
else:
    print("オブジェクトを選択してください。")

ざっと見た感じ、合ってそうですね。

FreeCAD 実行結果.log
[0]: X=-55.000, Y=-20.000, Z=-100.000
[1]: X=-55.000, Y=-20.000, Z=0.000
[2]: X=-55.000, Y=20.000, Z=-100.000
[3]: X=-55.000, Y=20.000, Z=0.000
[4]: X=-45.000, Y=-10.000, Z=-15.000
[5]: X=-45.000, Y=-10.000, Z=0.000
[6]: X=-45.000, Y=10.000, Z=-15.000
[7]: X=-45.000, Y=10.000, Z=0.000
[8]: X=-40.000, Y=-20.000, Z=-10.000
[9]: X=-40.000, Y=-20.000, Z=0.000
[10]: X=-40.000, Y=-10.000, Z=-10.000
[11]: X=-40.000, Y=-10.000, Z=0.000
[12]: X=-24.000, Y=-20.000, Z=-10.000
[13]: X=-24.000, Y=-20.000, Z=0.000
[14]: X=-24.000, Y=-10.000, Z=-10.000
[15]: X=-24.000, Y=-10.000, Z=0.000
[16]: X=-8.000, Y=-20.000, Z=-10.000
[17]: X=-8.000, Y=-20.000, Z=0.000
[18]: X=-8.000, Y=-10.000, Z=-10.000
[19]: X=-8.000, Y=-10.000, Z=0.000
[20]: X=8.000, Y=-20.000, Z=-10.000
[21]: X=8.000, Y=-20.000, Z=0.000
[22]: X=8.000, Y=-10.000, Z=-10.000
[23]: X=8.000, Y=-10.000, Z=0.000
[24]: X=20.359, Y=20.000, Z=-100.000
[25]: X=20.359, Y=20.000, Z=0.000
[26]: X=24.000, Y=-20.000, Z=-10.000
[27]: X=24.000, Y=-20.000, Z=0.000
[28]: X=24.000, Y=-10.000, Z=-10.000
[29]: X=24.000, Y=-10.000, Z=0.000
[30]: X=30.505, Y=10.000, Z=0.000
[31]: X=30.505, Y=10.000, Z=-15.000
[32]: X=40.000, Y=-20.000, Z=-10.000
[33]: X=40.000, Y=-20.000, Z=0.000
[34]: X=40.000, Y=-10.000, Z=-10.000
[35]: X=40.000, Y=-10.000, Z=0.000
[36]: X=55.000, Y=-20.000, Z=-100.000
[37]: X=55.000, Y=-20.000, Z=0.000
[38]: X=55.000, Y=-10.000, Z=-15.000
[39]: X=55.000, Y=-10.000, Z=0.000
[40]: X=80.000, Y=15.000, Z=-50.000
[41]: X=80.000, Y=15.000, Z=0.000

おわりに

C++ の Open CASCADE を自前でラッパーから実装するのは、あまりに大変そうだったため、手軽に扱える NuGet ライブラリが存在するのはとてもありがたいです。

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?