[Astro] #118 STEP / IGES / BREP 3D Viewer — occt-import-js(WASM) + Three.jsによるCADファイルビューア

[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を使用し、サーバーへのアップロード不要でファイルはすべてローカル環境で処理される。

動画(GIF)

[Astro] #118 STEP / IGES / BREP 3D Viewer

スクリーンショット

[Astro] #118 STEP / IGES / BREP 3D Viewer

sample data

サンプルデータは以下のサイトからお借りしています。

今回の実装内容

機能・モジュール概要
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ファイル — アセンブリ構造

STEP Viewer - アセンブリ構造

IGESファイル — パンテオン

STEP Viewer - IGES パンテオン

断面表示 + 測定ツール

STEP Viewer - 断面表示と測定

技術実装の詳細

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をそのままFloat32BufferAttributeBufferAttributeに変換できる。

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]));
  }
}

マーカーはSphereGeometrydepthTest: falseを設定し、モデルの裏側でも常に表示される。マーカーサイズはモデル全体のバウンディングボックスに対して自動スケールする。3点目のクリックで自動リセットし、新規測定を開始する。

5. GLBエクスポート

Three.jsのGLTFExporterbinary: 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用途
Coarse0.1大規模モデルのプレビュー
Low0.03軽量表示
Medium0.01標準(デフォルト)
High0.003詳細確認
Ultra0.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化。