【Streamlit】3Dモデルビューワー:Blenderの3Dモデルを表示する

Streamlit
この記事は約12分で読めます。

こんにちは、JS2IIUです。
今回は、StreamlitとPlotlyを使って、Blenderで作成した3Dモデルをブラウザ上で自由に視点を変えながら表示できるWebアプリケーションを作成する方法をご紹介します。今回もよろしくお願いします。

1. はじめに

3Dモデルをウェブ上で共有・表示したいニーズは多くありますが、専用のビューワーソフトをインストールする必要があったり、操作が複雑だったりすることが多いものです。今回紹介するアプリケーションでは、Streamlitの簡単さとPlotlyの強力な3D表示機能を組み合わせることで、ブラウザだけで3Dモデルを表示・操作できるようにします。

2. 必要な環境とライブラリ

まずは必要なライブラリをインストールしましょう:

Python
pip install streamlit numpy plotly trimesh

各ライブラリの役割:

  • Streamlit: Webアプリケーションのフレームワーク
  • NumPy: 数値計算ライブラリ
  • Plotly: インタラクティブな3D表示
  • Trimesh: 3Dメッシュデータの読み込みと処理

3. Blenderからのデータエクスポート

Blenderから3Dモデルをエクスポートする手順は以下の通りです:

  1. Blenderで対象のモデルを選択
  2. メニューから「ファイル」→「エクスポート」→「Wavefront (.obj)」を選択
  3. エクスポート設定で以下を確認:
  • Selection Only: チェックを入れる(選択したモデルのみエクスポート)
  • Transform: Apply Modifiers にチェックを入れる
  • Geometry: Triangulate Faces にチェックを入れる
  1. エクスポート先を選択して「OBJをエクスポート」をクリック

注意点:

  • テクスチャや材質情報は現在のビューワーでは表示されません
  • モデルが複雑すぎると表示が重くなる可能性があります
  • できるだけ三角形ポリゴンでエクスポートすることをお勧めします

4. アプリケーションの実装

コードの全体を示します。

Python
import streamlit as st
import numpy as np
import plotly.graph_objects as go
import trimesh
import os

def load_obj_file(uploaded_file):
    """
    アップロードされたOBJファイルをロードする関数
    """
    # 一時ファイルとして保存
    with open("temp.obj", "wb") as f:
        f.write(uploaded_file.getbuffer())

    # trimeshでメッシュを読み込む
    mesh = trimesh.load("temp.obj")

    # 一時ファイルを削除
    os.remove("temp.obj")

    return mesh

def main():
    # ページ設定
    st.set_page_config(
        page_title="3D Model Viewer",
        layout="wide"
    )

    # タイトルと説明
    st.title("3Dモデルビューワー")
    st.write("Blenderからエクスポートした3DモデルをWeb上で表示できます。")

    # ファイルアップローダーを追加
    uploaded_file = st.file_uploader(
        "OBJファイルをアップロード", 
        type=['obj'],
        help="Blenderからエクスポートした.objファイルをアップロードしてください。"
    )

    if uploaded_file is not None:
        try:
            # 2カラムレイアウトを作成
            col1, col2 = st.columns([3, 1])

            with col2:
                st.header("表示設定")
                # プロットのスタイル設定
                opacity = st.slider(
                    "不透明度", 
                    min_value=0.0, 
                    max_value=1.0, 
                    value=1.0, 
                    step=0.1
                )
                wireframe = st.checkbox("ワイヤーフレーム表示", False)
                color_scheme = st.selectbox(
                    "カラースキーム",
                    ["Viridis", "Plasma", "Inferno", "Magma"]
                )

                st.markdown("""
                ### 操作方法
                - マウスドラッグ: 回転
                - マウスホイール: ズーム
                - Shift+ドラッグ: 平行移動
                - ダブルクリック: ビューをリセット
                """)

            with col1:
                # メッシュの読み込み
                mesh = load_obj_file(uploaded_file)

                # メッシュデータの取得
                vertices = mesh.vertices
                faces = mesh.faces

                # 3Dプロットの作成
                fig = go.Figure()

                # メッシュの表示
                fig.add_trace(go.Mesh3d(
                    x=vertices[:, 0],
                    y=vertices[:, 1],
                    z=vertices[:, 2],
                    i=faces[:, 0],
                    j=faces[:, 1],
                    k=faces[:, 2],
                    opacity=opacity,
                    colorscale=color_scheme.lower(),
                    intensity=vertices[:, 2],  # Z座標を基にした色付け
                    showscale=True
                ))

                # ワイヤーフレーム表示
                if wireframe:
                    for face in faces:
                        vertices_face = vertices[face]
                        vertices_face = np.vstack([vertices_face, vertices_face[0]])
                        fig.add_trace(go.Scatter3d(
                            x=vertices_face[:, 0],
                            y=vertices_face[:, 1],
                            z=vertices_face[:, 2],
                            mode='lines',
                            line=dict(color='black', width=1),
                            showlegend=False
                        ))

                # レイアウトの設定
                fig.update_layout(
                    scene=dict(
                        xaxis_title="X軸",
                        yaxis_title="Y軸",
                        zaxis_title="Z軸",
                        camera=dict(
                            up=dict(x=0, y=0, z=1),
                            center=dict(x=0, y=0, z=0),
                            eye=dict(x=1.5, y=1.5, z=1.5)
                        ),
                        aspectmode='data'  # 実際のスケールを維持
                    ),
                    margin=dict(l=0, r=0, t=0, b=0),
                    height=700  # プロットの高さを指定
                )

                # Streamlitでプロットを表示
                st.plotly_chart(fig, use_container_width=True)

                # モデル情報の表示
                st.write("### モデル情報")
                st.write(f"頂点数: {len(vertices):,}")
                st.write(f"面の数: {len(faces):,}")

        except Exception as e:
            st.error(f"エラーが発生しました: {str(e)}")
            st.error("ファイルの形式が正しくないか、データが破損している可能性があります。")
    else:
        st.info("OBJファイルをアップロードしてください。")

        # 使い方の説明
        st.markdown("""
        ### 使い方
        1. Blenderでモデルを選択し、File > Export > Wavefront (.obj)を選択
        2. エクスポート設定で以下を確認:
           - Selection Only: チェックを入れる
           - Transform: Apply Modifiers にチェック
           - Geometry: Triangulate Faces にチェック
        3. エクスポートしたOBJファイルをこのページにアップロード
        """)

