Newer
Older
SmTIAS-LightSim / scripts / build_slide_test16_geometry.py
"""TEST_16(立方体キャビティ実験)の発表スライド用ジオメトリ図を生成する.

球版(build_slide_test15_geometry.py)と同じ体裁で,立方体の構成断面とサイズ入れ子図を
`output/_slide_panels/` に出力する.annotate=False 版は文字なし・要素太め・透過背景で,
注釈をスライド側で載せる前提.

1. test16_setup_cutaway(.png / _plain.png) : 実験構成の軸断面(L=200).
   評価板が上面開口にそのまま嵌まる・平らな天井・底面中心の LED・バッフル
2. test16_size_nest(.png / _plain.png)     : サイズスイープの入れ子断面(板は不変)

実行方法:
    uv run python scripts/build_slide_test16_geometry.py
"""

from __future__ import annotations

import sys
from pathlib import Path

import matplotlib
import numpy as np

matplotlib.use("Agg")
import matplotlib.pyplot as plt  # noqa: E402

sys.path.insert(0, str(Path(__file__).resolve().parent))

from sweep_cube_size import baffle_radius, led_z  # noqa: E402
from sweep_sphere_radius import (  # noqa: E402
    BAFFLE_GAP,
    BOARD_HALF,
    BOARD_SURFACE_Z,
    RIM_INSET,
    RIM_Z,
)

ROOT = Path(__file__).resolve().parents[1]
OUT_DIR = ROOT / "output" / "_slide_panels"

plt.rcParams["font.family"] = ["Yu Gothic", "MS Gothic", "DejaVu Sans"]
plt.rcParams["font.size"] = 13

BOARD_COLOR = "crimson"
WALL_COLOR = "#455a74"
LED_COLOR = "#e6a817"

CUBE_SIZES = (120.0, 160.0, 200.0, 280.0, 360.0)


def cube_outline(size: float) -> tuple[np.ndarray, np.ndarray]:
    """片側(+X)のリム→天井→側壁→底面中心の断面ポリラインを返す(mm)."""
    rim_x = BOARD_HALF[0] - RIM_INSET
    half = size / 2.0
    x = np.array([rim_x, half, half, 0.0])
    z = np.array([RIM_Z, RIM_Z, RIM_Z - size, RIM_Z - size])
    return x, z


def draw_cube(ax, size: float, color, lw: float) -> None:
    """立方体断面(左右対称)と LED・バッフルを描く."""
    x, z = cube_outline(size)
    ax.plot(np.concatenate([x, -x[::-1]]), np.concatenate([z, z[::-1]]),
            color=color, lw=lw, solid_capstyle="round")


