# ②(記憶の店)を、寸法つきの平面図(CAD 風)として描く # 元:公開している店の形のデータ(public/sim/layouts/unity/p0_memory.json。版 9b8dae7ebf8c)。読むだけで変えない # 実際の店の図面にないもの(ロボットの待機場所・道・止まる位置・会計の列など)は描かない # 使い方: python3 draw_plan.py <出力png> import json, sys, math import matplotlib; matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib.patches import Rectangle, Circle, Arc, Polygon SRC = "/home/claude/p2m-site/public/sim/layouts/unity/p0_memory.json" U = json.load(open(SRC)) plt.rcParams["font.family"] = "Noto Sans CJK JP" K = "#111111" fig, ax = plt.subplots(figsize=(16, 13.5), dpi=150) ax.set_aspect("equal"); ax.axis("off") W, D = U["outer"]["w"], U["outer"]["d"] def rect(r, lw=0.8, fc="none", ec=K, z=2, hatch=None): ax.add_patch(Rectangle((r[0], r[1]), r[2] - r[0], r[3] - r[1], fc=fc, ec=ec, lw=lw, zorder=z, hatch=hatch)) def label(r, s, fs=9, rot=0): ax.text((r[0] + r[2]) / 2, (r[1] + r[3]) / 2, s, ha="center", va="center", fontsize=fs, rotation=rot, zorder=6, bbox=dict(fc="white", ec=K, lw=0.6, pad=2)) # 外壁(入口は開ける) for r in U["outer_walls"]: rect(r, lw=0.6, fc=K, z=3) # 内壁・仕切り for w in U["walls"]: tall = w.get("height", 2.7) > 2.0 rect(w["rect"], lw=0.6, fc=K if tall else "white", z=3, hatch=None if tall else "////") # 部屋(厨房側は輪郭と名前だけ。建物の外は描かない) for r in U["rooms"]: if r["kind"] == "outside": continue lab = r["label"] if lab in ("レジ", "ドリンクバー", "待合"): rect(r["rect"], lw=1.0, z=4) label(r["rect"], {"レジ": "レジ", "ドリンクバー": "ドリンクバー", "待合": "待合"}[lab], fs=8) elif lab == "レジ内": continue else: rect(r["rect"], lw=1.2, z=2) x0, y0, x1, y1 = r["rect"] label(r["rect"], lab, fs=10 if (x1 - x0) > 1.5 else 8, rot=90 if (y1 - y0) > 2.5 * (x1 - x0) else 0) # 料理の受け渡し口(台) for p in U.get("places", []): pass for d in json.load(open("/home/claude/p2m-site/public/sim/layouts/p0_memory.json"))["layout"]["dish_up"]: rect(d["rect"], lw=1.2, fc="white", z=5, hatch="xxxx") x0, y0, x1, y1 = d["rect"] ax.text((x0 + x1) / 2, y0 - 0.35 if "下" in d["label"] else y1 + 0.35, "受け渡し口", ha="center", va="center", fontsize=8, zorder=6) # 店員の出入り口(扉ありは開く向き、なしは開口) for d in U["doors"]: x0, y0, x1, y1 = d["rect"] rect(d["rect"], lw=0, fc="white", ec="white", z=4) if d["door"]: w = x1 - x0 ax.add_patch(Arc((x0, y0), 2 * w, 2 * w, theta1=270, theta2=360, lw=0.7, ec=K, zorder=5)) ax.plot([x0, x0], [y0, y0 - w], color=K, lw=0.9, zorder=5) # トイレの入口 L0 = json.load(open("/home/claude/p2m-site/public/sim/layouts/p0_memory.json"))["layout"] for t in L0["toilet_doors"]: x0, y0, x1, y1 = t rect(t, lw=0, fc="white", ec="white", z=4) w = x1 - x0 if (x1 - x0) > (y1 - y0) else y1 - y0 if (x1 - x0) >= (y1 - y0): ax.add_patch(Arc((x0, y0), 2 * w, 2 * w, theta1=270, theta2=360, lw=0.7, ec=K, zorder=5)) else: ax.add_patch(Arc((x0, y0), 2 * w, 2 * w, theta1=0, theta2=90, lw=0.7, ec=K, zorder=5)) # 入口の扉(両開き) e = U["entrance"] a0, a1 = e["a0"], e["a1"]; h = (a1 - a0) / 2 ax.add_patch(Arc((a0, 0), 2 * h, 2 * h, theta1=270, theta2=360, lw=0.7, ec=K, zorder=5)) ax.add_patch(Arc((a1, 0), 2 * h, 2 * h, theta1=180, theta2=270, lw=0.7, ec=K, zorder=5)) ax.plot([a0, a0], [0, -h], color=K, lw=0.9); ax.plot([a1, a1], [0, -h], color=K, lw=0.9) ax.text((a0 + a1) / 2, -1.6, "入口", ha="center", va="center", fontsize=11, bbox=dict(fc="white", ec=K, lw=0.6, pad=2)) # 卓:天板・椅子・ソファ for t in U["tables"]: P = t["parts"] for b in P.get("benches", []): rect(b, lw=0.8, z=4) top = P["top"] if P.get("top_shape") == "circle": ax.add_patch(Circle(((top[0] + top[2]) / 2, (top[1] + top[3]) / 2), (top[2] - top[0]) / 2, fc="white", ec=K, lw=0.9, zorder=5)) else: rect(top, lw=0.9, fc="white", z=5) for c in P.get("chairs", []): r = c["rect"] if c.get("stool"): ax.add_patch(Circle(((r[0] + r[2]) / 2, (r[1] + r[3]) / 2), (r[2] - r[0]) / 2, fc="white", ec=K, lw=0.7, zorder=4)) else: rect(r, lw=0.7, fc="white", z=4) # 寸法線(mm) def dim_h(xs, y, tick=0.25, fs=9): ax.plot([xs[0], xs[-1]], [y, y], color=K, lw=0.6) for x in xs: ax.plot([x, x], [y - tick, y + tick], color=K, lw=0.6) for a, b in zip(xs, xs[1:]): ax.text((a + b) / 2, y + 0.3, f"{round((b - a) * 1000)}", ha="center", va="bottom", fontsize=fs) def dim_v(ys, x, tick=0.25, fs=9): ax.plot([x, x], [ys[0], ys[-1]], color=K, lw=0.6) for y in ys: ax.plot([x - tick, x + tick], [y, y], color=K, lw=0.6) for a, b in zip(ys, ys[1:]): ax.text(x - 0.3, (a + b) / 2, f"{round((b - a) * 1000)}", ha="right", va="center", fontsize=fs, rotation=90) def guide(x0, y0, x1, y1): ax.plot([x0, x1], [y0, y1], color=K, lw=0.4, ls=(0, (6, 4)), zorder=1) xs = [0.0, 7.6, 9.8, 12.0, 19.0, 24.0] # 通り芯(建物の角・パントリー・キッチンの境) ys_l = [0.0, 8.6, 17.0] # 左:仕切り・建物の上の端 ys_r = [0.0, 6.0, 17.0, 19.2] # 右:厨房の下の端・洗い場の下の端 for x in xs: guide(x, -0.6, x, D + 1.6) for y in sorted(set(ys_l + ys_r)): guide(-1.6, y, W + 1.6, y) dim_h(xs, D + 1.2) dim_v(ys_l, -1.2) dim_v(ys_r, W + 1.6) ax.text(W + 2.3, D / 2, f"{round(D * 1000)}", rotation=90, ha="left", va="center", fontsize=10) dim_h([0.0, a0, a1, W], -2.6) dim_h([0.0, W], -3.5) ax.text(W / 2, -4.3, "単位 mm 点線は通り芯", ha="center", fontsize=9) ax.set_xlim(-3.0, W + 3.4); ax.set_ylim(-4.8, D + 2.4) fig.savefig(sys.argv[1], bbox_inches="tight", facecolor="white") print("ok", sys.argv[1])