if __name__ == "__main__":
    main()

アプリケーションのコアとなる機能を見ていきましょう。

主要な機能:

  1. OBJファイルのアップロードとロード
  2. 3Dメッシュの表示
  3. 表示設定の調整(不透明度、ワイヤーフレーム表示など)
  4. インタラクティブな視点操作

5. コードの詳細解説

OBJファイルの読み込み

Python
def load_obj_file(uploaded_file):
    with open("temp.obj", "wb") as f:
        f.write(uploaded_file.getbuffer())

    mesh = trimesh.load("temp.obj")
    os.remove("temp.obj")

    return mesh

このコードでは:

  • アップロードされたファイルを一時的に保存
  • trimeshを使用してメッシュデータを読み込み
  • 一時ファイルを削除して後処理を整理

3Dビューワーのコア部分

Python
fig.add_trace(go.Mesh3d(
    x=vertices[:, 0],
    y=vertices[:, 1],
    z=vertices[:, 2],
    i=faces[:, 0],
    j=faces[:, 1],
    k=faces[:, 2],
    opacity=opacity,
    colorscale=color_scheme.lower(),
    intensity=vertices[:, 2],
    showscale=True
))

この部分では:

  • vertices(頂点データ)とfaces(面データ)を使用してメッシュを構築
  • opacity(不透明度)とcolorscale(カラースキーム)でビジュアルをカスタマイズ
  • intensityパラメータでZ座標に基づいた色付けを実現

ワイヤーフレーム表示の実装

Python
if wireframe:
    for face in faces:
        vertices_face = vertices[face]
        vertices_face = np.vstack([vertices_face, vertices_face[0]])
        fig.add_trace(go.Scatter3d(
            x=vertices_face[:, 0],
            y=vertices_face[:, 1],
            z=vertices_face[:, 2],
            mode='lines',
            line=dict(color='black', width=1),
            showlegend=False
        ))

このコードでは:

  • 各面の輪郭を線で描画
  • 面の頂点を結んでワイヤーフレームを形成
  • 最初の頂点に戻って閉じた形状を作成

6. カスタマイズのヒント

アプリケーションをさらに発展させるためのいくつかのアイデア:

  1. 表示機能の拡張
  • テクスチャのサポート追加
  • 複数のライティングオプション
  • 断面表示機能
  1. ユーザビリティの向上
  • モデルの自動スケーリング
  • 視点のプリセット
  • スクリーンショット機能
  1. パフォーマンスの最適化
  • メッシュの簡略化オプション
  • 大規模モデルの段階的読み込み
  • WebGLベースのレンダリングへの移行

7. まとめ

このアプリケーションを使用することで、Blenderで作成した3Dモデルを簡単にWeb上で共有・表示することができます。Streamlitの直感的なUIとPlotlyの強力な3D表示機能を組み合わせることで、専門的な知識がなくても3Dビューワーを作成・カスタマイズすることが可能です。

コードはGitHubなどで公開して、コミュニティと共有することをお勧めします。多くの人がこのベースから独自の機能を追加し、より便利なツールに発展させていくことを期待しています。

参考リンク

以上で解説を終わります。質問やフィードバックがありましたら、コメント欄でお気軽にご連絡ください!

コメント

タイトルとURLをコピーしました