# pyright: reportCallIssue=none
# Mitsuba 3 は variant ベースの動的属性解決を行うため Pylance/Pyright が
# mi.* を object 型と推論し reportCallIssue を誤検知する(GUIDE_06 の方針).
# 下記の Mitsuba 依存 import(load_scene_yaml / render_scene / save_png / save_luminance)を
# main() 内で遅延 import しているのは,--help だけ使う場面(Mitsuba 不在の軽量環境)でも
# 起動できるようにするためである.
"""sm-render コマンドラインエントリポイント.
YAML シーン定義を読み込み,Mitsuba 3 でレンダリングを実行して
PNG と luminance.npy を出力ディレクトリに保存する.
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
# Step 6 の evaluate CLI も同じファイルを読むため,名前を定数として一元管理する
_PNG_FILENAME = "render.png"
_NPY_FILENAME = "luminance.npy"
def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
"""コマンドライン引数をパースして返す.
Args:
argv: 引数リスト.None のとき sys.argv[1:] を使用.
Returns:
パース済み Namespace.
"""
parser = argparse.ArgumentParser(
prog="sm-render",
description=(
"YAML シーン定義から Mitsuba 3 でレンダリングし PNG と luminance.npy を出力する."
),
)
parser.add_argument(
"--scene",
"-s",
required=True,
type=Path,
metavar="YAML_PATH",
help="シーン定義 YAML ファイルのパス.",
)
parser.add_argument(
"--output",
"-o",
required=True,
type=Path,
metavar="OUTPUT_DIR",
help="出力先ディレクトリ(存在しない場合は親ごと作成する).",
)
return parser.parse_args(argv)
def main(argv: list[str] | None = None) -> int:
"""sm-render コマンドのメインエントリポイント.
処理フロー:
1. コマンドライン引数をパースする.
2. YAML シーン定義を load_scene_yaml で読み込み,SceneConfig を得る.
3. 出力ディレクトリを作成する(親ごと,既存なら何もしない).
4. render_scene で Mitsuba レンダリングを実行し,HDR 配列を得る.
5. save_png で render.png を保存する.
6. save_luminance で luminance.npy を保存する.
Args:
argv: 引数リスト.None のとき sys.argv[1:] を使用.
Returns:
終了コード(成功時 0,失敗時 1).
"""
# argparse 自体のエラー(必須引数欠落等)は ArgumentParser が直接 sys.exit するため
# ここでは ValueError / FileNotFoundError 等の実行時エラーのみ捕捉する
args = _parse_args(argv)
scene_path: Path = args.scene.resolve()
output_dir: Path = args.output.resolve()
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
try:
output_dir.mkdir(parents=True, exist_ok=True)
except OSError as exc:
print(f"エラー: 出力ディレクトリを作成できません — {exc}", file=sys.stderr)
return 1
# project_root を scene YAML の親ディレクトリとすることで,
# YAML 内の STL 相対パスが YAML ファイルを基点に解決される
project_root = scene_path.parent
try:
# llvm_* variant の場合,mitsuba の import チェーン(sensors.py / lights.py)が
# 走る前に DRJIT_LIBLLVM_PATH を設定しておく必要がある.
# runner.py の import(build_scene → sensors.py / lights.py → mitsuba)より先に呼ぶ.
if config.render.variant.startswith("llvm_"):
from sm_tias_lightsim.runtime.llvm_init import ensure_drjit_libllvm_path
ensure_drjit_libllvm_path()
from sm_tias_lightsim.render.runner import render_scene
hdr_image = render_scene(config, project_root=project_root)
except Exception as exc: # noqa: BLE001
print(f"エラー: レンダリングに失敗しました — {exc}", file=sys.stderr)
return 1
png_path = output_dir / _PNG_FILENAME
npy_path = output_dir / _NPY_FILENAME
try:
from sm_tias_lightsim.render.output import save_luminance, save_png
save_png(hdr_image, png_path)
save_luminance(hdr_image, npy_path)
except Exception as exc: # noqa: BLE001
print(f"エラー: 出力ファイルの保存に失敗しました — {exc}", file=sys.stderr)
return 1
print(f"レンダリング完了: {png_path}")
print(f"輝度マップ保存: {npy_path}")
return 0