リンクをコピーしました

OpenUSD(Universal Scene Description) をローカルでビルドし、usdview を単体で動かしたうえで、PySide6 のウィジェットに埋め込んでカスタムする手順をまとめました。
uv で Python 環境を用意し、StageView を使った埋め込みと、Qt UI からの操作までを一通り扱います。
USD をこれから触る人・ビルドで詰まった人向けの記録です。

この記事でわかること

  • OpenUSD を uv でビルドする手順
  • usdview を単体で起動する方法(uv コマンド・Python スクリプト)
  • PySide6 で USD Viewer を埋め込んだウィジェットの作り方
  • Qt UI ファイルでカスタムビューワー(カメラリセット・レンダーモード・HUD・BBox など)を操作する流れ

前提条件

  • Python 3.11uv が利用できること
  • macOS でビルドする場合、Xcode やビルドツールの準備(公式 OpenUSD を参照)

開発環境

  • macOS Sequoia 15.5
  • VS Code
  • zsh 5.9 (arm64-apple-darwin24.0)

サンプルリポジトリ

カスタムUSD Viewerの動作サンプル(GIF)


この記事の流れ

  1. Universal Scene Description (USD) とは?
  2. ローカルで OpenUSD をビルドする(Python / uv)
  3. USD Viewer を単体で起動する
  4. USD Viewer を PySide6 で埋め込んでカスタムする

Universal Scene Description (USD) とは?

ざっくり言うと、複雑になった 3D アセットのやり取りを、USD が中間に入って円滑にするという解釈でよいと思います。公式「USD とは」の抄訳です。

映画やゲームなどの CG パイプラインでは、大量の 3D データ(シーン記述)を生成・保存・送信する必要があります。これらはアプリごとにフォーマットが異なり、他アプリで読めないことが一般的です。

Universal Scene Description (USD) は、この課題を解決するために公開されたソフトウェアで、複数要素で構成された 3D シーンを効率的かつスケーラブルに共有・拡張するための基盤です。モデルやアニメーションの交換だけでなく、仮想セット・シーン・ショットにまとめ、破壊的でない編集や、単一 API・シーングラフによるジオメトリ・シェーディング・ライティングなどのプレビュー・編集を可能にします。オープンソースで、Apache 2.0 に近い TOST ライセンスで提供されています。

