
要件定義
RhinoComputeを用いたPython、JavaScript、React、Three.jsの学習環境の開発に向けて、具体的な要件を整理します。
これにより、開発の全体像を把握し、必要な作業を明確にします。
プロジェクトの目的
RhinoComputeとReactを用いてPython、JavaScript、React、Three.jsの学習環境を構築する
システム概要

Python
RhinoCommonを使用して3Dジオメトリを生成。
Flaskを用いて3DジオメトリデータをJSON形式で提供するAPIを構築。
RhinoCompute
ローカル環境でRhinoのコア機能を外部プログラムから呼び出すAPIサーバー。
PythonスクリプトからRhinoComputeを利用して3Dジオメトリを作成。
React
Three.jsを活用し、Webアプリケーション上で3Dジオメトリを表示。
Flask APIから取得した3Dデータを動的にレンダリング。
機能要件

技術要件
(1) 使用ツール・フレームワーク
- RhinoCompute(ローカルでのAPIサーバー)
- Python 3.11.9
- Flask(軽量Webフレームワーク)
- React(フロントエンドフレームワーク)
- Three.js(3Dレンダリングライブラリ)
(2) 環境要件
- OS: Windows 11 以上(Rhino8 対応)
- ブラウザ: 最新のGoogle Chrome
- Node.js: v18.20.5
- Rhino 8: インストール済み
(3) 必要なライブラリ
- Python:
- compute-rhino3d
- rhino3dm
- flask-cors
- React:
- react 18.3.1
- react-dom 18.3.1
- web-vitals 4.2.4
- three
- @react-three/fiber
- @react-three/drei
- three-mesh-bvh@0.8.0
開発工程
環境構築(1日)
- RhinoComputeのセットアップ
- 必要なライブラリのインストール
Pythonスクリプトの作成(2日)
- RhinoCommonでボックス生成
- Flask APIの作成とテスト
Reactフロントエンドの構築(3日)
- Three.jsを用いた3Dモデル描画
- Flask APIとの通信機能実装
成果物の仕様
Step1:3Dボックス生成・表示
機能
- 3Dモデル表示機能:Pythonスクリプトで生成されたボックスをReactアプリ上に描画。
基本操作
- ユーザーがマウスでモデルを回転、拡大縮小できる。
拡張性
- ボックス以外の形状にも簡単に対応可能。
Step2:3Dボール生成・表示・画面遷移
機能
- 3Dモデル表示機能:Pythonスクリプトで生成されたボックスをReactアプリ上に描画。
- 画面遷移機能:リンクごとに画面遷移し閲覧可能
基本操作
- ユーザーがマウスでモデルを回転、拡大縮小できる。
拡張性
- ボックス以外の形状にも簡単に対応可能。
環境構築
ルートディレクトリをプロジェクト名として作成します。
mkdir 004-ComputeStudySite
cd 004-ComputeStudySite
RhinoComputeの構築・動作確認
backendディレクトリを作成します。
mkdir backend
compute.rhino3dをbackendディレクトリにコピーして、rhino.computeに移動します。
cd backend/compute.rhino3d/src/rhino.compute/
環境変数を設定して、.NET8.0を使用させます。
set DOTNET_ROLL_FORWARD=Major
コマンドプロンプトからRhinoComputeを起動します。
dotnet run
エンドポイントをテスト
Rhino.Compute コンソール アプリケーションを実行している状態で、これらのエンドポイントのいずれかを参照して、すべてが動作していることを確認します。
- http://localhost:6500/version
- http://localhost:6500/healthcheck
- http://localhost:6500/activechildren
- http://localhost:6500/sdk
Pythonライブラリのインストール
必要なPythonライブラリをインストールします。以下をbackendディレクトリで実行します。
pip install compute-rhino3d
pip install rhino3dm
pip install flask-cors
Pythonスクリプトの作成
RhinoComputeを利用して3Dボックスを生成するPythonスクリプトを作成します。
type nul > rhino_box.py
rhino_box.py
import compute_rhino3d.Util
import compute_rhino3d.Brep
import json
# RhinoComputeサーバーの設定
compute_rhino3d.Util.url = "http://localhost:6500/" # RhinoComputeのURL
compute_rhino3d.Util.apiKey = "" # 必要であればAPIキーを設定
def create_box():
# コーナー座標を定義 (リスト形式)
corner1 = [0, 0, 0] # Point3dの代わりにリスト形式
corner2 = [10, 10, 10]
# RhinoCompute APIを使ってボックスを作成
box = {
"corner1": corner1,
"corner2": corner2
}
return box
def serialize_brep(box):
# BrepをJSON形式に変換(手動でデータをフォーマット)
brep_data = {
"type": "box",
"data": box
}
return brep_data
if __name__ == "__main__":
# ボックスを作成
box = create_box()
# JSON形式に変換
json_data = serialize_brep(box)
# 結果をファイルに保存
with open("box_data.json", "w") as f:
json.dump(json_data, f)
print("ボックスデータをJSON形式で出力しました: box_data.json")
Flask APIの作成とテスト
Flaskを使ってデータを提供するAPIを作成します。
type null > server.py
server.py
from flask import Flask, jsonify
import json
app = Flask(__name__)
@app.route('/box', methods=['GET'])
def get_box():
# JSONファイルからデータを読み込み
with open("box_data.json", "r") as f:
data = json.load(f)
return jsonify(data)
if __name__ == "__main__":
app.run(debug=True)
実行
python server.py
これで、http://localhost:5000/boxにアクセスすることで、JSON形式のボックスデータが取得可能になります。
このデータはボックスのコーナー座標情報が含まれており、Reactフロントエンドでこの情報を元に3Dボックスを表示することができます。
次に、Reactでこのデータを利用してボックスを表示するために、Three.jsとReactの連携を行うコードを作成します。
Reactフロントエンドの構築
Reactアプリケーションの作成
mkdir frontend
furontendディレクトリで React アプリケーションを作成します。
cd frontend
npx create-react-app .
これにより、React アプリケーションが frontend ディレクトリ内に作成されます。
必要なモジュールを手動でインストールします。
npm install web-vitals
この設定により、ファイル変更のポーリングが有効になり、Windows 環境でのホットリロードが機能します。
reactとreact-domのダウングレード
reactとreact-domをバージョン18にダウングレードしてライブラリをインストールできるようにします。
npm install react@18 react-dom@18
再度Reactを起動します。
npm start
Flask APIとの通信機能実装
CORS(app)を追加することで、Flaskアプリケーションに対して、外部(異なるポートやドメイン)からのリクエストを許可しています。CORS(app)のデフォルト設定では、すべてのオリジン(ドメイン)からのリクエストを許可します。
- バックエンド(Flask)からデータを取得する処理:
fetchBoxData.jsでは、fetchを使ってhttp://localhost:5000/boxからデータを取得しています。これにより、Flask APIのエンドポイントと通信し、3Dボックスのデータ(JSON)を取得しています。
- データの表示:
App.jsで取得したデータ(boxData)を画面に表示するロジックが含まれています。データが正常に取得できれば、ユーザーにその内容が視覚的に提示されます。
この部分が フロントエンドとバックエンドの通信 を行う部分で、まさに Flask APIとの通信機能実装 となります。
server.py
from flask import Flask, jsonify
from flask_cors import CORS # 追加: flask_corsモジュールのインポート
app = Flask(__name__)
# CORSを有効にする
CORS(app) # この行を追加すると、すべてのオリジンからのリクエストを許可する
@app.route('/box', methods=['GET'])
def get_box():
# ここで、3Dボックスのデータを返す処理を実装します
return jsonify({
"type": "box",
"data": {
"corner1": [0, 0, 0],
"corner2": [10, 10, 10]
}
})
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5000)
src/utils/ フォルダに fetchBoxData.js を作成します。
cd src
mkdir utils
cd utils
type null > fetchBoxData.js
src/utils/fetchBoxData.js
このファイルにはバックエンドの API (http://localhost:5000/box) からデータを取得する関数を記述します。
// src/utils/fetchBoxData.js
export const fetchBoxData = async () => {
try {
const response = await fetch('http://localhost:5000/box');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.json();
return data;
} catch (error) {
console.error('Error fetching box data:', error);
return null;
}
};
App.js でデータを取得
App.js でこの fetchBoxData 関数を使ってデータを取得し、画面に表示します。
src/App.js
import React, { useEffect, useState } from 'react';
import { fetchBoxData } from './utils/fetchBoxData'; // 作成したAPI呼び出し関数
function App() {
const [boxData, setBoxData] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const getBoxData = async () => {
const data = await fetchBoxData();
if (data) {
setBoxData(data);
}
setLoading(false);
};
getBoxData();
}, []);
if (loading) {
return <div>Loading...</div>;
}
return (
<div>
<h1>Box Data</h1>
<pre>{JSON.stringify(boxData, null, 2)}</pre>
</div>
);
}
export default App;
動作確認
frontendフォルダで以下のコマンドを実行して、Reactアプリを再起動します。
npm start
- ブラウザで
http://localhost:3000/にアクセスして、バックエンドから取得したデータが表示されるか確認します。
しかしこの段階では、http://localhost:3000/をリロードしてもRhinoCompute側では処理が発生していません。また、http://localhost:5000/boxをリロードしたときもRhinoCompute側では処理が発生していません。
現状は以下のようになっています。
http://localhost:3000/をリロードしたとき、http://localhost:5000/box側は処理が発生している
http://localhost:6500/boxをリロードしたとき、RhinoComputeは処理が発生している。
つまり、ReactとFlask間は連携できているが、FlaskとRhinoCompute間は連携できていないということです。
データの流れを整理
データの概念図
以下でデータの連携の大まかな流れを確認します。
各種リンクをリロードしたときにRhinoComputeを起動しているコマンドプロンプトで以下のように挙動すればOKです。
http://localhost:3000/ をリロードした時、
[12:39:41 INF] HTTP GET /box responded 200 in 1.1171 ms
[12:39:41 INF] HTTP GET /box responded 200 in 0.7969 ms
http://localhost:5000/box をリロードした時、
[12:40:15 INF] HTTP GET /box responded 200 in 1.3534 ms
http://localhost:6500/ をリロードした時、
[12:40:27 INF] HTTP GET /box responded 200 in 0.7817 ms
http://localhost:3000/ 以下が表示されます。
rhino_box.pyとserver.pyの修正
rhino_box.py
import rhino3dm
import requests
import json
# RhinoCompute の URL を設定
compute_url = "http://localhost:6500/box" # RhinoCompute サーバーが動作しているURL
def create_box():
"""
RhinoCompute を使用してボックスを生成し、JSON データとして返します。
"""
try:
# RhinoComputeへの接続確認
response = requests.get(compute_url)
if response.status_code != 200:
raise Exception("Failed to connect to RhinoCompute")
# ボックスの角 (corner1, corner2) と高さ
corner1 = rhino3dm.Point3d(0, 0, 0) # 原点
corner2 = rhino3dm.Point3d(100, 100, 100) # サイズを指定
# ボックスのBoundingBoxを生成
bbox = rhino3dm.BoundingBox(corner1, corner2)
# ボックスを生成
box = rhino3dm.Box(bbox)
# BoxからBrepを作成
brep = rhino3dm.Brep.CreateFromBox(box)
# Brepの頂点座標をJSON形式で整形して返す
vertices = []
for vertex in brep.Vertices:
# Point3d を使って座標を取得
point = vertex.Location # vertexのLocation属性がPoint3d型
vertices.append({"x": point.X, "y": point.Y, "z": point.Z})
return {"vertices": vertices}
except Exception as e:
# エラー処理
print(f"エラーが発生しました: {e}")
return {"error": str(e)}
server.py
from flask import Flask, jsonify
import rhino_box # rhino_box.py をインポート
from flask_cors import CORS # CORSのインポート
app = Flask(__name__)
CORS(app) # CORSを有効にする
@app.route('/box', methods=['GET'])
def get_box():
try:
# rhino_box.py で定義した create_box() を呼び出してボックスを生成
box_data = rhino_box.create_box() # create_box() は rhino_box.py に定義
if "error" in box_data:
return jsonify(box_data), 500
return jsonify(box_data), 200
except Exception as e:
return jsonify({"error": f"Server error: {e}"}), 500
if __name__ == '__main__':
app.run(debug=True)
以下のように、http://localhost:3000/をリロードすると、RhinoComputeとFlaskサーバーがGETしていることがわかります。
これで、FlaskとRhinoComputeを連携でき、それにより、React、Flask、RhinoComputeが連携できるようになりました。

