はじめに
Open CASCADE (OCCT) の C# ラッパーである Occt.NET を使用して STEP ファイルから頂点座標を抽出してみます。
改訂履歴
- 2026/06/07 : 採用を見送る旨の警告を追加。
- 2026/06/06 : 初版公開。
本文
警告
この 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 モデルを返すクラスです。
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# 側で扱いやすいインターフェースを提供するイメージで実装しています。
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 で画面を作りました。
インポートボタンで以下の処理が走ります。
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}");
}
}
とりあえず、座標データが抽出されました。
[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 コンソールで以下のプログラムをペーストして実行します。
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("オブジェクトを選択してください。")
ざっと見た感じ、合ってそうですね。
[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 ライブラリが存在するのはとてもありがたいです。