(出典: What is USD - openusd.org


ローカルで OpenUSD をビルドする

1. リポジトリの取得

サブモジュールで追加する場合:

git submodule add https://github.com/PixarAnimationStudios/OpenUSD OpenUSD

クローンする場合:

git clone https://github.com/PixarAnimationStudios/OpenUSD.git

2. uv で仮想環境を作り、ビルドを実行

公式 OpenUSD のビルドドキュメント を確認しつつ、例としては次のようにします。

uv init -p 3.11
uv add PyOpenGL PySide6 numpy
uv run OpenUSD/build_scripts/build_usd.py BuildUSD

ビルドが成功すると、次のようなメッセージが出ます。

Success! To use USD, please ensure that you have:

  The following in your PYTHONPATH environment variable:
  /Users/Username/path/custom-embed-usdviewer/BuildUSD/lib/python

  The following in your PATH environment variable:
  /Users/Username/path/custom-embed-usdviewer/BuildUSD/bin

3. 環境変数の設定

PYTHONPATH:

cat > .env <<EOL
PYTHONPATH=./BuildUSD/lib/python
EOL

PATH:

export PATH=./BuildUSD/bin:$PATH

USD Viewer を単体で起動する

uv で usdview を直接起動

uv run --env-file=.env usdview OpenUSD/extras/usd/tutorials/convertingLayerFormats/Sphere.usda

初回は起動に時間がかかることがあります。

Python スクリプト経由で起動

open_usd_viewer.py を次の内容で作成します。

"""Simple USD viewer using pxr.Usdviewq."""
import sys
import pxr.Usdviewq as Usdviewq

if __name__ == "__main__":
    import signal
    signal.signal(signal.SIGINT, signal.SIG_DFL)
    try:
        usd_path = "assets/Sphere.usda"
        sys.argv.append(usd_path)
        Usdviewq.Launcher().Run()
    except Usdviewq.InvalidUsdviewOption as e:
        print("ERROR: " + str(e), file=sys.stderr)
        sys.exit(1)

起動:

uv run open_usd_viewer.py

USD Viewer を PySide6 で埋め込んでカスタムする

usdview を埋め込んだウィジェットを作成

ポイントは from pxr.Usdviewq.stageView import StageView で StageView を使うことと、ラッパー関数を用意して後から UI(ボタン・コンボボックスなど)から操作できるようにすることです。

embed_usd_widget.py のコード(クリックで展開)
"""Embed a USD viewer in a PySide6 widget."""
import os
import sys
from datetime import datetime
from PySide6 import QtCore, QtWidgets
from pxr import Usd, UsdUtils
from pxr.Usdviewq.stageView import StageView, RenderModes


class EmbedUSDWidget(QtWidgets.QWidget):
    """Widget to embed USD stage viewer."""

    def __init__(self, stage=None):
        super(EmbedUSDWidget, self).__init__()
        self.model = StageView.DefaultDataModel()
        self.stage = stage
        self.view = StageView(dataModel=self.model)

        layout = QtWidgets.QVBoxLayout(self)
        layout.addWidget(self.view)
        layout.setContentsMargins(0, 0, 0, 0)

        if self.stage:
            self.set_stage(self.stage)
        self.view.updateView(resetCam=True, forceComputeBBox=True)

    def image_save(self):
        image = self.view.grabFramebuffer()
        timestamp = datetime.now().strftime("%Y-%m-%d_%H%M%S")
        os.makedirs("screenshots", exist_ok=True)
        filename = f"screenshots/screenshot_{timestamp}.png"
        image.save(filename)

    def set_stage(self, stage):
        self.model.stage = stage

    def set_render_mode(self, mode):
        if mode in RenderModes:
            self.model.viewSettings.renderMode = mode

    def reset_camera(self):
        self.view.updateView(resetCam=True, forceComputeBBox=True)

    def show_hud(self, enable=True):
        """Show the HUD (Heads-Up Display)."""
        self.model.viewSettings.showHUD = enable

    def show_bboxes(self, enable=True):
        self.model.viewSettings.showBBoxes = enable

    def closeEvent(self, event):
        """Close event handler for cleanup."""
        self.view.closeRenderer()

(原文の EmmbedUSDWidgetEmbedUSDWidgetClose envetClose event、コメントの HUD 表記を統一しています。)

Qt UI でカスタムビューワーを作る

Qt Designer(Qt Creator)で .ui ファイルを作成し、そのレイアウトに上記の Embed USD ウィジェットを追加します。

Qt CreatorでUIファイルを編集したイメージUI のボタン・コンボボックス・チェックボックスを、ウィジェットの reset_cameraset_render_modeshow_hudshow_bboxesimage_save などに接続します。

mkdir -p UI && curl -L -o UI/usdViewerController.ui https://raw.githubusercontent.com/testkun08080/custom-embed-usdviewer/main/UI/usdViewerController.ui

app.py では、QUiLoader で UI を読み込み、メインレイアウトに EmbedUSDWidget を追加します。
その後、各 UI 要素のシグナルをウィジェットのスロットに接続します(レンダーモード、HUD、BBox、AOV 選択など)。
詳細は custom-embed-usdviewer の app.py を参照してください。

起動:

uv run app.py

カスタムUSD Viewerの最終画面


まとめ

  • OpenUSD をローカルでビルドし、usdview を単体で動かしたうえで、PySide6StageView を使い、USD Viewer をウィジェットとして埋め込む流れを紹介しました。
  • Qt の UI ファイルでボタン・コンボボックスを用意し、カメラリセット・レンダーモード・HUD・BBox 表示・スクリーンショットなどをラッパー経由で操作できるようにすると、社内のプレビューツールやビューワーとして拡張しやすいと思います。

参考文献