Newer
Older
SmTIAS-LightSim / src / sm_tias_lightsim / cli / render.py
@rintoHasegawa rintoHasegawa on 28 May 5 KB [add] Speed-1 LLVM JIT 自動検出と診断ログ強化 — runtime/llvm_init.py を新規実装.ensure_drjit_libllvm_path() で DRJIT_LIBLLVM_PATH 未設定時に Windows 標準 4 候補 (C:\Program Files\LLVM 等) + POSIX glob + LLVM_DIR + extra_candidates を順次探索して os.environ 設定(既設定なら上書きしない).log_variant_status() で llvm_/cuda_/scalar_ 経路を INFO 明示,要求 llvm_* で実 scalar_* なら WARNING 追加.render/runner.py で variant=llvm_* 時に自動検出を呼び ImportError 時は原因明示ログ→ re-raise,mitsuba を関数内遅延 import 化.visualize/baker.py の _set_variant_once に LLVM 自動探索分岐を追加し direct/path 両方で log_variant_status を呼ぶ.cli/render.py で import 順を制御し sensors.py/lights.py のモジュールレベル mitsuba import 前に検出を確実に通す.docs/02_ENV/ENV_02 に LLVM インストール手順・DRJIT_LIBLLVM_PATH 説明・動作確認コマンドを追記.新規テスト 51 件 (runtime/test_llvm_init.py 25, render/test_runner_jit_init.py 14, visualize/test_baker_jit_init.py 8, cli/test_render_jit_init.py 4) で仕様 A1-E1 完全カバー,DRJIT_LIBLLVM_PATH 設定下で 606 件全通過.速度は不変(目的は再発防止+診断容易化).数値同等性は MC 内部シードで SHA256 異なるが平均相対誤差 7.23e-05 で統計同等性パス.これで環境変数消失によるサイレントクラッシュが再発しない土台が整備された.TEST_04 Speed-1 セクションを実施結果で更新.CLAUDE.md 開発進捗に Speed-1 完了追記+実装済みモジュール一覧に runtime/llvm_init.py 追加
# 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