[Astro] #117 DXF 3D Viewer — dxf-parser + Three.jsによるDXFファイルの2D/3Dビューア
概要
CADの標準フォーマットであるDXF(Drawing Exchange Format)ファイルを、ブラウザ上で2D/3D表示するビューアを構築した。サーバーへのアップロード不要で、ファイルはすべてローカル環境(ブラウザ内)で処理される。
DXF 3D Viewer
DXFファイルをブラウザで2D/3D表示。レイヤー管理、テキスト表示切替、スクリーンショット出力対応。
lain-lab.com動画(GIF)
sample data
サンプルデータは、「クワバラ設計企画」のWEBサイトよりお借りしています。
クワバラ設計企画
自動機・省力機械・FA装置設計をはじめとする各種機械設計を行っております。オンラインによるデータの受け渡しも可能ですので、遠方の方でもお気軽にお問い合わせください。
www.kuwabara-mech-design.jp今回の実装内容
| 機能・モジュール | 概要 |
|---|---|
| DXFパース | dxf-parserによるエンティティ・レイヤー・ブロック定義の解析 |
| 9種エンティティ描画 | LINE, LWPOLYLINE, POLYLINE, CIRCLE, ARC, ELLIPSE, 3DFACE, POINT, SOLID |
| TEXT/MTEXT描画 | CanvasTexture + Spriteによるテキストラベル表示 |
| DIMENSION描画 | 寸法線の線分+寸法値テキスト表示 |
| INSERT描画 | ブロック参照の再帰展開(座標変換・スケール・回転対応) |
| レイヤー管理 | AutoCAD Color Index対応、レイヤー別ON/OFF切替 |
| Shift-JIS自動判定 | 日本語DXF(ANSI_932)の自動検出・再読み込み |
| ビュー制御 | Top(XY)/Front(XZ)切替、Grid/Axes/Text表示切替 |
| スクリーンショット | 現在のビューポートをPNG画像として保存 |
背景 — Point Cloud Viewerとの連携
本ツールの開発動機は、自作のPoint Cloud Viewer(点群ビューア)で実装した「断面ポリライン→DXFエクスポート」機能の出力を検証するためのビューアが必要だったこと。
Point Cloud Viewerでは、点群データを任意の軸でスライスし、断面プロファイルをDXFのPOLYLINEとしてエクスポートする機能がある。その出力されたDXFを、別途CADソフトを起動せずにブラウザ内で即座に確認できる環境を作りたかった。
結果として、自分のツール間でデータの入出力が完結する形になった。
点群データ (PLY/LAS/LAZ)
→ Point Cloud Viewer(スライス&断面生成)
→ DXF Export(POLYLINE)
→ DXF 3D Viewer(確認・スクリーンショット)
スクリーンショット
サンプル図面の表示
Point Cloud Viewer出力の断面DXF
技術実装の詳細
1. dxf-parserの出力構造
dxf-parserはDXFファイルをテキストとして受け取り、以下の構造に変換する。
interface IDxf {
header: Record<string, IPoint | number>;
entities: IEntity[]; // エンティティ配列(LINE, CIRCLE等)
blocks: Record<string, IBlock>; // ブロック定義(INSERT参照先)
tables: ITables; // レイヤー定義等
}
各エンティティはtype(種別)、layer(所属レイヤー)、colorIndex(色番号)と、種別固有のデータ(頂点座標、中心点、半径等)を持つ。16種のエンティティに対応しているが、本ツールでは実用上重要な9種+TEXT/MTEXT/DIMENSION/INSERTの計13種を描画対象とした。
2. Three.jsでのエンティティ描画
各DXFエンティティをThree.jsのオブジェクトに変換する。基本方針は「LINEベースのエンティティはBufferGeometry + LineBasicMaterial、面を持つエンティティはMesh」。
// LINE: 2頂点を結ぶ線分
function drawLine(entity, color) {
const points = entity.vertices.map(
v => new THREE.Vector3(v.x, v.y, v.z || 0)
);
const geometry = new THREE.BufferGeometry().setFromPoints(points);
const material = new THREE.LineBasicMaterial({ color });
const line = new THREE.Line(geometry, material);
getLayerGroup(entity.layer).add(line);
}
// CIRCLE: EllipseCurveで円を生成し、LineとしてXY平面に描画
function drawCircle(entity, color) {
const curve = new THREE.EllipseCurve(
entity.center.x, entity.center.y,
entity.radius, entity.radius,
0, Math.PI * 2, false, 0
);
const points = curve.getPoints(64).map(
p => new THREE.Vector3(p.x, p.y, entity.center.z || 0)
);
// ... BufferGeometry + Line
}
// ARC: EllipseCurveのstartAngle/endAngleを指定
// ELLIPSE: majorAxis + axisRatioからradiusX/Yと回転角を計算
3. TEXT/MTEXTの描画(Sprite方式)
DXFのTEXT/MTEXTは2Dラベルとして描画する必要がある。Three.jsのCSS2DRendererも選択肢だが、スクリーンショット出力との相性を考慮し、CanvasTexture + Sprite方式を採用した。
function createTextSprite(text, position, height, color) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// テキストをCanvasに描画
ctx.font = `${fontSize}px monospace`;
ctx.fillStyle = '#' + color.toString(16).padStart(6, '0');
ctx.fillText(text, 4, canvas.height / 2);
// CanvasTexture → SpriteMaterial → Sprite
const texture = new THREE.CanvasTexture(canvas);
const material = new THREE.SpriteMaterial({ map: texture, transparent: true });
const sprite = new THREE.Sprite(material);
// ワールド空間でテキスト高さに合わせてスケール
sprite.scale.set(worldHeight * aspect, worldHeight, 1);
sprite.position.set(position.x, position.y, position.z);
return sprite;
}
Sprite方式の利点は、常にカメラに正対するためどの角度からもテキストが読めること。またdepthTest: falseを指定することで、他のジオメトリに隠れずに常に表示される。
テキストのON/OFF切替は、entityGroup.traverseでSpriteインスタンスを検出してvisibleを切り替えるだけで実現できる。
4. INSERT(ブロック参照)の再帰展開
DXFのINSERTエンティティは、blocksテーブルに定義されたブロックを指定位置・スケール・回転で配置する参照。描画時にはブロック内の全エンティティの座標を変換して展開する。
function drawInsert(entity, dxf, layerColors) {
const block = dxf.blocks[entity.name];
const pos = entity.position;
const cos = Math.cos(entity.rotation * Math.PI / 180);
const sin = Math.sin(entity.rotation * Math.PI / 180);
function transformPoint(p) {
const x = p.x * entity.xScale;
const y = p.y * entity.yScale;
return {
x: x * cos - y * sin + pos.x,
y: x * sin + y * cos + pos.y,
z: p.z * entity.zScale + pos.z
};
}
for (const blockEntity of block.entities) {
// 座標を変換したコピーを作成
const transformed = JSON.parse(JSON.stringify(blockEntity));
if (transformed.vertices)
transformed.vertices = transformed.vertices.map(transformPoint);
if (transformed.center)
transformed.center = transformPoint(transformed.center);
// 既存の描画関数にそのまま渡す
switch (transformed.type) {
case 'LINE': drawLine(transformed, color); break;
case 'CIRCLE': drawCircle(transformed, color); break;
// ...
}
}
}
座標変換後のコピーを既存の描画関数に渡すことで、描画関数自体を変更せずにINSERT対応を実現した。
5. レイヤー管理とAutoCAD Color Index
DXFのレイヤーはtables.layer.layersに定義されており、各レイヤーにAutoCAD Color Index(ACI)が割り当てられている。エンティティの色は以下の優先順で決定される。
- エンティティ自体の
colorIndex(256以外の場合) - 所属レイヤーのACI色
- デフォルト色
const ACI_COLORS = {
1: 0xff0000, 2: 0xffff00, 3: 0x00ff00, 4: 0x00ffff,
5: 0x0000ff, 6: 0xff00ff, 7: 0xffffff, 8: 0x808080, ...
};
レイヤーごとにTHREE.Groupを作成し、UIからの表示切替はgroup.visibleのトグルで実現。
6. Shift-JIS自動判定
日本のCADソフトが出力するDXFはShift-JIS(コードページ ANSI_932)でエンコードされている場合が多い。FileReader.readAsText()のデフォルトはUTF-8のため、日本語レイヤー名やテキストが文字化けする。
対策として、最初にUTF-8で読み込み、文字化け(U+FFFD置換文字)またはヘッダー内のANSI_932指定を検出した場合にShift-JISで再読み込みする2段階方式を採用した。
reader.onload = (e) => {
let text = e.target.result;
if (text.includes('\ufffd') || text.includes('ANSI_932')) {
const reReader = new FileReader();
reReader.onload = (e2) => parseDxf(e2.target.result);
reReader.readAsText(file, 'Shift_JIS');
return;
}
parseDxf(text);
};
reader.readAsText(file);
対応エンティティ一覧
| エンティティ | 描画方法 | 備考 |
|---|---|---|
| LINE | BufferGeometry + Line | 2頂点の線分 |
| LWPOLYLINE | BufferGeometry + Line | 軽量ポリライン、閉合対応 |
| POLYLINE | BufferGeometry + Line | 3Dポリライン |
| CIRCLE | EllipseCurve → Line | 64分割で近似 |
| ARC | EllipseCurve(角度指定)→ Line | 開始角/終了角対応 |
| ELLIPSE | EllipseCurve + 長軸/短軸比 → Line | 回転対応 |
| POINT | Points | sizeAttenuation: falseで固定サイズ |
| 3DFACE | Mesh(三角/四角分割)+ ワイヤーフレーム | 半透明面+輪郭線 |
| SOLID | Mesh | 3DFACEと同一処理 |
| TEXT | CanvasTexture + Sprite | テキスト高さに合わせたスケーリング |
| MTEXT | CanvasTexture + Sprite | フォーマットコード除去、1行目表示 |
| DIMENSION | Line + Sprite | 寸法線+寸法値テキスト |
| INSERT | 再帰展開 | 座標変換・スケール・回転適用 |
技術スタック
- Astro — ページ構築
- Three.js — 3D描画、OrbitControls
- dxf-parser — DXFファイルパーサー(CDN経由で読み込み)
- HTML5 Canvas — テキストラベルのテクスチャ生成
更新履歴
- [v0.1]: 初期実装。LINE/LWPOLYLINE/POLYLINE/CIRCLE/ARC/ELLIPSE/POINT/3DFACE/SOLID描画、レイヤー管理、Shift-JIS自動判定。
- [v0.2]: TEXT/MTEXT描画(CanvasTexture + Sprite)、テキスト表示ON/OFFチェックボックス追加。
- [v0.3]: DIMENSION描画(寸法線+テキスト)、INSERT描画(ブロック参照の再帰展開・座標変換)。
- [v1.0]: スクリーンショットPNG出力機能追加。