def make_setup_cutaway(size: float = 200.0, annotate: bool = True) -> None:
    """実験構成の軸断面を描く(annotate=False で文字なし・スライド用)."""
    fig, ax = plt.subplots(figsize=(8.2, 8.2))
    draw_cube(ax, size, WALL_COLOR, 4.0 if annotate else 5.0)

    ax.plot([-BOARD_HALF[0], BOARD_HALF[0]], [BOARD_SURFACE_Z] * 2,
            color=BOARD_COLOR, lw=8.0 if annotate else 10.0, solid_capstyle="butt")

    zl = led_z(size)
    rb = baffle_radius(size)
    ax.plot(0.0, zl, marker="^", color=LED_COLOR, ms=16 if annotate else 22,
            mec="black", mew=0.8, zorder=5)
    ax.plot([-rb, rb], [zl + BAFFLE_GAP] * 2, color="black", lw=5.0 if annotate else 7.0)
    for sign in (-1.0, 1.0):
        ax.plot([0.0, sign * BOARD_HALF[0]], [zl, BOARD_SURFACE_Z],
                color=LED_COLOR, lw=1.4 if annotate else 5.5, ls=":", alpha=0.9)

    if annotate:
        common = {"fontsize": 14, "ha": "left", "va": "center",
                  "arrowprops": {"arrowstyle": "-", "color": "0.3", "lw": 1.2}}
        ax.annotate("評価板 99×102 mm\n(上面の開口にそのまま嵌まる)",
                    xy=(BOARD_HALF[0] * 0.55, BOARD_SURFACE_Z), xytext=(115, 25), **common)
        ax.annotate("平らな天井\n(板の縁から水平に広がる)",
                    xy=((BOARD_HALF[0] + size / 2.0) / 2.0, RIM_Z), xytext=(125, -35),
                    **common)
        ax.annotate("立方体キャビティ 一辺 L\n(内壁は拡散反射・白)",
                    xy=(size / 2.0, -size * 0.55), xytext=(115, -120), **common)
        ax.annotate("LED(底面中心に 1 灯・壁面に密着・\n評価板の方を向ける)",
                    xy=(3, zl), xytext=(60, -230), **common)
        ax.annotate("バッフル\n(評価板への直接光を完全に遮断)",
                    xy=(rb * 0.8, zl + BAFFLE_GAP), xytext=(60, -180), **common)
        ax.plot([-100, 0], [-245, -245], color="black", lw=2.0)
        ax.plot([-100, -100], [-241, -249], color="black", lw=2.0)
        ax.plot([0, 0], [-241, -249], color="black", lw=2.0)
        ax.text(-50, -255, "100 mm", ha="center", va="top", fontsize=12)
        ax.set_xlim(-130, 250)
        ax.set_ylim(-265, 40)

    else:
        ax.set_xlim(-125, 125)
        ax.set_ylim(-215, 15)
    ax.set_aspect("equal")
    ax.axis("off")
    fig.tight_layout()
    out = OUT_DIR / ("test16_setup_cutaway.png" if annotate else "test16_setup_cutaway_plain.png")
    fig.savefig(out, dpi=150, bbox_inches="tight", transparent=not annotate)
    plt.close(fig)
    print(f"出力: {out}")


def make_size_nest(sizes: tuple[float, ...] = CUBE_SIZES, annotate: bool = True) -> None:
    """サイズスイープの入れ子断面を描く(評価板は不変のまま立方体だけ拡大)."""
    fig, ax = plt.subplots(figsize=(8.8, 9.6))
    colors = plt.cm.viridis(np.linspace(0.05, 0.85, len(sizes)))
    for size, color in zip(sizes, colors, strict=True):
        draw_cube(ax, size, color, 2.6 if annotate else 3.4)
        zl = led_z(size)
        rb = baffle_radius(size)
        ax.plot(0.0, zl, marker="^", color=color, ms=9 if annotate else 13,
                mec="black", mew=0.6, zorder=5)
        ax.plot([-rb, rb], [zl + BAFFLE_GAP] * 2, color="black", lw=2.2 if annotate else 3.0)
        # ラベルは自分の底面の右上(自キューブの内側)に置く.入れ子の外側ほど底が
        # 深いので互いに重ならず,他サイズの壁線とも交差しない
        ax.text(size / 2.0 - 8, RIM_Z - size + 12, f"L={size:g}", color=color,
                fontsize=13 if annotate else 30, va="bottom", ha="right", fontweight="bold")

    ax.plot([-BOARD_HALF[0], BOARD_HALF[0]], [BOARD_SURFACE_Z] * 2,
            color=BOARD_COLOR, lw=8.0 if annotate else 10.0, solid_capstyle="butt", zorder=6)

    if annotate:
        ax.text(0, 16, "評価板・カメラ・LED 条件は不変", color=BOARD_COLOR,
                fontsize=15, ha="center", fontweight="bold")
        ax.text(0, -395, "立方体の一辺だけを 120〜360 mm で変える(LED・バッフルは追従)",
                fontsize=13, ha="center", color="0.25")
    ax.set_aspect("equal")
    ax.axis("off")
    fig.tight_layout()
    out = OUT_DIR / ("test16_size_nest.png" if annotate else "test16_size_nest_plain.png")
    fig.savefig(out, dpi=150, bbox_inches="tight", transparent=not annotate)
    plt.close(fig)
    print(f"出力: {out}")


def main() -> int:
    """スライド用ジオメトリ図を生成する."""
    OUT_DIR.mkdir(parents=True, exist_ok=True)
    make_setup_cutaway()
    make_setup_cutaway(annotate=False)
    make_size_nest()
    make_size_nest(annotate=False)
    return 0


if __name__ == "__main__":
    sys.exit(main())