"""大型店の店の形(郊外のゆったり型・70卓まで)。2026-11-02 エイイチと決めた定義で作る試作 定義(2026-11-02) - 大型店は郊外の店(都市部の大型店は採算が合いにくい)。ゆったり型:仕切り壁多め・ボックス席中心 - 卓は最大70卓くらい(⑤の46卓と差が出るように) - 外寸 34m×22m(748㎡・約226坪)。厨房・洗い場は大きめ(2026-11-02 エイイチ)の横長。客席側の1卓あたりの広さは ⑤⑥(7.7〜8.1㎡)より広め - 受け渡し口は「奥の真ん中」が基本。店の回し方で置き場所が変わるので、候補(パントリーの左寄り・右寄り)を選べるようにしておく(DISH_POS)。 厨房・客席は3つとも同じで、変わるのは受け渡し口と待機場所だけ(比べたときの差が受け渡し口の位置だけになるように) - レジ2つ・客用トイレ2か所・ドリンクバー2つ・ロボットの待機場所6台分 - 通路は今の店と同じ決まり(ロボットのふだんの道1.0m・最低0.8m) 作り方・確かめ・Unity の細かい形と地図は famires_v3 と同じ規則。公開している6店の形(public/sim/layouts/ の p0〜p5)は変えない。 使い方: python3 large_store.py [center|left|right|b] [--out DIR] (b は ⑨ 厨房が右・入口が左下の角) python3 large_store.py --publish (サイトに載せる⑧奥の真ん中・⑨を public/sim/layouts/large/ に出す) """ import json import tempfile import sys from pathlib import Path import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt import famires_v3 as F from famires_gen2 import strip, island, two_seat_row, lsofa, register, toilet_block, UNIT from famires_gen2 import boh_right as gen_boh_right HERE = Path(__file__).resolve().parent W, D = 34.0, 22.0 # 奥行きは厨房・洗い場を大きめにするため 20→22m(2026-11-02 エイイチ:大型店なのでキッチン・洗い場は大きめでよい) BACK_W = 12.0 # 厨房側(パントリー・キッチン・洗い場・保存)の幅 FY = 11.8 # パントリーの客席側の辺(受け渡し口の y) X0 = (W - BACK_W) / 2 # 厨房側は奥の真ん中に置いたまま。候補で動かすのは受け渡し口と待機場所だけ # 受け渡し口の候補(パントリーの客席側の辺のどこに開けるか)。基本は真ん中。 # 待機場所(6台分)は受け渡し口の横で、厨房の真ん中の側に置く DISH_POS = ("center", "left", "right") NAMES = {"center": "受け渡し口が奥の真ん中", "left": "受け渡し口が奥の左寄り", "right": "受け渡し口が奥の右寄り"} def back_block(x0, pos="center"): x1 = x0 + BACK_W 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 - 4.0], kind="back"), dict(label="洗い場", rect=[x0, D - 4.0, mid + 1.0, D], kind="back"), dict(label="冷蔵・冷凍・保存", rect=[mid + 1.0, D - 4.0, x1 - 3.0, D], kind="back"), dict(label="更衣室・社員トイレ", rect=[x1 - 3.0, D - 4.0, x1, D], kind="back"), ] c = {"center": mid, "left": x0 + 1.8, "right": x1 - 1.8}[pos] # 受け渡し口の真ん中 dish = [dict(label="受け渡し口", rect=[c - 1.5, FY, c + 1.5, FY + 0.4])] # 6台分(1台 0.8m)を横一列。真ん中と左寄りは受け渡し口の右、右寄りは左 sx = c - 1.7 - 4.8 if pos == "right" else c + 1.7 standby = dict(label="ロボット待機・充電", rect=[sx, FY - 0.85, sx + 4.8, FY - 0.05], slots=6) return rooms, dish, standby, [c, FY - 0.45] def build(pos="center"): x0 = X0 x1 = x0 + BACK_W rooms, dish, sb, start = back_block(x0, pos) walls = [] # 客用トイレ2か所(左上・右上の角) tl, tw, td1 = toilet_block([0.0, D - 2.8, 3.0, D], "right", W, D) tr, tw2, td2 = toilet_block([W - 3.0, D - 2.8, W, D], "left", W, D) rooms += [tl, tr] walls += tw + tw2 # 入口は正面(下の壁)の真ん中。左にレジ2つ(同じレジ内を挟まず、レジ内の右に縦に2つ)、右に待合 ea0, ea1 = W / 2 - 1.5, W / 2 + 1.5 rx = ea0 - 1.8 # レジのカウンターの左端 rooms += [*register([rx, 0.3, rx + 0.7, 2.8], [rx - 0.8, 0.3, rx, 5.9]), dict(label="レジ", rect=[rx, 3.4, rx + 0.7, 5.9], kind="guest"), dict(label="待合", rect=[ea1 + 1.4, 0.3, ea1 + 1.9, 3.3], kind="guest"), # ドリンクバー2つ(厨房の左右の壁ぎわ。客席の左右それぞれから近い) dict(label="ドリンクバー", rect=[x0 - 0.7, 13.6, x0, 16.6], kind="drink"), dict(label="ドリンクバー", rect=[x1, 13.6, x1 + 0.7, 16.6], kind="drink")] t = [] # ---- 左の客席 t += strip(0.0, 1.4, "v", 7, walls=walls) # 左の壁ぞい(トイレの手前で止める) t += island(2.7, 3.4, "v", 5, walls=walls) t += island(6.75, 3.4, "v", 5, walls=walls) t += strip(4.4, D - 1.8, "h", 2, seats=6, walls=walls) # 家族用の6人ボックス(上の壁ぞい・左) t += strip(1.6, 0.0, "h", 6, seats=6, walls=walls) # 家族用の6人ボックス(下の壁ぞい・左) # ---- 右の客席(左と同じ並び) t += strip(W - 1.2, 1.4, "v", 7, walls=walls) t += island(W - 2.7 - 2.55, 3.4, "v", 5, walls=walls) t += island(W - 6.75 - 2.55, 3.4, "v", 5, walls=walls) t += strip(W - 4.4 - 2 * UNIT, D - 1.8, "h", 2, seats=6, walls=walls) t += strip(W - 1.6 - 6 * UNIT, 0.0, "h", 6, seats=6, walls=walls) L = dict(name=f"⑧ 大型店・郊外のゆったり型({NAMES[pos]})", style="ゆったり", outer=dict(w=W, d=D), entrance=dict(side="bottom", a0=ea0, a1=ea1), rooms=rooms, walls=walls, toilet_doors=[td1, td2], dish_up=dish, robot_standby=sb, start=start, tables=t) return L def build_b(): """⑨ 大型店のもう1つの形:厨房が右の壁ぞい(パントリーは縦長で、受け渡し口は下の端。③と同じ向き)・入口が左下の角。 客席は右の厨房を除いた横長の1つの部屋に、ゆったりの島を5本並べる。卓の数・席の種類は⑧と同じ70卓""" # 厨房側:パントリーは縦長で奥の壁まで(洗い場への通り道を兼ねる)。キッチンの上に大きめの洗い場 rooms = [dict(label="パントリー", rect=[24.3, 7.0, 26.3, D], kind="back"), dict(label="キッチン", rect=[26.3, 7.0, 31.0, 16.0], kind="back"), dict(label="冷蔵・冷凍・保存", rect=[31.0, 11.5, W, 16.0], kind="back"), dict(label="更衣室・社員トイレ", rect=[31.0, 7.0, W, 11.5], kind="back"), dict(label="洗い場", rect=[26.3, 16.0, W, D], kind="back")] walls = [dict(label="パントリーの壁", rect=[24.0, 7.0, 24.3, D])] dish = [dict(label="受け渡し口", rect=[24.3, 7.0, 26.3, 7.4])] # ロボットの待機場所は、パントリーの縦の壁ぞい(受け渡し口のすぐ上から6台分を縦に。2026-11-02 エイイチ) sb = dict(label="ロボット待機・充電", rect=[23.2, 7.4, 24.0, 12.2], slots=6) start = [25.3, 6.55] # 客用トイレは右下に大きめの1か所・男女別(2026-11-02 エイイチ:店の設計上、水回りはまとまっている方がよい。 # 厨房・更衣室の社員トイレの下にまとめる)。下が男・上が女のつもり(Unity ではどちらも「トイレ」)。入口はどちらも左向き # 上の端は更衣室・社員トイレの下の辺(y 7.0)までのばし、厨房との間にすき間を残さない(2026-11-02 エイイチ) tm, tw, td1 = toilet_block([30.5, 0.0, W, 3.5], "left", W, D) tf, tw2, td2 = toilet_block([30.5, 3.5, W, 7.0], "left", W, D) rooms += [tm, tf] walls += tw + tw2 rooms += [dict(label="待合", rect=[0.0, 0.9, 0.5, 3.6], kind="guest"), # 縦のカウンター2つ。客は入口の側(左)に立ち、入口へ向かって並ぶ(③と同じ置き方) # 2つ目は1つ目のすぐ上(レジの上の端と島の下の端の間を1.3m空け、左の客席へ下から入れるようにする) *register([3.6, 0.4, 4.3, 2.9], [4.3, 0.4, 5.1, 5.4]), dict(label="レジ", rect=[3.6, 2.9, 4.3, 5.4], kind="guest"), # ドリンクバーは1つにまとめて大きくし、上の壁ぎわのパントリーの近くに置く(2026-11-02 エイイチ) dict(label="ドリンクバー", rect=[17.6, D - 0.7, 24.0, D], kind="drink")] t = strip(0.0, 7.2, "v", 4, walls=walls) # 左の壁ぞい(レジの上から) for x in (2.7, 6.75, 10.8, 14.85, 18.9): # ゆったりの島を5本(間の通路1.5m) t += island(x, 6.9, "v", 5, walls=walls) t += strip(5.6, 0.0, "h", 8, seats=6, walls=walls) # 家族用の6人ボックス(下の壁ぞい) # 右下にあった6人ボックス3つは、トイレにした所から上の壁ぞいへ移した(2026-11-02 エイイチ) t += strip(0.0, D - 1.8, "h", 8, seats=6, walls=walls) # 家族用の6人ボックス(上の壁ぞい。左の角から並べる。2026-11-02 エイイチ) return dict(name="⑨ 大型店・郊外のゆったり型(厨房が右・入口が左下の角)", style="ゆったり", outer=dict(w=W, d=D), entrance=dict(side="bottom", a0=0.6, a1=3.6), rooms=rooms, walls=walls, toilet_doors=[td1, td2], dish_up=dish, robot_standby=sb, start=start, tables=t) def run(pos, out): L = F.prepare(build_b() if pos == "b" else build(pos)) v = F.check_rules(L) if v: for k, m in v: print("NG", k, m) return None ctx = {} res_a, err_a = F.analyze(L, F.SETTINGS["res"], ctx) res_b, err_b = F.analyze(L, F.SETTINGS["check_res"]) v = err_a + [e for e in err_b if e not in err_a] if v: for k, m in v: print("NG", k, m) return None conv = [a["id"] for a, b in zip(res_a, res_b) if a["category"] != b["category"]] summ = F.summarize(L, res_a) nm = "p7_large_b" if pos == "b" else f"p6_large_{pos}" out.mkdir(parents=True, exist_ok=True) fig, ax = plt.subplots(figsize=(18, 11), dpi=110) F.draw(ax, L, res_a, summ, conv) fig.savefig(out / f"{nm}.png", bbox_inches="tight", facecolor="white") plt.close(fig) # サイトの平面図(serving-robot.html の planSvg・bigPlanSvg)と同じ形のデータ。画像は large_plan_images.py で描く ds = [r["dist"] for r in res_a if r.get("dist") is not None] plan = dict(w=W, d=D, outline=[[0, 0], [W, 0], [W, D], [0, D]], rooms=[[*r["rect"], r.get("kind", "back"), r["label"]] for r in L["rooms"]], walls=[w["rect"] for w in L["walls"]], tables=[[*F.fp(t), t["seats"]] for t in L["tables"]], dish=[d["rect"] for d in L["dish_up"]], standby=L["robot_standby"]["rect"], ent=[L["entrance"]["side"], L["entrance"]["a0"], L["entrance"]["a1"]]) info = dict(key=nm, name=L["name"], tables=len(L["tables"]), seats=summ["seats"], dist_m=[round(min(ds), 1), round(max(ds), 1)], dist_mean=summ["robot_dist_mean"], plan=plan) json.dump(info, open(out / f"{nm}_plan.json", "w", encoding="utf-8"), ensure_ascii=False) print("OK", nm, summ, "マス目の差", conv) return L, res_a, summ, conv, ctx PUBLISH = {"p6_large_center": "center", "p7_large_b": "b"} # サイトに載せる大型店の形(2026-11-02 エイイチ:⑧奥の真ん中・⑨入口が角) def publish(): """サイトに載せる大型店の形を public/sim/layouts/large/ に出す(今の6店の public/sim/layouts/ 直下は変えない)。 作り方・確かめ・Unity の細かい形と地図は famires_v3 と同じ。全部が決まりを守ったときだけ書く""" import hashlib out = HERE.parent.parent / "public" / "sim" / "layouts" / "large" h = hashlib.sha256((F.generation_id() + (HERE / "large_store.py").read_text(encoding="utf-8")).encode()) gen_id = "large-" + h.hexdigest()[:12] tmp = Path(tempfile.mkdtemp(prefix="large_")) allsum = [] for nm, pos in PUBLISH.items(): r = run(pos, tmp) if r is None: sys.exit(f"[NG] {nm}: 決まりに違反があるので、{out} には何も書いていない") L, res_a, summ, conv, ctx = r F.export(L, res_a, summ, conv, nm, tmp, gen_id) U = F.export_unity(L, res_a, ctx, nm, tmp, gen_id) vr = F.check_register_clear(U) if vr: sys.exit(f"[NG] {nm}: {vr}") allsum.append(dict(file=nm, status="OK", generation=gen_id, resolution_mismatch=conv, **summ)) json.dump(allsum, open(tmp / "summary.json", "w", encoding="utf-8"), ensure_ascii=False, indent=1) out.mkdir(parents=True, exist_ok=True) for f in tmp.rglob("*"): if f.is_file() and not f.name.endswith(".png") and not f.name.endswith("_plan.json"): dst = out / f.relative_to(tmp) dst.parent.mkdir(parents=True, exist_ok=True) f.replace(dst) print("published", out, gen_id) if __name__ == "__main__": args = sys.argv[1:] if "--publish" in args: publish() sys.exit(0) out = Path(tempfile.gettempdir()) / "variants_large" # 試作の図はリポジトリの外に出す(png は GitHub に入れない) if "--out" in args: i = args.index("--out") out = Path(args[i + 1]) args = args[:i] + args[i + 2:] for pos in (args or list(DISH_POS)): run(pos, out)