# pyright: reportCallIssue=none
# Mitsuba 3 は variant ベースの動的属性解決を行うため Pylance/Pyright が
# mi.* を object 型と推論し reportCallIssue を誤検知する(GUIDE_06 の方針).
"""sm-visualize コマンドラインエントリポイント.
YAML シーン定義を読み込み,3D 照度ベイクと PyVista インタラクティブビューアを提供する.
処理フロー:
1. YAML シーン定義を読み込み SceneConfig を得る.
2. --no-bake 指定がない場合,bake_irradiance で直接照度を計算する.
3. show_scene で PyVista 表示(または screenshot 保存)を行う.
4. --output 指定があれば per-face irradiance を npz 形式で保存する.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"""コマンドライン引数をパースして返す.
Args:
argv: 引数リスト.None のとき sys.argv[1:] を使用.
Returns:
パース済み Namespace.
"""
parser = argparse.ArgumentParser(
prog="sm-visualize",
description=(
"YAML シーン定義から 3D 照度ベイクを実行し,PyVista でインタラクティブ表示する."
),
)
parser.add_argument(
"--scene",
"-s",
required=True,
type=Path,
metavar="YAML_PATH",
help="シーン定義 YAML ファイルのパス.",
)
parser.add_argument(
"--no-bake",
action="store_true",
default=False,
help="照度ベイクをスキップしてシーンレイアウトのみ表示する.",
)
parser.add_argument(
"--screenshot",
type=Path,
default=None,
metavar="PNG_PATH",
help="スクリーンショット保存先 PNG パス.指定時はウィンドウを開かず off_screen で保存する",
)
parser.add_argument(
"--output",
"-o",
type=Path,
default=None,
metavar="OUTPUT_DIR",
help="ベイク結果(per-face irradiance)を npz 形式で保存するディレクトリ(任意).",
)
parser.add_argument(
"--off-screen",
action="store_true",
default=False,
help="ウィンドウを開かない(screenshot 専用モード).",
)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
"""sm-visualize コマンドのメインエントリポイント.
処理フロー:
1. コマンドライン引数をパースする.
2. YAML シーン定義を load_scene_yaml で読み込み,SceneConfig を得る.
3. --no-bake がなければ bake_irradiance で照度を計算する.
4. --output が指定されていれば per-face irradiance を npz で保存する.
5. show_scene で PyVista 表示(または screenshot 保存)を行う.
Args:
argv: 引数リスト.None のとき sys.argv[1:] を使用.
Returns:
終了コード(成功時 0,失敗時 1).
"""
args = _parse_args(argv)
scene_path: Path = args.scene.resolve()
screenshot: Path | None = args.screenshot.resolve() if args.screenshot else None
output_dir: Path | None = args.output.resolve() if args.output else None
no_bake: bool = args.no_bake
off_screen: bool = args.off_screen
# YAML 読み込み
try:
from sm_tias_lightsim.scene.schema import load_scene_yaml
config = load_scene_yaml(scene_path)
except FileNotFoundError as exc:
print(f"エラー: シーン YAML ファイルが見つかりません — {exc}", file=sys.stderr)
return 1
except Exception as exc: # noqa: BLE001
print(f"エラー: シーン YAML の読み込みに失敗しました — {exc}", file=sys.stderr)
return 1
# project_root: YAML ファイルの親ディレクトリを基点とする
project_root = scene_path.parent
# 照度ベイク
irradiance: dict | None = None
if not no_bake:
print("照度ベイクを開始します(直接照度のみ)...")
try:
from sm_tias_lightsim.visualize.baker import bake_irradiance
irradiance = bake_irradiance(config, project_root)
print("照度ベイク完了.")
except Exception as exc: # noqa: BLE001
print(f"エラー: 照度ベイクに失敗しました — {exc}", file=sys.stderr)
return 1
else:
print("--no-bake: 照度ベイクをスキップします.シーンレイアウトのみ表示します.")
# ベイク結果を npz で保存
if irradiance is not None and output_dir is not None:
try:
output_dir.mkdir(parents=True, exist_ok=True)
import numpy as np
npz_path = output_dir / f"{config.name}_irradiance.npz"
np.savez(npz_path, **irradiance)
print(f"照度データ保存: {npz_path}")
except Exception as exc: # noqa: BLE001
print(f"エラー: 照度データの保存に失敗しました — {exc}", file=sys.stderr)
return 1
# PyVista 表示
try:
from sm_tias_lightsim.visualize.viewer import show_scene
show_scene(
config,
project_root,
irradiance=irradiance,
off_screen=off_screen,
screenshot=screenshot,
)
except Exception as exc: # noqa: BLE001
print(f"エラー: PyVista 表示に失敗しました — {exc}", file=sys.stderr)
return 1
if screenshot is not None:
print(f"スクリーンショット保存: {screenshot}")
return 0