3Dボックスを表示
RhinoComputeで生成した3Dジオメトリデータを元にThree.jsでボックスを作成しWeb上に表示します。
ライブラリをインストール
frontendディレクトリに移動して、Three.jsをインストールします。以下のコマンドでインストールできます。
npm install three
npm install @react-three/fiber
npm install @react-three/drei
npm install three-mesh-bvh@0.8.0
npm install @mediapipe/tasks-vision@latest
まとめてインストールしたい場合は以下のコマンドでインストール
npm install three @react-three/fiber @react-three/drei three-mesh-bvh@0.8.0 @mediapipe/tasks-vision@latest
App.js
// src/App.js
import React, { useEffect, useState } from 'react';
import Box from './components/Box'; // Boxコンポーネントをインポート
function App() {
const [boxData, setBoxData] = useState(null);
useEffect(() => {
// Flask APIからボックスデータを取得
fetch('http://localhost:5000/box')
.then(response => response.json())
.then(data => {
setBoxData(data); // ボックスデータを状態に保存
})
.catch(error => console.error('Error fetching box data:', error));
}, []);
if (!boxData) {
return <div>Loading Box Data...</div>; // データが読み込まれるまで表示
}
return (
<div>
<h1>3D Box</h1>
<Box data={boxData} /> {/* Boxコンポーネントにボックスデータを渡す */}
</div>
);
}
export default App;
components/Box.js
cd frontend/src
mkdir components
type null > Box.js
Reactコンポーネントを作成して、RhinoComputeから取得した頂点データをThree.jsを使って描画します。
import React, { useEffect, useRef } from 'react';
import * as THREE from 'three';
function Box({ data }) {
const mountRef = useRef(null);
useEffect(() => {
// データがない場合は描画しない
if (!data || !data.vertices) return;
// シーン、カメラ、レンダラーをセットアップ
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer();
renderer.setSize(window.innerWidth, window.innerHeight);
const mountNode = mountRef.current;
mountNode.appendChild(renderer.domElement);
// 頂点データからボックスの寸法を計算
const vertices = data.vertices;
const width = Math.abs(vertices[0].x - vertices[1].x);
const height = Math.abs(vertices[0].y - vertices[3].y);
const depth = Math.abs(vertices[0].z - vertices[4].z);
// ボックスのジオメトリを作成
const geometry = new THREE.BoxGeometry(width, height, depth);
// ボックスのマテリアルを作成(色は赤)
const material = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: false });
// メッシュとしてボックスをシーンに追加
const box = new THREE.Mesh(geometry, material);
scene.add(box);
// カメラの初期位置を設定
camera.position.z = 500;
// アニメーションループ
const animate = function () {
requestAnimationFrame(animate);
box.rotation.x += 0.01; // 回転
box.rotation.y += 0.01;
renderer.render(scene, camera);
};
animate();
// ウィンドウリサイズ時にカメラアスペクト比を更新
const handleResize = () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
};
window.addEventListener('resize', handleResize);
// クリーンアップ
return () => {
window.removeEventListener('resize', handleResize);
mountNode.removeChild(renderer.domElement);
};
}, [data]);
return <div ref={mountRef} style={{ width: '100%', height: '100vh' }} />;
}
export default Box;
rhino_box.py
import rhino3dm
import requests
import json
# RhinoCompute の URL を設定
compute_url = "http://localhost:6500/box" # RhinoCompute サーバーが動作しているURL
def create_box():
"""
RhinoCompute を使用してボックスを生成し、JSON データとして返します。
"""
try:
# RhinoComputeへの接続確認
response = requests.get(compute_url)
if response.status_code != 200:
raise Exception("Failed to connect to RhinoCompute")
# ボックスの角 (corner1, corner2) と高さ
corner1 = rhino3dm.Point3d(0, 0, 0) # 原点
corner2 = rhino3dm.Point3d(100, 100, 100) # サイズを指定
# ボックスのBoundingBoxを生成
bbox = rhino3dm.BoundingBox(corner1, corner2)
# ボックスを生成
box = rhino3dm.Box(bbox)
# BoxからBrepを作成
brep = rhino3dm.Brep.CreateFromBox(box)
# Brepの頂点座標をJSON形式で整形して返す
vertices = []
for vertex in brep.Vertices:
# Point3d を使って座標を取得
point = vertex.Location # vertexのLocation属性がPoint3d型
vertices.append({"x": point.X, "y": point.Y, "z": point.Z})
return {"vertices": vertices}
except Exception as e:
# エラー処理
print(f"エラーが発生しました: {e}")
return {"error": str(e)}
動作確認

