"""ファミレスの店の形 第2版:席を「列」で置く。 決まり(2026-10-21 エイイチと決めたこと) - 基本は4人掛けの背中合わせのソファの列(島)。壁ぞいはソファの片側の列 - 狭い所は2人席で埋める - 家族・団体用に6人のブース(L字ソファ・6人ボックス)を入れる - 店の色は2つ 効率重視型:仕切り壁なし・通路は狭め(1.2m)・2人席多め ゆったり型:仕切り壁多め・通路は広め(1.5m)・家族用ブース多め - 入口の位置はパターンごとに変える(角・横の壁・正面の中央) 測ること:待機場所の前から各卓まで、ロボット(仮に幅70cm)が届くか・何m走るか 使い方: python3 famires_gen2.py """ import json from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.patches import Rectangle, Circle, Polygon from famires_gen import analyze as _analyze, overlap plt.rcParams["font.family"] = "Noto Sans CJK JP" HERE = Path(__file__).parent OUT = HERE / "variants2" INK, MUTED, BACK, SEAT, SOFA = "#1f2328", "#8a9099", "#eceef1", "#ffffff", "#dee2e6" ROBOT, DRINK, DISH, NG = "#e8590c", "#1c7ed6", "#2f9e44", "#e03131" UNIT = 1.85 # ボックス1つの長さ(ベンチ0.55+テーブル0.75+ベンチ0.55) CROSS = {4: 1.2, 6: 1.8} # ベンチの長さ(2人掛け・3人掛け) BACKGAP = 0.1 # 背中合わせのソファの背の厚み PART = 0.15 # 仕切り壁の厚み # ---------------------------------------------------------------- 席の部品 def booth(x, y, axis, seats): """ボックス席1つ。axis='v' はベンチが上下(列が縦に並ぶ)、'h' はベンチが左右""" c = CROSS[seats] w, h = (c, UNIT) if axis == "v" else (UNIT, c) return dict(type="booth", axis=axis, x=round(x, 2), y=round(y, 2), w=w, h=h, seats=seats) def strip(x, y, axis, n, seats=4, walls=None): """ボックスの列。walls を渡すと、ボックスの間に仕切り壁を入れる(ゆったり型)""" out = [] for i in range(n): if axis == "v": out.append(booth(x, y + i * UNIT, "v", seats)) else: out.append(booth(x + i * UNIT, y, "h", seats)) if walls is not None: c = CROSS[seats] for i in range(1, n): if axis == "v": walls.append(dict(label="仕切り", joint=True, rect=[x, y + i * UNIT - PART / 2, x + c, y + i * UNIT + PART / 2])) else: walls.append(dict(label="仕切り", joint=True, rect=[x + i * UNIT - PART / 2, y, x + i * UNIT + PART / 2, y + c])) return out def island(x, y, axis, n, seats=4, walls=None): """背中合わせのソファの列(島)。walls を渡すと背中に仕切り壁を立て、ボックスの間も仕切る""" c = CROSS[seats] back = PART if walls is not None else BACKGAP if axis == "v": a = strip(x, y, "v", n, seats, walls) b = strip(x + c + back, y, "v", n, seats, walls) if walls is not None: walls.append(dict(label="仕切り", joint=True, rect=[x + c, y - 0.2, x + c + back, y + n * UNIT + 0.2])) else: a = strip(x, y, "h", n, seats, walls) b = strip(x, y + c + back, "h", n, seats, walls) if walls is not None: walls.append(dict(label="仕切り", joint=True, rect=[x - 0.2, y + c, x + n * UNIT + 0.2, y + c + back])) return a + b def island_width(seats=4, walls=False): return 2 * CROSS[seats] + (PART if walls else BACKGAP) def two_seat_row(x, y, side, n, pitch=1.1): """壁ぞいの2人席の列(壁側はベンチ、通路側は椅子)。side は壁の位置""" out = [] for i in range(n): if side in ("bottom", "top"): out.append(dict(type="t2", side=side, x=round(x + i * pitch, 2), y=y, w=0.8, h=1.4, seats=2)) else: out.append(dict(type="t2", side=side, x=x, y=round(y + i * pitch, 2), w=1.4, h=0.8, seats=2)) return out def lsofa(x, y, w, h, flip=False, mirror=False): return dict(type="booth6L", x=x, y=y, w=w, h=h, seats=6, flip=flip, mirror=mirror) def register(counter, staff): """レジカウンター(長さ約2.5〜3m)と、その内側の店員が立つ所""" return [dict(label="レジ", rect=counter, kind="guest"), dict(label="レジ内", rect=staff, kind="back")] def toilet_block(rect, door, W, D, thick=0.3, door_w=0.9, approach=1.2): """客用トイレ。客席に面する側は厚い壁(約30cm)、入口(door の側)の前は1.2mあけて席を置かない""" x0, y0, x1, y1 = rect walls = [] sides = {"left": x0 > 0, "right": x1 < W, "bottom": y0 > 0, "top": y1 < D} # 外壁でない側 for side, interior in sides.items(): if not interior: continue if side in ("left", "right"): xa = x0 if side == "left" else x1 - thick segs = [(y0, y1)] if side == door: m = (y0 + y1) / 2 segs = [(y0, m - door_w / 2), (m + door_w / 2, y1)] walls += [dict(label="トイレの壁", rect=[xa, a, xa + thick, b]) for a, b in segs] else: ya = y0 if side == "bottom" else y1 - thick segs = [(x0, x1)] if side == door: m = (x0 + x1) / 2 segs = [(x0, m - door_w / 2), (m + door_w / 2, x1)] walls += [dict(label="トイレの壁", rect=[a, ya, b, ya + thick]) for a, b in segs] m = ((x0 + x1) / 2, (y0 + y1) / 2) zone = {"left": [x0 - approach, m[1] - door_w / 2, x0, m[1] + door_w / 2], "right": [x1, m[1] - door_w / 2, x1 + approach, m[1] + door_w / 2], "bottom": [m[0] - door_w / 2, y0 - approach, m[0] + door_w / 2, y0], "top": [m[0] - door_w / 2, y1, m[0] + door_w / 2, y1 + approach]}[door] return dict(label="トイレ", rect=list(rect), kind="guest"), walls, zone # ---------------------------------------------------------------- 裏側(厨房など) def boh_right(W, D, px, fy, ty, pw=2.0, sw=2.4, wash_gap=1.0): """厨房が右。パントリーは縦長で、受け渡し口は下の端(ベースの店と同じ形)""" rooms = [ dict(label="パントリー", rect=[px + 0.3, fy, px + 0.3 + pw, ty], kind="back"), dict(label="キッチン", rect=[px + 0.3 + pw, fy, W - sw, ty], kind="back"), dict(label="冷蔵・冷凍・保存", rect=[W - sw, (fy + ty) / 2, W, ty], kind="back"), dict(label="更衣室・社員トイレ", rect=[W - sw, fy, W, (fy + ty) / 2], kind="back"), dict(label="洗い場", rect=[px + 0.3 + pw, ty + wash_gap, W, D], kind="back"), ] walls = [dict(label="パントリーの壁", rect=[px, fy, px + 0.3, ty])] dish = [dict(label="受け渡し口", rect=[px + 0.3, fy, px + 0.3 + pw, fy + 0.4])] standby = dict(label="ロボット待機・充電", rect=[px + 0.5 + pw, fy - 0.85, px + 0.5 + pw + 2.4, fy - 0.05], slots=3) return rooms, walls, dish, standby, [px + 0.3 + pw / 2, fy - 0.45] def boh_top(W, D, x0, x1, fy, staff): """厨房が奥(上)。パントリーは横長で、受け渡し口は真ん中""" mid = (x0 + x1) / 2 rooms = [ dict(label="パントリー", rect=[x0, fy, x1, fy + 1.2], kind="back"), dict(label="キッチン", rect=[x0, fy + 1.2, x1, D - 3.0], kind="back"), dict(label="洗い場", rect=[x0, D - 3.0, mid, D], kind="back"), dict(label="冷蔵・冷凍・保存", rect=[mid, D - 3.0, x1, D], kind="back"), dict(label="更衣室・社員トイレ", rect=staff, kind="back"), ] dish = [dict(label="受け渡し口", rect=[mid - 1.5, fy, mid + 1.5, fy + 0.4])] standby = dict(label="ロボット待機・充電", rect=[mid + 1.7, fy - 0.85, mid + 4.1, fy - 0.05], slots=3) return rooms, [], dish, standby, [mid, fy - 0.45] # ---------------------------------------------------------------- 5パターン def p1_efficient_corner(): W, D = 24.0, 16.0 rooms, walls, dish, sb, start = boh_right(W, D, 14.0, 6.5, 13.8) toilet, twalls, tdoor = toilet_block([0.0, 13.8, 2.8, 16.0], "right", W, D) # 左上の角。入口は右向き rooms += [toilet] walls += twalls rooms += [dict(label="待合", rect=[0.0, 0.9, 0.5, 3.3], kind="guest"), # 縦のカウンター。客は入口の側(左)に立ち、入口へ向かって並ぶ。下の通路をふさがない(2026-10-23 エイイチと決めた) *register([3.3, 0.4, 4.0, 2.9], [4.0, 0.4, 4.9, 2.9]), dict(label="ドリンクバー", rect=[16.8, 2.8, 20.8, 3.5], kind="drink")] t = strip(0.0, 4.6, "v", 4) # 左の壁ぞいのソファ(トイレの手前で止める) t += island(2.4, 4.8, "v", 4) # 背中合わせのソファの列(間の通路1.2m) t += island(6.1, 4.8, "v", 5) t += island(9.8, 4.8, "v", 4) t += strip(6.8, 0.0, "h", 3, seats=6) # 家族用の6人ボックス(下の壁ぞい・レジ内の出入りを空ける) t += two_seat_row(14.6, 0.0, "bottom", 8) # 2人席(右下の壁ぞい) t += [lsofa(21.2, 3.0, 2.8, 3.5, flip=True, mirror=True)] return dict(name="③ 効率重視型・入口が左下の角", style="効率重視", outer=dict(w=W, d=D), entrance=dict(side="bottom", a0=0.6, a1=3.0), rooms=rooms, walls=walls, toilet_doors=[tdoor], dish_up=dish, robot_standby=sb, start=start, tables=t) def p2_efficient_narrow(): W, D = 17.0, 12.0 rooms, walls, dish, sb, start = boh_right(W, D, 10.0, 5.0, 10.0, pw=1.5, sw=1.8, wash_gap=0.8) toilet, twalls, tdoor = toilet_block([0.0, 10.0, 2.4, 12.0], "bottom", W, D) # 左上の角。入口は下向き rooms += [toilet] walls += twalls sb["rect"] = [12.0, 4.15, 13.6, 4.95] sb["slots"] = 2 rooms += [dict(label="待合", rect=[16.5, 3.2, 17.0, 4.8], kind="guest"), *register([13.6, 0.3, 14.3, 3.0], [12.9, 0.3, 13.6, 3.0]), # 入口に向いた縦のカウンター dict(label="ドリンクバー", rect=[10.4, 1.6, 12.4, 2.2], kind="drink")] t = two_seat_row(0.0, 2.2, "left", 5) # 左の壁ぞいの2人席(トイレの入口の手前で止める) t += two_seat_row(3.5, 10.6, "top", 6, pitch=1.05) # 上の壁ぞいの2人席(トイレから1m以上離す) t += island(2.6, 2.9, "v", 3) t += island(6.3, 2.9, "v", 3) t += strip(1.6, 0.0, "h", 2, seats=6) # 家族用の6人ボックス t += two_seat_row(5.6, 0.0, "bottom", 3) return dict(name="④ 効率重視型・狭い店・入口が右の壁", style="効率重視", outer=dict(w=W, d=D), entrance=dict(side="right", a0=0.6, a1=3.0), rooms=rooms, walls=walls, toilet_doors=[tdoor], dish_up=dish, robot_standby=sb, start=start, tables=t) def p3_relaxed_center(): W, D = 26.0, 17.0 rooms, walls, dish, sb, start = boh_top(W, D, 9.0, 19.0, 9.4, staff=[19.0, 14.5, 23.0, 17.0]) toilet, twalls, tdoor = toilet_block([0.0, 14.6, 3.0, 17.0], "right", W, D) # 左上の角。入口は右向き walls += twalls rooms += [toilet, *register([11.2, 0.3, 11.9, 2.8], [10.4, 0.3, 11.2, 2.8]), # 入口の横に縦向き(前の通路をふさがない) dict(label="待合", rect=[15.4, 0.3, 15.9, 2.3], kind="guest"), # 入口の右に縦向き(6人席の前をふさがない) dict(label="ドリンクバー", rect=[15.0, 4.6, 18.6, 5.3], kind="drink")] t = strip(0.0, 1.4, "v", 6, walls=walls) # 左の壁ぞい(ボックスごとに仕切り・トイレの手前で止める) t += island(2.7, 3.4, "v", 5, walls=walls) t += island(6.75, 3.4, "v", 3, walls=walls) t += strip(24.8, 1.4, "v", 6, walls=walls) # 右の壁ぞい t += island(20.75, 3.4, "v", 5, walls=walls) t += strip(1.6, 0.0, "h", 4, seats=6, walls=walls) # 家族用の6人ボックス(下の壁ぞい・左) t += strip(16.4, 0.0, "h", 4, seats=6, walls=walls) # 同(右)。入口の右側 return dict(name="⑤ ゆったり型・入口が正面の中央", style="ゆったり", outer=dict(w=W, d=D), entrance=dict(side="bottom", a0=12.0, a1=15.0), rooms=rooms, walls=walls, toilet_doors=[tdoor], dish_up=dish, robot_standby=sb, start=start, tables=t) def p4_relaxed_side(): W, D = 24.0, 17.0 rooms, walls, dish, sb, start = boh_right(W, D, 13.0, 6.5, 14.5) toilet, twalls, tdoor = toilet_block([9.4, 14.8, 12.4, 17.0], "bottom", W, D) # 上の壁の真ん中。入口は下向き rooms += [toilet] walls += twalls rooms += [dict(label="待合", rect=[0.6, 0.0, 3.0, 0.5], kind="guest"), # 縦のカウンター。客は入口の側(左)に立ち、入口へ向かって並ぶ。下の通路をふさがない(2026-10-23 エイイチと決めた) *register([2.8, 0.7, 3.5, 3.2], [3.5, 0.7, 4.4, 3.2]), dict(label="ドリンクバー", rect=[16.0, 3.4, 20.0, 4.1], kind="drink")] t = strip(0.3, 15.2, "h", 4, seats=6, walls=walls) # 家族用の6人ボックス(上の壁ぞい) t += strip(0.0, 5.2, "v", 5, walls=walls) # 左の壁ぞい t += island(2.7, 5.6, "v", 4, walls=walls) t += island(6.75, 5.6, "v", 4, walls=walls) t += strip(24.0 - 5 * UNIT, 0.0, "h", 5, seats=6, walls=walls) # 家族用の6人ボックス(右下・右の壁まで) t += two_seat_row(6.6, 0.0, "bottom", 7) t += [lsofa(21.2, 3.0, 2.8, 3.5, flip=True, mirror=True)] # 下のボックスとの間を1.2mあける return dict(name="⑥ ゆったり型・入口が左の壁", style="ゆったり", outer=dict(w=W, d=D), entrance=dict(side="left", a0=1.0, a1=3.4), rooms=rooms, walls=walls, toilet_doors=[tdoor], dish_up=dish, robot_standby=sb, start=start, tables=t) def p5_crammed(): """③と同じ卓・同じ席数のまま、列の間の通路だけを1.2mから0.95mに詰めた比較用。 はじめは0.8m(BellaBot の通れる幅ちょうど)だったが、Unity でロボットがほとんど動けず、 右端の列の横に詰めた分の余りがあったので、島を右へずらして0.95mにした(2026-10-23 エイイチと決めた)""" L = p1_efficient_corner() L["name"] = "⑦ ③の列の間の通路を95cmに(卓と席数は③と同じ)" AISLE = 0.95 t = strip(0.0, 4.6, "v", 4) x = 1.2 + AISLE for n in (4, 5, 4): # ③と同じ島(4・5・4ボックス) t += island(x, 4.8, "v", n) x += island_width() + AISLE t += strip(6.8, 0.0, "h", 3, seats=6) t += two_seat_row(14.6, 0.0, "bottom", 8) t += [lsofa(21.2, 3.0, 2.8, 3.5, flip=True, mirror=True)] L["tables"] = t return L # ---------------------------------------------------------------- 測る・確かめる def footprint(t): return (t["x"], t["y"], t["x"] + t["w"], t["y"] + t["h"]) def check_overlaps(L): """卓どうし・卓と部屋が重なっていないか""" fps = [footprint(t) for t in L["tables"]] bad = [] for i, a in enumerate(fps): for j in range(i + 1, len(fps)): if overlap(a, fps[j], -0.01): bad.append(("卓どうし", i, j)) for r in L["rooms"]: if overlap(a, r["rect"], -0.01): bad.append(("卓と" + r["label"], i)) return bad def rect_gap(a, b): dx = max(0.0, max(a[0], b[0]) - min(a[2], b[2])) dy = max(0.0, max(a[1], b[1]) - min(a[3], b[3])) return (dx * dx + dy * dy) ** 0.5 def check_toilet(L, near=1.0): """決まり:トイレから1m以内に席を置かない。トイレの入口の前1.2mには何も置かない""" toilets = [r["rect"] for r in L["rooms"] if r["label"] == "トイレ"] doors = L.get("toilet_doors", []) others = [r["rect"] for r in L["rooms"] if r["label"] != "トイレ"] bad = [] for i, t in enumerate(L["tables"]): fp = footprint(t) if any(rect_gap(fp, tr) < near for tr in toilets): bad.append(("トイレから1m以内の席", i)) if any(overlap(fp, d, -0.01) for d in doors): bad.append(("トイレの入口の前に席", i)) for r in others: if any(overlap(r, d, -0.01) for d in doors): bad.append(("トイレの入口の前に部屋", r)) return bad def analyze(L): # famires_gen.analyze は type ごとの形を知っているので、ここでは矩形として渡す tmp = dict(L) tmp["tables"] = [dict(type="rect", x=t["x"], y=t["y"], w=t["w"], h=t["h"], seats=t["seats"]) for t in L["tables"]] return _analyze(tmp) # ---------------------------------------------------------------- 描く def draw_seat(ax, t, ng, part): ec = NG if ng else INK x, y, w, h = t["x"], t["y"], t["w"], t["h"] lab = str(t["seats"]) if t["type"] == "booth": ax.add_patch(Rectangle((x, y), w, h, fc=SEAT, ec=ec, lw=0.9, zorder=3)) for b in ((x, y, w, 0.55), (x, y + h - 0.55, w, 0.55)) if t["axis"] == "v" else ((x, y, 0.55, h), (x + w - 0.55, y, 0.55, h)): ax.add_patch(Rectangle(b[:2], b[2], b[3], fc=SOFA, ec=ec, lw=0.7, zorder=3)) ax.text(x + w / 2, y + h / 2, lab, ha="center", va="center", fontsize=8, color=ec, weight="bold" if t["seats"] == 6 else "normal", zorder=5) elif t["type"] == "t2": ax.add_patch(Rectangle((x, y), w, h, fc="none", ec="none", zorder=3)) side = t["side"] if side == "bottom": bench, tab, chair = (x, y, w, 0.45), (x + 0.05, y + 0.5, 0.7, 0.6), (x + w / 2, y + 1.2) elif side == "top": bench, tab, chair = (x, y + h - 0.45, w, 0.45), (x + 0.05, y + 0.3, 0.7, 0.6), (x + w / 2, y + 0.2) elif side == "left": bench, tab, chair = (x, y, 0.45, h), (x + 0.5, y + 0.05, 0.6, 0.7), (x + 1.2, y + h / 2) else: bench, tab, chair = (x + w - 0.45, y, 0.45, h), (x + 0.3, y + 0.05, 0.6, 0.7), (x + 0.2, y + h / 2) ax.add_patch(Rectangle(bench[:2], bench[2], bench[3], fc=SOFA, ec=ec, lw=0.7, zorder=3)) ax.add_patch(Rectangle(tab[:2], tab[2], tab[3], fc=SEAT, ec=ec, lw=0.9, zorder=3)) ax.add_patch(Circle(chair, 0.18, fc=SEAT, ec=ec, lw=0.7, zorder=3)) ax.text(tab[0] + tab[2] / 2, tab[1] + tab[3] / 2, lab, ha="center", va="center", fontsize=7, color=ec, zorder=5) elif t["type"] == "booth6L": pts = [(x, y), (x + w, y), (x + w, y + h * 0.45), (x + w * 0.55, y + h * 0.45), (x + w * 0.55, y + h), (x, y + h)] lx, ly = x + w * 0.3, y + h * 0.4 if t.get("flip"): pts = [(px, 2 * y + h - py) for px, py in pts] ly = y + h * 0.6 if t.get("mirror"): pts = [(2 * x + w - px, py) for px, py in pts] lx = x + w * 0.7 ax.add_patch(Polygon(pts, closed=True, fc=SOFA, ec=ec, lw=1.0, zorder=3)) ax.text(lx, ly, lab, ha="center", va="center", fontsize=8, color=ec, weight="bold", zorder=5) def draw(ax, L, results, stats): W, D = L["outer"]["w"], L["outer"]["d"] ax.set_xlim(-1.2, W + 1.2) ax.set_ylim(-1.8, D + 0.6) ax.set_aspect("equal") ax.axis("off") e = L["entrance"] segs = {"bottom": [((0, 0), (W, 0))], "top": [((0, D), (W, D))], "left": [((0, 0), (0, D))], "right": [((W, 0), (W, D))]} for side, ((xa, ya), (xb, yb)) in [(k, v[0]) for k, v in segs.items()]: if side != e["side"]: ax.plot([xa, xb], [ya, yb], color=INK, lw=3) elif side in ("bottom", "top"): ax.plot([0, e["a0"]], [ya, ya], color=INK, lw=3) ax.plot([e["a1"], W], [ya, ya], color=INK, lw=3) else: ax.plot([xa, xa], [0, e["a0"]], color=INK, lw=3) ax.plot([xa, xa], [e["a1"], D], color=INK, lw=3) m = (e["a0"] + e["a1"]) / 2 pos = {"bottom": (m, -0.5), "top": (m, D + 0.4), "left": (-0.7, m), "right": (W + 0.7, m)}[e["side"]] ax.text(*pos, "入口" if e["side"] in ("bottom", "top") else "入\n口", ha="center", va="center", fontsize=10, weight="bold") for r in L["rooms"]: fc = {"back": BACK, "guest": "#f8f9fa", "drink": "#d0ebff"}[r["kind"]] ec = DRINK if r["kind"] == "drink" else INK x0, y0, x1, y1 = r["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc=fc, ec=ec, lw=1.0, zorder=2)) tall = (y1 - y0) > (x1 - x0) * 1.6 text = "\n".join(r["label"]) if tall and len(r["label"]) <= 5 else r["label"] ax.text((x0 + x1) / 2, (y0 + y1) / 2, text, ha="center", va="center", fontsize=8 if len(r["label"]) > 5 else 9, color=DRINK if r["kind"] == "drink" else INK, zorder=5) for w in L["walls"]: x0, y0, x1, y1 = w["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="#868e96", ec="#495057", lw=0.5, zorder=4)) for d in L["dish_up"]: x0, y0, x1, y1 = d["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc=DISH, ec=DISH, zorder=4)) for r in results: if r["reach"]: xs, ys = zip(*r["path"]) ax.plot(xs, ys, color=ROBOT, lw=0.9, alpha=0.3, zorder=2.5) for t, r in zip(L["tables"], results): draw_seat(ax, t, not r["reach"], False) sb = L["robot_standby"] x0, y0, x1, y1 = sb["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="#fff4e6", ec=ROBOT, lw=1.4, zorder=5)) for i in range(sb["slots"]): ax.add_patch(Circle((x0 + (x1 - x0) * (i + 0.5) / sb["slots"], (y0 + y1) / 2), 0.22, fc=ROBOT, ec="white", lw=0.8, zorder=6)) by = {} for t in L["tables"]: by[t["seats"]] = by.get(t["seats"], 0) + 1 mix = "・".join(f"{k}人席{v}" for k, v in sorted(by.items())) ng = f' 届かない卓 {stats["tables"] - stats["reachable"]}({stats["unreachable_seats"]}席)' if stats["reachable"] < stats["tables"] else "" ax.text(0, -1.15, f'{L["name"]} {stats["seats"]}席({mix})・{W:g}m×{D:g}m', fontsize=10, color=INK, va="center") ax.text(0, -1.6, f'受け渡し口から卓まで 平均{stats["mean_dist"]}m・最長{stats["max_dist"]}m{ng} ' f'(オレンジの線=ロボットの道・幅は仮に70cm/赤=届かない卓/寸法はすべて仮)', fontsize=8.5, color=ROBOT if not ng else NG, va="center") def main(): OUT.mkdir(exist_ok=True) builds = [("p1_efficient_corner", p1_efficient_corner), ("p2_efficient_narrow", p2_efficient_narrow), ("p3_relaxed_center", p3_relaxed_center), ("p4_relaxed_side", p4_relaxed_side), ("p5_crammed", p5_crammed)] summary = [] for nm, fn in builds: L = fn() bad = check_overlaps(L) + check_toilet(L) results, stats = analyze(L) fig, ax = plt.subplots(figsize=(14, 10.5), dpi=120) draw(ax, L, results, stats) fig.savefig(OUT / f"{nm}.png", bbox_inches="tight", facecolor="white") plt.close(fig) json.dump(L, open(OUT / f"{nm}.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2) summary.append(dict(file=nm, name=L["name"], overlaps=len(bad), **stats)) print(nm, stats, "重なり:", bad[:5]) json.dump(summary, open(OUT / "summary.json", "w", encoding="utf-8"), ensure_ascii=False, indent=2) if __name__ == "__main__": main()