# 球体筐体実験（TEST_11）用の球殻メッシュ生成スクリプト．
#
# 現行筐体（前側/後側）を理想化した「完全閉球」の白色キャビティに置き換え，
# 幾何レバーの理想極限（漏れゼロの積分球）で照明均一性の上限を測る実験に使う．
# 評価板（照明ムラ補正ボード）を内包できる中心・半径を引数で指定する．
"""球殻（integrating sphere 状の閉じた白キャビティ）STL を生成する．

内半径 r の icosphere（法線を内向きに反転）と外半径 r+t の icosphere を
連結した watertight な厚み付き殻を作り，バイナリ STL として出力する．
単位は mm（プロジェクトのシーン YAML 規約に合わせる）．

使用例:
    uv run python scripts/generate_sphere_housing.py \
        --radius 100 --thickness 3 --center 0 0 -40 \
        --output configs/meshes/球殻_r100.stl
"""

from __future__ import annotations

import argparse
from pathlib import Path

import numpy as np
import trimesh


def build_sphere_shell(
    radius: float,
    thickness: float,
    center: tuple[float, float, float],
    subdivisions: int,
) -> trimesh.Trimesh:
    """厚み付き球殻メッシュを生成する．

    内面（半径 radius）は法線をキャビティ側（中心向き）に反転させ，
    外面（半径 radius + thickness）は外向きのまま連結する．
    2 つの閉曲面から成るため全体として watertight な実体殻になる．

    Args:
        radius: 内半径（mm）．キャビティの半径．
        thickness: 殻厚（mm）．
        center: 球中心座標 (x, y, z)（mm）．
        subdivisions: icosphere の分割回数．6 で辺長 ≈ radius * 0.03 程度．

    Returns:
        球殻メッシュ（mm 単位）．

    Raises:
        ValueError: radius または thickness が正でない場合．
    """
    if radius <= 0:
        raise ValueError(f"内半径は正の値を指定してください: {radius}")
    if thickness <= 0:
        raise ValueError(f"殻厚は正の値を指定してください: {thickness}")

    inner = trimesh.creation.icosphere(subdivisions=subdivisions, radius=radius)
    # 内面の法線は実体（殻）から見て外側＝キャビティ中心向きが正しいため反転する
    inner.invert()
    outer = trimesh.creation.icosphere(subdivisions=subdivisions, radius=radius + thickness)

    shell = trimesh.util.concatenate([outer, inner])
    shell.apply_translation(np.asarray(center, dtype=np.float64))
    return shell


def _parse_args(argv: list[str] | None = None) -> argparse.Namespace:
    """コマンドライン引数をパースして返す．"""
    parser = argparse.ArgumentParser(
        prog="generate_sphere_housing",
        description="球体筐体実験用の閉じた球殻 STL（mm 単位）を生成する．",
    )
    parser.add_argument("--radius", type=float, default=100.0, help="内半径（mm）．既定 100")
    parser.add_argument("--thickness", type=float, default=3.0, help="殻厚（mm）．既定 3")
    parser.add_argument(
        "--center",
        type=float,
        nargs=3,
        default=(0.0, 0.0, -40.0),
        metavar=("X", "Y", "Z"),
        help="球中心座標（mm）．既定 0 0 -40（評価板を内包する位置）",
    )
    parser.add_argument(
        "--subdivisions",
        type=int,
        default=6,
        help="icosphere 分割回数．既定 6（面数 81920 x 2，辺長 ≈ 3mm）",
    )
    parser.add_argument(
        "--output",
        type=Path,
        default=Path("configs/meshes/球殻_r100.stl"),
        help="出力 STL パス．既定 configs/meshes/球殻_r100.stl",
    )
    return parser.parse_args(argv)


def main(argv: list[str] | None = None) -> int:
    """球殻を生成して STL 出力し，検証情報を表示する．"""
    args = _parse_args(argv)

    shell = build_sphere_shell(
        radius=args.radius,
        thickness=args.thickness,
        center=tuple(args.center),
        subdivisions=args.subdivisions,
    )

    args.output.parent.mkdir(parents=True, exist_ok=True)
    shell.export(args.output)

    # 期待体積 = (4/3)π((r+t)^3 - r^3)．メッシュ体積との一致で内面反転の正しさを確認できる
    expected_volume = (
        4.0 / 3.0 * np.pi * ((args.radius + args.thickness) ** 3 - args.radius**3)
    )
    print(f"出力: {args.output}")
    print(f"faces: {len(shell.faces)}, watertight: {shell.is_watertight}")
    print(f"bounds (mm): {shell.bounds.round(2).tolist()}")
    print(f"体積 (mm^3): {shell.volume:.0f}（理論値 {expected_volume:.0f}）")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())