サイドバーとページ遷移の実装
サイドバーを追加して、ページ遷移の機能を実装するには以下のステップを行います。
React Router のインストール
ページ遷移には react-router-dom を利用します。以下のコマンドでインストールします:
npm install react-router-dom
サイドバーコンポーネントの作成
以下のようなサイドバーを作成します。サイドバーにリンクを配置して、クリックした際に異なるページに遷移できるようにします。
cd src/components
type null > Sidebar.js
// src/components/Sidebar.js
import React from "react";
import { Link } from "react-router-dom";
const Sidebar = () => {
return (
<div className="sidebar" style={{ width: "200px", background: "#f0f0f0", padding: "10px" }}>
<h2>Menu</h2>
<ul style={{ listStyleType: "none", padding: 0 }}>
<li>
<Link to="/">main</Link>
</li>
<li>
<Link to="/python">Python</Link>
</li>
<li>
<Link to="/python/box">Python Box</Link>
</li>
<li>
<Link to="/javascript">JavaScript</Link>
</li>
<li>
<Link to="/react">React</Link>
</li>
<li>
<Link to="/threejs">Three.js</Link>
</li>
</ul>
</div>
);
};
export default Sidebar;
ページ構成とルート設定
App.js でルーティングを設定します。
// src/App.js
import React from "react";
import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
import Sidebar from "./components/Sidebar";
import MainPage from "./pages/MainPage";
import PythonPage from "./pages/PythonPage";
import PythonBoxPage from './pages/PythonBoxPage';
import JavaScriptPage from "./pages/JavaScriptPage";
import ReactPage from "./pages/ReactPage";
import ThreeJsPage from "./pages/ThreeJsPage";
const App = () => {
return (
<Router>
<div style={{ display: "flex" }}>
<Sidebar />
<div style={{ marginLeft: "200px", padding: "20px", width: "100%" }}>
<Routes>
<Route path="/" element={<MainPage />} />
<Route path="/python" element={<PythonPage />} />
<Route path="/python/box" element={<PythonBoxPage />} />
<Route path="/javascript" element={<JavaScriptPage />} />
<Route path="/react" element={<ReactPage />} />
<Route path="/threejs" element={<ThreeJsPage />} />
</Routes>
</div>
</div>
</Router>
);
};
export default App;
各ページコンポーネントの作成
例えば、BoxPage を以下のように作成します:
cd src
mkdir pages
type null > Main.js
type null > PythonPage.js
type null > PythonBoxPage.js
type null > JavaScriptPage.js
type null > ReactPage.js
type null > ThreeJsPage.js
PythonBoxPage.js
先ほどまでApp.jsでボックスを表示させていましたが、PythonBoxPage.jsにその役割を移行するイメージです。
代わりにApp.jsでは各ページへの遷移をする役割を持ちます。
import React, { useEffect, useState } from 'react';
import Box from '../components/Box';
const PythonBoxPage = () => {
const [boxData, setBoxData] = useState(null);
useEffect(() => {
// Flask APIからボックスデータを取得
fetch('http://localhost:5000/box')
.then(response => response.json())
.then(data => {
setBoxData(data);
})
.catch(error => console.error('Error fetching box data:', error));
}, []);
if (!boxData) {
return <div>Loading Box Data...</div>;
}
return (
<div>
<h1>Python Box</h1>
<Box data={boxData} />
</div>
);
};
export default PythonBoxPage;
他ページ
他のページ (PythonPage.js, JavaScriptPage.js など) も同様に作成しますが、シンプルなテキストだけを表示する形で十分です。
// src/pages/PythonPage.js
import React from "react";
const PythonPage = () => {
return <h1>Python Page</h1>;
};
export default PythonPage;
スタイル調整 (オプション)
サイドバーやページ全体のデザインをより良くするために CSS を適用します。
cd src
mkdir styles
type null > App.css
App.css
/* src/styles/App.css */
.sidebar {
height: 100vh;
position: fixed;
top: 0;
left: 0;
background-color: #282c34;
color: white;
padding: 1rem;
}
.sidebar a {
color: white;
text-decoration: none;
}
.sidebar a:hover {
text-decoration: underline;
}
これで、サイドバーからリンクをクリックして各ページに遷移し、ボックスを表示するページや他のページが閲覧できるようになります!
