diff --git a/scripts/build_led_placement_comparison.py b/scripts/build_led_placement_comparison.py index 6400ca2..ee3b65c 100644 --- a/scripts/build_led_placement_comparison.py +++ b/scripts/build_led_placement_comparison.py @@ -37,6 +37,7 @@ sys.path.insert(0, str(Path(__file__).resolve().parent)) sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) +import sweep_led_placement as sweep # noqa: E402 from analyze_fourfold_component import aggregate, load_roi, ring_harmonics # noqa: E402 from build_radial_comparison import radial_stats, ring_min_max_ratio # noqa: E402 from build_radius_sweep_visual import decompose_cov, normalized_map # noqa: E402 @@ -50,16 +51,19 @@ COUNT_THETA, CUBE_N8_AZIMUTH, DIRECTION_THETAS, - OUTPUT_BASE, + MATERIALS, REPEAT_PAIR_INDEX, RING_SPECS, SHAPES, SIZE_PAIRS, THETA_GRID, collect_rows, + set_material, write_summary, ) +OUTPUT_BASE = sweep.OUTPUT_BASE + plt.rcParams["font.family"] = ["Yu Gothic", "MS Gothic", "DejaVu Sans"] HARMONICS = (1, 2, 3, 4, 8) # 8 は真下向き多灯(N=8)の痕跡用 @@ -590,7 +594,12 @@ """照明配置スイープの解析図と集約表を生成する.""" parser = argparse.ArgumentParser(description="照明配置スイープの解析図を生成") parser.add_argument("--no-figures", action="store_true", help="表と CSV だけ出す") + parser.add_argument("--material", choices=tuple(MATERIALS), default="legacy", + help="解析する材質系列(スイープの --material と同じ)") args = parser.parse_args() + global OUTPUT_BASE # noqa: PLW0603 + set_material(args.material) + OUTPUT_BASE = sweep.OUTPUT_BASE write_summary() rows = collect_rows() diff --git a/scripts/sweep_led_placement.py b/scripts/sweep_led_placement.py index 85fc621..1a62e27 100644 --- a/scripts/sweep_led_placement.py +++ b/scripts/sweep_led_placement.py @@ -141,6 +141,56 @@ GROUPS = ("position", "direction", "count", "repeat", "azimuth", "ring") +# G. 校正材質での引き直し(追加実験): TEST_14 で確定した純 diffuse ρ=0.81 に内壁を置き換え, +# 主要な結論(位置依存の幅・多灯・真下向き)が材質に依らないことを確かめる. +# 出力・一時 YAML は別ディレクトリ(サフィックス)に分ける +MATERIALS = { + "legacy": None, # テンプレートのまま(blendbsdf ρ=0.85・ω=0.05・α=0.1) + "calibrated": {"type": "diffuse", "reflectance": [0.81, 0.81, 0.81]}, +} +MATERIAL_SUFFIX = {"legacy": "", "calibrated": "_rho081"} +_material = "legacy" +# 「主要条件だけ」のサブセット: 位置スイープをペア 1・3 で,多灯(N=2/4)と真下向きをペア 3 で +KEY_SUBSET_PAIRS = (0, 2) +KEY_SUBSET_PAIR_MULTI = 2 + + +def set_material(material: str) -> None: + """内壁材質の種類を切り替え,出力・一時ディレクトリを材質ごとに分ける.""" + global _material, TMP_DIR, MESH_DIR, OUTPUT_BASE # noqa: PLW0603 + if material not in MATERIALS: + raise ValueError(f"未知の材質指定: {material}({', '.join(MATERIALS)})") + _material = material + suffix = MATERIAL_SUFFIX[material] + TMP_DIR = ROOT / "configs" / f"_tmp_led_placement{suffix}" + MESH_DIR = TMP_DIR / "meshes" + OUTPUT_BASE = ROOT / "output" / f"led_placement_sweep{suffix}" + + +def current_material() -> str: + """現在の材質指定を返す.""" + return _material + + +def key_subset(conds: list[Condition]) -> list[Condition]: + """材質の引き直しに使う主要条件だけを残す.""" + keep = [] + for c in conds: + pair = next((i for i, p in enumerate(SIZE_PAIRS) + if p[0 if c.shape == "sphere" else 1] == c.size), -1) + if c.group == "position" and pair in KEY_SUBSET_PAIRS: + keep.append(c) + elif (c.group == "count" and pair == KEY_SUBSET_PAIR_MULTI + and c.theta_deg == COUNT_THETA and c.n_led in (2, 4)): + keep.append(c) + elif (c.group == "direction" and pair == KEY_SUBSET_PAIR_MULTI + and c.theta_deg == COUNT_THETA and c.aim == "down"): + keep.append(c) + elif (c.group == "ring" and pair == KEY_SUBSET_PAIR_MULTI + and c.theta_deg == COUNT_THETA and c.aim == "down" and c.n_led == 4): + keep.append(c) + return keep + # 評価板の中心(キャビティ側の面)= 板向き LED の照準点 BOARD_CENTER = np.array([0.0, 0.0, BOARD_SURFACE_Z]) # LED 円板を立方体の稜に近づけすぎない余裕(mm) @@ -150,6 +200,7 @@ SUMMARY_COLUMNS = [ "name", "group", "shape", "size_mm", "theta_deg", "aim", "n_led", "rep", "azimuth_deg", + "material", "cavity_area_mm2", "led_x_mm", "led_y_mm", "led_z_mm", "led_standoff_mm", "tilt_deg", "baffled", "baffle_radius_mm", "baffle_embed_mm", "board_front_frac", "occlusion_blocked_frac", "occlusion_total", @@ -446,8 +497,10 @@ バッフルだけを変え,多灯では radiance を 1/N にして総光束を保存する. """ config = copy.deepcopy(yaml.safe_load(TEMPLATE_YAML.read_text(encoding="utf-8"))) - config["name"] = f"led_placement_{cond.name}" + config["name"] = f"led_placement_{cond.name}{MATERIAL_SUFFIX[_material]}" config["render"]["seed"] = cond.rep - 1 + if MATERIALS[_material] is not None: + config["materials"]["printer_white"] = dict(MATERIALS[_material]) config["description"] = ( f"照明配置スイープ [{cond.shape} / {cond.size:g}mm]: θ={cond.theta_deg:g}° の壁面に " f"{cond.n_led} 灯(向き={cond.aim})." @@ -647,6 +700,7 @@ } row.update(record) row["render_variant"] = variant + row["material"] = _material return row @@ -762,14 +816,21 @@ help="実行する条件グループ(省略時は全部)") parser.add_argument("--limit", type=int, default=None, help="先頭 N 条件だけ実行する(動作確認用)") + parser.add_argument("--material", choices=tuple(MATERIALS), default="legacy", + help="内壁材質(calibrated=TEST_14 の純 diffuse ρ=0.81.出力は別 dir)") + parser.add_argument("--key-subset", action="store_true", + help="材質の引き直し用の主要条件だけに絞る") parser.add_argument("--workers", type=int, default=1, help="同時に実行する条件数(既定 1=逐次.GPU 1 枚なら 2〜3 が目安)") parser.add_argument("--list", action="store_true", help="条件一覧を表示して終了") args = parser.parse_args() + set_material(args.material) conds = [c for c in conditions() if (not args.shapes or c.shape in args.shapes) and (not args.groups or c.group in args.groups)] + if args.key_subset: + conds = key_subset(conds) if args.limit: conds = conds[: args.limit] if args.list: