[Astro] #118 STEP / IGES / BREP 3D Viewer — occt-import-js(WASM) + Three.jsによるCADファイルビューア
概要
CADの標準交換フォーマットであるSTEP(.step/.stp)、IGES(.iges/.igs)、BREP(.brep/.brp)ファイルを、ブラウザ上で3D表示するビューアを構築した。パース処理にはOpenCASCADEのWASMビルドであるocct-import-jsを使用し、サーバーへのアップロード不要でファイルはすべてローカル環境で処理される。
STEP / IGES 3D Viewer
STEP/IGES/BREPファイルをブラウザで3D表示。階層ツリー、断面表示、測定ツール、GLBエクスポート対応。
lain-lab.com動画(GIF)
スクリーンショット
sample data
サンプルデータは以下のサイトからお借りしています。
STEP Tools — Sample AP203 Data Files
Sample STEP files for use with AP203 applications. Various geometry types from ST-ACIS, Pro/E, CATIA sources.
www.steptools.com
ADVENTURE Project — サンプルデータ
東京大学 設計用大規模計算力学システム開発プロジェクト。パンテオンのIGES形式CADデータ等を公開。
adventure.sys.t.u-tokyo.ac.jp今回の実装内容
| 機能・モジュール | 概要 |
|---|---|
| CADファイルパース | occt-import-js(WASM)によるSTEP/IGES/BREPの解析・テッセレーション |
| 3D描画 | Three.js MeshStandardMaterial、per-face color、EdgesGeometry |
| 階層ツリー | パーツツリー表示、コネクターライン、表示ON/OFF、クリック→ハイライト+フォーカス |
| テッセレーション調整 | 5段階品質(Coarse〜Ultra)、単位選択(mm/cm/m/inch/foot) |
| 断面表示 | ClippingPlaneによるX/Y/Z軸断面、位置スライダー、フリップ |
| 測定ツール | 2点クリック→距離+ΔX/ΔY/ΔZ表示 |
| ビュー制御 | Iso/Top/Front/Right/Fit/Center、Grid/Axes/Edges/Wireframe切替 |
| 表示設定 | 背景色、エッジ色の変更 |
| GLBエクスポート | GLTFExporterによるGLB形式出力 |
| スクリーンショット | ビューポートのPNG画像保存 |
| ローディングUI | ビューポートオーバーレイ(スピナー+プログレスバー) |
背景 — DXF Viewerからの展開
前回のDXF 3D Viewerでは2D CADフォーマット(DXF)に対応した。今回は3D CADのネイティブ交換フォーマットであるSTEP/IGESに対応することで、lain-labのCADツール群をさらに拡充した。
DXF Viewerがdxf-parser(JavaScriptパーサー)を使用したのに対し、本ツールではOpenCASCADEのWASMビルド(occt-import-js)を使用している。OpenCASCADE(OCCT)は産業用CADカーネルであり、STEP/IGESの正確なB-Rep(境界表現)ジオメトリをポリゴンメッシュにテッセレーションする。
CADファイル (STEP/IGES/BREP)
→ occt-import-js (WASM, ~7.6MB)
→ テッセレーション(B-Rep → ポリゴン)
→ Three.js (MeshStandardMaterial + EdgesGeometry)
→ GLBエクスポート
スクリーンショット
STEPファイル — アセンブリ構造
IGESファイル — パンテオン
断面表示 + 測定ツール
技術実装の詳細
1. occt-import-jsの出力構造
occt-import-jsはSTEP/IGES/BREPファイルをバイナリ(Uint8Array)として受け取り、テッセレーション済みのメッシュデータをJSON形式で返す。
// WASM初期化
const occt = await occtimportjs();
// STEPファイルのパース
const fileBuffer = new Uint8Array(buffer);
const result = occt.ReadStepFile(fileBuffer, {
linearUnit: 'millimeter',
linearDeflectionType: 'bounding_box_ratio',
linearDeflection: 0.01, // テッセレーション品質
angularDeflection: 0.5,
});
出力のJSON構造は以下の通り。
interface OcctResult {
success: boolean;
root: { // 階層構造のルートノード
name: string;
meshes: number[]; // meshes配列へのインデックス
children: OcctNode[]; // 子ノード
};
meshes: OcctMesh[]; // Three.js互換のメッシュ配列
}
interface OcctMesh {
name: string;
color: [number, number, number] | null;
brep_faces: { // B-Rep面ごとの色情報
first: number; // 三角形インデックス(開始)
last: number; // 三角形インデックス(終了)
color: [number, number, number] | null;
}[];
attributes: {
position: { array: number[] };
normal: { array: number[] };
};
index: { array: number[] };
}
meshesの形式はThree.jsのBufferGeometryとほぼ同一で、position/normal/indexをそのままFloat32BufferAttributeとBufferAttributeに変換できる。
2. per-face colorの処理
STEPファイルではB-Rep(境界表現)の各面に異なる色が割り当てられていることがある。occt-import-jsはこの情報をbrep_faces配列として出力する。
function buildMesh(geometryMesh, showEdges) {
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position',
new THREE.Float32BufferAttribute(geometryMesh.attributes.position.array, 3));
let materials = [defaultMaterial];
if (geometryMesh.brep_faces.length > 0) {
// 面ごとに異なるマテリアルを割り当て
for (const face of geometryMesh.brep_faces) {
const color = face.color
? new THREE.Color(face.color[0], face.color[1], face.color[2])
: defaultColor;
materials.push(new THREE.MeshStandardMaterial({ color }));
}
// geometry.addGroupで三角形範囲→マテリアルインデックスを対応付け
geometry.addGroup(firstIndex * 3, count * 3, materialIndex);
}
return new THREE.Mesh(geometry, materials);
}
geometry.addGroupでインデックス範囲とマテリアル配列のインデックスを対応付けることで、1つのメッシュ内で面ごとに異なる色を表示する。
3. 断面表示(Cross Section)
Three.jsのClippingPlaneを使用して断面表示を実現。マテリアルのclippingPlanesプロパティに平面を設定するだけで、指定平面の片側のジオメトリが非表示になる。
renderer.localClippingEnabled = true;
const clipPlane = new THREE.Plane(new THREE.Vector3(0, -1, 0), 0);
function setClipping(enabled) {
const planes = enabled ? [clipPlane] : [];
modelGroup.traverse((child) => {
if (child instanceof THREE.Mesh) {
const mat = child.material;
if (Array.isArray(mat)) {
mat.forEach(m => { m.clippingPlanes = planes; });
} else {
mat.clippingPlanes = planes;
}
}
});
}
平面の法線方向(X/Y/Z軸)と位置(バウンディングボックスに対する割合)をUIから制御し、Flipチェックボックスで法線を反転させる。エッジ線のマテリアルにも同じClippingPlaneを適用して、断面で面とエッジが一致するようにしている。
4. 測定ツール
Raycasterでメッシュ上の2点をクリックし、3D空間上の距離を計測する。
function addMeasurePoint(point) {
measurePoints.push(point.clone());
if (measurePoints.length === 2) {
const dx = measurePoints[1].x - measurePoints[0].x;
const dy = measurePoints[1].y - measurePoints[0].y;
const dz = measurePoints[1].z - measurePoints[0].z;
const dist = Math.sqrt(dx * dx + dy * dy + dz * dz);
// マーカーと線分を描画
measureGroup.add(createMarker(measurePoints[0], 0x00ff88));
measureGroup.add(createMarker(measurePoints[1], 0xff4444));
measureGroup.add(createMeasureLine(measurePoints[0], measurePoints[1]));
}
}
マーカーはSphereGeometryでdepthTest: falseを設定し、モデルの裏側でも常に表示される。マーカーサイズはモデル全体のバウンディングボックスに対して自動スケールする。3点目のクリックで自動リセットし、新規測定を開始する。
5. GLBエクスポート
Three.jsのGLTFExporterでbinary: trueオプションを指定し、GLB形式で出力する。
const { GLTFExporter } = await import('three/examples/jsm/exporters/GLTFExporter.js');
const exporter = new GLTFExporter();
exporter.parse(modelGroup, (result) => {
const blob = new Blob([result], { type: 'application/octet-stream' });
const url = URL.createObjectURL(blob);
// ダウンロード
}, null, { binary: true });
GLTFExporterはdynamic importで遅延読み込みし、初期バンドルサイズへの影響を抑えている。マテリアルはMeshStandardMaterialを使用しているため、GLTFの標準PBRマテリアルとして正しくエクスポートされる。
テッセレーション品質
occt-import-jsのlinearDeflectionパラメータでテッセレーション品質を制御する。値が小さいほど高品質(ポリゴン数増加)。
| 品質レベル | linearDeflection | 用途 |
|---|---|---|
| Coarse | 0.1 | 大規模モデルのプレビュー |
| Low | 0.03 | 軽量表示 |
| Medium | 0.01 | 標準(デフォルト) |
| High | 0.003 | 詳細確認 |
| Ultra | 0.001 | 最高品質、曲面の忠実再現 |
品質変更時はWASMによるリパースが発生するため、大きいファイルでは数秒のローディングが入る。
技術スタック
- Astro — ページ構築
- Three.js — 3D描画、OrbitControls、EdgesGeometry、ClippingPlane、GLTFExporter
- occt-import-js — OpenCASCADE WASMビルド(STEP/IGES/BREPパーサー、CDN経由)
- Raycaster — パーツ選択、測定ツールの点ピック
更新履歴
- [v0.1]: 初期実装。STEP/IGES/BREPの読み込み・3D表示、階層ツリー、テッセレーション品質設定、ビュー制御、スクリーンショット出力。
- [v1.0]: 断面表示(ClippingPlane)、測定ツール(2点間距離)、GLBエクスポート、Center to Originボタン、パーツクリック→ハイライト+フォーカス、ビューポートローディングオーバーレイ、MeshStandardMaterial化。