"""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())