はじめに:「見えないもの」を可視化する挑戦
「この金具、L字に曲げて欲しいんですけど、いくらかかりますか?」
オーダーメイド金属加工の現場では、こうした問い合わせが日々寄せられます。しかし、見積もりを出すまでには時間がかかります。図面を起こし、材料費を計算し、加工費を算出する。お客様は数日待った末、ようやく金額を知ることができます。
さらに難しいのは、完成形のイメージです。「L字に曲げる」と言っても、角度は90度なのか、緩やかなカーブなのか。板厚によって見た目も変わります。専門知識がない方にとって、図面だけで完成形を想像するのは困難です。
私たちは、オーダーメイド製品のマッチングを行う「カナエテ」というECプラットフォームの開発を進めています。この課題を解決するため、Three.jsを使った3Dシミュレーターという、技術的にチャレンジングな機能の実装に挑戦しました。
本記事では、Web技術で金属加工をリアルタイムに可視化し、即座に見積もりを提示するシステムの実装に挑んだ過程で、どのような技術的課題に直面し、どう解決したのかをお伝えします。
第1章:Three.jsとの出会い
1.1 技術選定の背景
プロジェクトの初期段階で、私たちはいくつかの実装方法を検討しました。
案1:2D図面の自動生成
- メリット:実装が比較的簡単、データ量が軽い
- デメリット:立体感がなく、完成形が想像しづらい
案2:静止画の組み合わせ
- メリット:きれいなビジュアル表現が可能
- デメリット:パターンごとに大量の画像が必要、角度が固定される
案3:3Dモデルのリアルタイム生成
- メリット:直感的、自由に回転・拡大可能、リアルタイム更新
- デメリット:技術的難易度が高い、パフォーマンスの懸念
最終的に、ユーザー体験を最優先し、案3の3Dモデル生成を選択しました。
1.2 Three.jsとは何か
Three.jsは、WebGLを簡単に扱えるようにしたJavaScriptライブラリです。WebGLは、ブラウザ上で3Dグラフィックスを描画するための技術ですが、生のWebGLを扱うのは非常に複雑です。
Three.jsを使うと、3D空間に物体を配置し、光を当て、カメラを動かすといった操作を、シンプルなコードで実現できます。
// Three.jsの基本構造(イメージ)
const scene = new Scene(); // 舞台を用意
const camera = new Camera(); // カメラを設置
const renderer = new Renderer(); // 描画エンジンを起動
const box = new Mesh(geometry, material); // 3D物体を作成
scene.add(box); // 舞台に配置
renderer.render(scene, camera); // 撮影・描画
まるで、映画のセットを組み立てるような感覚です。
1.3 なぜThree.jsを選んだのか
選定理由は大きく3つありました:
①ブラウザだけで動作する お客様に特別なソフトウェアをインストールしてもらう必要がありません。スマートフォンでもPCでも、ブラウザさえあれば3Dモデルを確認できます。
②リアルタイム更新が可能 寸法を変更すると、即座に3Dモデルが更新されます。「もう少し長く」「角度を変えて」といった調整を、その場で試せます。
③豊富なエコシステム Three.jsは世界中で使われており、ドキュメントやコミュニティが充実しています。問題にぶつかっても、解決策を見つけやすい環境がありました。
第2章:10種類の曲げパターンへの挑戦
2.1 曲げ加工の多様性
金属の曲げ加工には、実に様々なパターンがあります。
- 1角曲げ:シンプルなL字型(90度の直角、または緩やかな角度)
- 2角曲げ:Z字型、コの字型、階段状
- 3角曲げ:M字型、W字型
- 4角曲げ:フック状、複雑な凹凸形状
- 箱曲げ:四方を曲げて箱型に
私たちは、これら10種類の主要パターンすべてに対応することを目指しました。
2.2 最初の壁:板厚の計算
3Dモデルを作るのは比較的簡単でした。しかし、すぐに大きな問題に直面しました。
問題:金属板には厚みがある
金属を曲げると、外側と内側で長さが変わります。板厚が2mmの場合、曲げの中心線は表面から1mm内側にあります。この「板厚分のズレ」を正確に計算しないと、実際に製作したときに寸法が合わないのです。
これは、単なるビジュアルツールではなく、実際の製造に使えるシミュレーターを作る上で避けられない課題でした。
2.3 数学との格闘:板厚補正の実装
板厚を考慮した正確な3D配置を実現するため、三角関数を駆使した計算ロジックを実装しました。
// 板厚を考慮した位置調整の計算(簡略版)
const adjustThicknessWidth = (angle: number, thickness: number) => {
// 角度をラジアンに変換
const radian = (angle * Math.PI) / 180;
// 板厚分の補正値を計算
const adjustment = (thickness / 2) * Math.tan(radian / 2); return adjustment;};
この計算により、どんな角度で曲げても、指定した寸法通りに仕上がる3Dモデルを生成できるようになりました。
実装には、以下のようなヘルパー関数を用意しました:
calcHypotenuse(x, y)– 斜辺の計算calcHeight(hypotenuse, x)– 高さの計算calcAngle(hypotenuse, x)– 角度の計算convertRad2RotateAtan(a, b)– ラジアンから角度への変換
これらの関数を組み合わせることで、複雑な曲げパターンでも正確に表現できるようになりました。
第3章:実装の核心部分
3.1 Three.jsシーンの構築
3Dシミュレーターの基本構造は、以下のような要素で構成されています。
①シーン(Scene) すべての3Dオブジェクトが配置される「舞台」です。
②カメラ(PerspectiveCamera) ユーザーの視点を表現します。遠近感のある自然な見え方を実現するため、透視投影カメラを使用しました。
③レンダラー(WebGLRenderer) 3Dシーンを2D画面に描画するエンジンです。
④ライティング 金属の質感を表現するため、以下の照明を配置しました:
- PointLight(点光源)×2:メインの照明
- AmbientLight(環境光):全体的な明るさの底上げ
⑤メッシュ(Mesh) 実際の金属板を表現する3Dオブジェクトです。形状(Geometry)と材質(Material)を組み合わせて作ります。
3.2 金属質感の再現
金属らしい見た目を実現するため、MeshStandardMaterialを使用しました。これはPBR(Physically Based Rendering:物理ベースレンダリング)という、現実の物理法則に基づいた描画手法をサポートしています。
const material = new MeshStandardMaterial({ map: texture, // テクスチャ画像
metalness: 0.64, // 金属性(0=非金属、1=完全な金属)
roughness: 0.34, // 粗さ(0=鏡面、1=完全拡散)});
さらに、実際の金属板の写真をテクスチャとして貼り付けることで、素材ごとの質感の違いも表現しています。
- ガルバリウム:銀色の光沢
- GLカラー:塗装された表面
- ステンレス:鏡のような反射
- 亜鉛:マットな質感
3.3 リアルタイム更新の仕組み
ユーザーが寸法を入力すると、即座に3Dモデルが更新される。これを実現するため、Vue 3のリアクティブシステムと連携しました。
// Vue 3 Composition API との連携
const form = useForm({
a_side: 25, // 左側の長さ
b_side: 25, // 右側の長さ
c_side: 102, // 角度
thickness: 0.5, // 板厚
depth: 25, // 奥行き});
// 値が変更されたら3Dシーンを更新
watch(form, () => { updateScene(form);}, { deep: true });
入力フォームの値が変わるたびに、3Dシーンが再計算され、新しい形状が表示されます。まるで粘土をこねるように、リアルタイムで形を変えられます。
3.4 モジュール設計:10パターンを効率的に管理
10種類の曲げパターンそれぞれに、専用のComposable(再利用可能なロジック)を作成しました。
resources/ts/Composables/threejs/
├── useThreejsHelper.ts // 共通の計算関数
├── useThreejs1_1.ts // 1角曲げ(1-1)
├── useThreejs1_2.ts // 1角曲げ(1-2)
├── useThreejs2_1.ts // 2角曲げ(2-1)
├── useThreejs2_2.ts // 2角曲げ(2-2)
├── useThreejs2_3.ts // 2角曲げ(2-3)
├── useThreejs3_1.ts // 3角曲げ(3-1)
├── useThreejs4_1.ts // 4角曲げ(4-1)
├── useThreejs4_2.ts // 4角曲げ(4-2)
├── useThreejs4_3.ts // 4角曲げ(4-3)
└── useThreejsBox.ts // 箱曲げ
この設計により、以下のメリットが生まれました:
- 保守性の向上:パターンごとに独立しているため、1つの修正が他に影響しない
- テストの容易性:個別にユニットテストを書ける
- 拡張性:新しい曲げパターンを追加しやすい
第4章:開発中の試行錯誤
4.1 パフォーマンス問題との戦い
初期のプロトタイプでは、3Dモデルの更新が遅く、入力値を変えるたびにカクカクと動いていました。
原因の特定
- 毎回シーン全体を再構築していた
- メモリリークが発生していた
- テクスチャの読み込みが非効率だった
解決策
// 既存のメッシュを更新する方法に変更
const updateScene = (formData) => {
// 古いメッシュを削除
scene.remove(oldMesh);
oldMesh.geometry.dispose();
oldMesh.material.dispose();
// 新しいメッシュを作成
const newMesh = createMesh(formData);
scene.add(newMesh);
// 参照を更新
oldMesh = newMesh;};
さらに、テクスチャは初回のみ読み込み、キャッシュして再利用するようにしました。
4.2 TypeScriptによる型安全性の確保
JavaScriptで書いていた初期版では、バグが頻発しました。特に、寸法データの受け渡しで、数値と文字列が混在してしまう問題がありました。
// 型定義の例
interface ThreeDModelingFormData {
a_side: number; // 左側の長さ(mm)
b_side: number; // 右側の長さ(mm)
c_side: number; // 角度(度)
thickness: number; // 板厚(mm)
depth: number; // 奥行き(mm)
material: string; // 材質
width: number; // 幅(mm)}
TypeScriptに移行したことで、以下の効果がありました:
- コンパイル時にエラーを検出できる
- IDEの補完が効いて開発効率が上がる
- リファクタリングが安全に行える
4.3 カメラアニメーションの改善
3Dモデルが表示された瞬間、いきなり正面から見える状態では味気ない。そこで、初期表示時にカメラが滑らかに回転するアニメーションを追加しました。
// カメラアニメーションの実装
const animateCamera = () => { const targetPosition = new Vector3(50, 30, 50);
const animate = () => {
// 現在位置から目標位置へ徐々に移動
camera.position.lerp(targetPosition, 0.05);
camera.lookAt(0, 0, 0);
// 目標に近づくまで繰り返し
if (camera.position.distanceTo(targetPosition) > 0.1) {
requestAnimationFrame(animate);
}
renderer.render(scene, camera); };
animate();};
この小さな工夫が、ユーザー体験を大きく向上させました。
第5章:ユーザー体験の追求
5.1 OrbitControlsによる自由な操作
お客様が3Dモデルを自由に回転・拡大できるよう、OrbitControlsを導入しました。
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
const controls = new OrbitControls(camera, renderer.domElement);controls.enableDamping = true; // 慣性を有効化
controls.dampingFactor = 0.05; // 滑らかさの調整
これにより、以下の操作が可能になりました:
- 左ドラッグ:モデルを回転
- 右ドラッグ:視点を移動
- マウスホイール:ズームイン/アウト
- スマホ:ピンチ操作に対応
5.2 材質選択とテクスチャの切り替え
お客様が選択した材質に応じて、3Dモデルの見た目も変わります。
材質データは定数ファイルで一元管理し、価格情報とも連動させています。
5.3 自動見積もり機能
3Dシミュレーターと同時に、リアルタイムで見積もり金額も表示されます。
計算要素:
- 材料費:材質 × 板厚 × 幅 × 長さ
- 加工費:曲げ回数 × 加工単価
- 送料:重量と配送先から自動計算
- 数量割引:まとめ買いで単価が下がる
// 価格計算のロジック(簡略版)
const calculatePrice = (formData) => {
const materialCost = getMaterialPrice( formData.material, formData.thickness, formData.width ) * formData.totalLength;
const processingCost = getBendingPrice() * numberOfBends;
const shippingCost = calculateShipping(weight, destination);
return (materialCost + processingCost + shippingCost) * formData.quantity;};
お客様は、寸法を調整しながら予算内に収まるかを確認できます。
第6章:品質保証への取り組み
6.1 テスト戦略
複雑な計算ロジックを含むため、自動テストは必須でした。
単体テスト(Vitest)
describe('板厚補正計算', () => {
it('90度の曲げで正しく補正される', () => {
const result = adjustThicknessWidth(90, 2);
expect(result).toBeCloseTo(1.0, 1); });
it('45度の曲げで正しく補正される', () => {
const result = adjustThicknessWidth(45, 2);
expect(result).toBeCloseTo(0.414, 2); });});
E2Eテスト 実際のブラウザで3Dシーンが正しく描画されるかを確認しました。
6.2 静的解析とコード品質
PHPStan(Level 6) バックエンドのLaravelコードを厳密にチェック。
ESLint + Prettier フロントエンドのTypeScript/Vueコードを整形・検証。
vue-tsc Vueコンポーネントの型チェック。
これらのツールにより、リリース前にバグを検出できる体制を構築しました。
6.3 パフォーマンス計測
Lighthouseを使い、以下の指標を監視しました:
- FCP(First Contentful Paint):1.2秒以内
- LCP(Largest Contentful Paint):2.5秒以内
- TTI(Time to Interactive):3.5秒以内
3Dモデルの読み込みは重いため、初期表示の最適化に特に注力しました。
第7章:学びと成果
7.1 技術的な学び
Three.jsの深い理解
- WebGLの仕組みと制約
- 3D空間における座標系と変換行列
- メモリ管理とパフォーマンス最適化
- PBRマテリアルによる質感表現
Vue 3との統合パターン
- Composition APIによる状態管理
- リアクティブシステムとの連携
- ライフサイクルフックの活用
- TypeScriptとの型安全な統合
数学の実践的応用
- 三角関数による位置計算
- ラジアンと角度の変換
- ベクトル演算
- 幾何学的な問題解決
7.2 開発プロセスの改善
モジュール設計の重要性 10種類のパターンを個別のモジュールに分離したことで、並行開発が可能になり、チーム全体の生産性が向上しました。
型安全性の価値 TypeScriptの導入により、リファクタリングの心理的ハードルが下がり、コードの改善サイクルが加速しました。
ユーザーテストの効果 実際のお客様に使っていただき、フィードバックを得たことで、UI/UXの改善点が明確になりました。
7.3 実装を通じて得られた知見
Webでの3D表現の可能性 ブラウザだけでここまでリアルな3D表現ができることを実証できました。特別なアプリのインストール不要で、直感的な操作性を実現できる手応えを得ました。
複雑なビジネスロジックとの統合 見積もり計算、在庫管理、製造業者とのマッチングなど、複雑なバックエンドロジックと3Dフロントエンドを統合する設計パターンを確立できました。
製造業DXの技術的基盤 伝統的な製造業の業務フローを、最新のWeb技術でデジタル化する具体的な手法を実践的に学ぶことができました。これは他の製造分野にも応用可能な知見です。
第8章:挑戦から見えた可能性
8.1 技術的な拡張可能性
AR(拡張現実)への対応 Three.jsのシーンは、WebXR APIを使うことでAR表示も可能です。実際の設置場所でサイズ感を確認できる機能は、技術的には実現可能な範囲にあります。
物理シミュレーション 重量や強度の計算を追加し、「この寸法で耐えられるか」といった構造的な検証も、Three.jsの物理演算ライブラリを組み合わせれば実装できる見込みです。
AI支援 過去のオーダーデータから、「この用途ならこの寸法がおすすめ」といった提案機能も、機械学習モデルとの統合により実現可能性があります。
8.2 他の加工タイプへの展開
金属曲げ加工で培った技術は、他の加工タイプにも応用できます:
- 切削加工:3D形状を自由に設計
- 溶接:複数パーツの組み合わせシミュレーション
- 板金加工:展開図の自動生成
8.3 製造業DXへの応用可能性
この技術的挑戦を通じて、製造業のデジタル化に対するWeb技術の適用可能性を実証できました。
- 見積もり業務の自動化の技術的実現性
- 設計データの標準化とWeb表示の両立
- 製造プロセスの可視化手法の確立
- リアルタイムコミュニケーション基盤の構築
これらの知見は、金属加工に限らず、様々な製造分野に応用できる可能性があります。
おわりに:技術的挑戦を振り返って
Three.jsという技術に出会い、金属曲げ加工という伝統的な製造業の世界をWeb上で表現するという、技術的にチャレンジングな試みに取り組みました。
開発の過程では、数学的な計算の難しさ、パフォーマンスの壁、ユーザー体験の追求など、多くの課題に直面しました。それらを一つひとつ解決していく過程で、Web技術の可能性と限界の両方を深く理解することができました。
「見えないもの」を可視化する。これは、Web技術の本質的な価値の一つです。
この挑戦で培った技術は、まだ完全に公開できる段階ではありませんが、製造業DXという大きなテーマに対して、具体的なアプローチを示すことができたと考えています。今後も、最新技術を活用しながら、ものづくりの現場に寄り添ったシステム開発に挑戦し続けます。
技術スタック
- フロントエンド:Vue 3(Composition API)、TypeScript、Inertia.js
- 3Dレンダリング:Three.js 0.155
- スタイリング:Tailwind CSS
- バックエンド:Laravel 12.0、PHP 8.2
- テスト:Vitest、Pest、PHPStan(Level 6)
- インフラ:AWS(Laravel Vapor)、Docker