# -*- coding: utf-8 -*- """仮の病院 h1:h0b の形に、全部の部屋の中(小部屋・扉・家具)と4つの道を足す。 2026-11-10 エイイチと決めた:病院のフロアと道を全部作る。部屋の中まで全部、道は 3ルート+裏口の道。 - 部屋・廊下・積む所と届け先の点は h0b と同じ(h0・h0b の JSON は変えない) - 部屋の中の並びは、外来・検査部・薬剤部・外来化学療法室・救急室の典型をコードで起こした仮置き - 道は 緑(救急室→検査部)・青 表(内科外来の処置室→検査部、大廊下を通る)・青 裏口(職員通路を通る)・赤(薬剤部→外来化学療法室) - 高さは配膳のベースに合わせる:部屋・小部屋の壁 2.4m 使い方:python3 make_hospital_h1.py 出力先 出力:hospital_h1.json・hospital_h1.png(全体)・hospital_h1_rooms.png(部屋ごとの寄り) """ import json import sys from pathlib import Path import make_hospital as mh WALL_H = 2.4 H = {"ベンチ": 0.45, "いす": 0.45, "机": 0.72, "ベッド": 0.6, "カウンター": 1.0, "棚": 1.8, "分析装置": 1.3, "リクライニングチェア": 1.0, "台": 0.9, "機器": 1.2, "精算機": 1.5, "置き台": 0.9, "台車": 1.0, "島": 1.0, "車いすの場所": 0.0} SUB = [] # 小部屋:部屋の中を壁で区切った所 FUR = [] # 家具 OPEN = [] # 部屋の壁のうち、廊下へ開いている所(壁を建てない) DOORS = [] # 部屋の壁の扉(廊下や別の部屋へ出る) INNER = [] # 部屋の中の通路(壁で囲まれていない、人やロボットが通る所) def sub(room, name, rect, doors=(), what=""): SUB.append({"room": room, "name": name, "rect": [round(v, 2) for v in rect], "doors": [{"at": [round(d[0], 2), round(d[1], 2)], "w": d[2]} for d in doors], "what": what}) def fur(room, kind, rect, name=""): FUR.append({"room": room, "kind": kind, "name": name or kind, "rect": [round(v, 2) for v in rect], "h": H[kind]}) def door(room, x, y, w, to, name=""): DOORS.append({"room": room, "at": [round(x, 2), round(y, 2)], "w": w, "to": to, "name": name}) def opening(room, side, a, b, to): OPEN.append({"room": room, "side": side, "span": [a, b], "to": to}) def inner(room, name, rect, w): INNER.append({"room": room, "name": name, "rect": [round(v, 2) for v in rect], "w": w}) def benches(room, x0, x1, y, n_rows, pitch=1.5, length=4.0, gap=1.0): """x0〜x1 に長さ length のベンチを並べ、y から pitch ごとに n_rows 列""" for r in range(n_rows): x = x0 while x + length <= x1 + 1e-6: fur(room, "ベンチ", (x, y + r * pitch, x + length, y + r * pitch + 0.45)) x += length + gap # ---------------- 内科外来(x0〜42, y31〜57.9)---------------- R = "内科外来" door(R, 12.0, 31.0, 1.6, "外来の大廊下", "内科外来の処置室(積む所)") door(R, 12.0, 57.9, 1.6, "外来の奥の職員通路", "内科外来の処置室(裏口)") sub(R, "処置室", (0, 31, 22, 46), [(12.0, 31.0, 1.6), (12.0, 46.0, 1.6), (22.0, 42.05, 1.2)], "採血・点滴・検体を出す所。南の扉が積む所(表)") fur(R, "置き台", (12.8, 38.4, 13.6, 40.4), "検体の置き台(ロボットに積む)") for i in range(4): fur(R, "ベッド", (0.4, 33 + i * 2.4, 2.4, 33.9 + i * 2.4), "処置ベッド") fur(R, "島", (3.5, 36.0, 10.5, 37.5), "看護師の作業台") for x0, x1 in ((1.0, 10.5), (13.5, 21.0)): fur(R, "カウンター", (x0, 44.6, x1, 45.4), "採血台") n = int((x1 - x0) // 1.9) for k in range(n): fur(R, "いす", (x0 + 0.5 + k * 1.9, 43.6, x0 + 1.0 + k * 1.9, 44.1), "採血のいす") inner(R, "処置室の裏の通路", (11.0, 46.0, 13.1, 57.9), 2.1) sub(R, "器材室", (0, 46, 11, 57.9), [(11.0, 52.0, 1.0)]) for i in range(3): fur(R, "棚", (0.4, 47.5 + i * 3.2, 9.5, 48.1 + i * 3.2)) sub(R, "検体の準備室", (13.1, 46, 22, 57.9), [(13.1, 52.0, 1.0)]) fur(R, "台", (14.0, 47.0, 21.0, 47.8), "検体の仕分け台") fur(R, "棚", (21.2, 48.5, 21.8, 56.5), "冷蔵庫") opening(R, "S", 22.0, 42.0, "外来の大廊下") inner(R, "内科の中待合", (22, 31, 42, 35.5), 4.5) benches(R, 23.0, 41.5, 32.2, 2, pitch=1.4, length=4.5, gap=0.5) for i in range(4): x0 = 22 + 5 * i sub(R, f"診察室{i + 1}", (x0, 35.5, x0 + 5, 41.0), [(x0 + 2.5, 35.5, 1.0), (x0 + 2.5, 41.0, 0.9)]) fur(R, "机", (x0 + 0.3, 38.0, x0 + 1.5, 40.6), "診察机") fur(R, "ベッド", (x0 + 3.6, 37.0, x0 + 4.5, 39.0), "診察ベッド") inner(R, "診察室の裏の通路", (22, 41.0, 42, 43.1), 2.1) for i, nm in enumerate(("内視鏡室1", "内視鏡室2")): x0 = 22 + 10 * i sub(R, nm, (x0, 43.1, x0 + 10, 51.0), [(x0 + 5, 43.1, 1.4)]) fur(R, "ベッド", (x0 + 3.0, 46.0, x0 + 5.0, 46.9), "検査ベッド") fur(R, "機器", (x0 + 6.0, 45.5, x0 + 7.2, 47.5), "内視鏡の機器") sub(R, "回復室", (22, 51.0, 42, 57.9), [(32.0, 51.0, 1.4), (32.0, 57.9, 1.4)]) for i in range(6): fur(R, "ベッド", (23.0 + i * 3.0, 55.0, 23.9 + i * 3.0, 57.0), "回復ベッド") # ---------------- ほかの外来(x42〜97.95, y31〜57.9)---------------- R = "ほかの外来" X0, X1 = 42.0, 97.95 depts = ["外科・整形外科", "小児科", "眼科・耳鼻科"] dw = (X1 - X0) / 3 opening(R, "S", X0, X1, "外来の大廊下") for j, dn in enumerate(depts): a, b = X0 + j * dw, X0 + (j + 1) * dw inner(R, f"{dn}の中待合", (a, 31, b, 36), 5.0) fur(R, "カウンター", (a + 0.6, 31.5, a + 4.0, 32.3), f"{dn}の受付") benches(R, a + 5.0, b - 0.5, 32.3, 2, pitch=1.4, length=4.0, gap=0.6) cw = dw / 3 for k in range(3): c0 = a + k * cw sub(R, f"{dn}の診察室{k + 1}", (c0, 36, c0 + cw, 41.5), [(c0 + cw / 2, 36, 1.0), (c0 + cw / 2, 41.5, 0.9)]) fur(R, "机", (c0 + 0.3, 38.4, c0 + 1.5, 41.1), "診察机") fur(R, "ベッド", (c0 + cw - 1.3, 37.5, c0 + cw - 0.4, 39.5), "診察ベッド") inner(R, f"{dn}の裏の通路", (a, 41.5, b, 43.6), 2.1) sub(R, f"{dn}の処置・検査室", (a, 43.6, b, 57.9), [((a + b) / 2, 43.6, 1.4), ((a + b) / 2, 57.9, 1.4)]) for k in range(4): fur(R, "ベッド", (a + 1.0 + k * 4.2, 54.5, a + 1.9 + k * 4.2, 56.5), "処置ベッド") fur(R, "島", (a + 5.0, 47.0, b - 5.0, 48.4), "作業台") fur(R, "棚", (a + 0.4, 44.5, a + 1.0, 51.5), "器材の棚") door(R, (a + b) / 2, 57.9, 1.4, "外来の奥の職員通路") # ---------------- 外来化学療法室(x100.05〜120, y31〜60)---------------- R = "外来化学療法室" door(R, 104.0, 31.0, 1.6, "外来の大廊下", "外来化学療法室の受付(届け先)") fur(R, "カウンター", (105.2, 31.8, 109.0, 32.6), "受付(薬を受け取る)") benches(R, 111.8, 119.5, 32.0, 2, pitch=1.4, length=3.5, gap=0.5) fur(R, "島", (106.0, 40.0, 114.0, 43.0), "ナースステーション") for i in range(9): fur(R, "リクライニングチェア", (100.6, 36.0 + i * 2.3, 102.2, 36.8 + i * 2.3), "点滴のいす") for i in range(6): fur(R, "ベッド", (117.6, 35.5 + i * 2.6, 119.6, 36.4 + i * 2.6), "点滴のベッド") sub(R, "準備室", (106.0, 52.0, 114.0, 60.0), [(110.0, 52.0, 1.2)]) fur(R, "台", (107.0, 58.6, 113.0, 59.4), "点滴の準備台") sub(R, "トイレ", (114.5, 53.0, 120.0, 60.0), [(114.5, 55.0, 1.0)]) # ---------------- 薬剤部(x0〜23, y0〜13)---------------- R = "薬剤部" door(R, 13.0, 13.0, 1.6, "職員の中廊下", "薬剤部の払い出し口(積む所)") fur(R, "置き台", (14.2, 11.8, 16.5, 12.6), "払い出し台(ロボットに積む)") sub(R, "無菌調製室", (0, 0, 7, 6), [(7.0, 3.0, 0.9)], "抗がん剤を調製する所") fur(R, "機器", (0.5, 0.5, 2.0, 4.0), "安全キャビネット") sub(R, "前室", (7, 0, 10, 6), [(8.5, 6.0, 0.9)]) fur(R, "台", (11.0, 2.0, 21.0, 3.0), "調剤台") fur(R, "台", (11.0, 5.0, 21.0, 6.0), "調剤台") fur(R, "機器", (19.5, 0.4, 22.6, 1.6), "錠剤の分包機") fur(R, "棚", (0.4, 7.5, 9.0, 8.1), "薬品棚") fur(R, "棚", (0.4, 9.8, 9.0, 10.4), "薬品棚") fur(R, "机", (17.0, 8.0, 22.0, 9.5), "事務机") # ---------------- お薬の窓口(x0〜23, y15.1〜27)---------------- R = "お薬の窓口" opening(R, "N", 1.0, 17.0, "外来の大廊下") door(R, 6.0, 15.1, 0.9, "職員の中廊下") fur(R, "カウンター", (1.0, 18.5, 16.5, 19.3), "お薬の窓口") benches(R, 2.0, 16.5, 21.0, 3, pitch=1.6, length=4.5, gap=0.5) # 2026-11-11 トイレを足した(患者が待ち時間に行く所。大廊下に人がほとんど歩かなかったため)。大廊下から入る sub(R, "トイレ(西)", (17.5, 15.1, 22.95, 27.0), [(20.2, 27.0, 1.6)], "男女・多目的。大廊下に面する") # ---------------- 総合受付・会計・待合(x25.1〜70, y15.1〜27。玄関の通り x44〜52 をはさむ)---------------- R = "総合受付・会計・待合" opening(R, "N", 25.1, 70.0, "外来の大廊下") # 玄関の通り(x44〜52)は部屋の外枠の内側なので壁は建たない。x70 の東の辺は生理検査との壁なので開けない(2026-11-10 依頼143 で見つかった間違いを直した) door(R, 32.5, 15.1, 0.9, "職員の中廊下") # 2026-11-11 エレベーターを足した(ほかの階との行き来。病棟から検査に来る患者・見舞いの人・職員)。大廊下に面したホール sub(R, "エレベーター(3基)", (25.1, 15.1, 31.0, 21.0), [(26.1, 21.0, 1.1), (28.05, 21.0, 1.1), (30.0, 21.0, 1.1)], "ほかの階との出入り口") inner(R, "エレベーターホール", (25.1, 21.0, 31.0, 27.0), 6.0) door(R, 58.0, 15.1, 0.9, "職員の中廊下") fur(R, "カウンター", (32.0, 16.6, 43.6, 17.4), "総合受付") fur(R, "カウンター", (52.4, 16.6, 63.0, 17.4), "会計") for i in range(4): fur(R, "精算機", (64.0 + i * 1.4, 15.5, 65.0 + i * 1.4, 16.3), "自動精算機") benches(R, 31.8, 43.0, 19.5, 4, pitch=1.6, length=3.5, gap=0.5) benches(R, 53.0, 69.5, 19.5, 4, pitch=1.6, length=4.0, gap=0.6) # ---------------- 倉庫(x25.1〜44, y0〜13)---------------- R = "倉庫" door(R, 35.0, 13.0, 1.8, "職員の中廊下") for i in range(4): fur(R, "棚", (26.0, 1.0 + i * 2.4, 42.0, 1.6 + i * 2.4)) fur(R, "台車", (26.0, 10.6, 32.0, 12.4), "台車置き場") # ---------------- 階段・設備(h0 で部屋のなかった所)---------------- EXTRA_ROOMS = [("階段・設備(西)", 23.0, 0.0, 25.1, 13.0, "その他"), ("階段・設備(東)", 98.0, 0.0, 100.1, 13.0, "その他")] # ---------------- 正面玄関(x44〜52, y0〜13)---------------- R = "正面玄関" opening(R, "N", 44.0, 52.0, "職員の中廊下・玄関の通り") sub(R, "風除室", (44, 0, 52, 4), [(48.0, 0.0, 3.0), (48.0, 4.0, 3.0)], "自動扉が2つ") fur(R, "台", (44.3, 5.0, 45.3, 10.0), "車いす置き場") fur(R, "カウンター", (50.2, 6.0, 51.7, 9.0), "案内") # ---------------- 医事・事務(x52〜70, y0〜13)---------------- R = "医事・事務" door(R, 55.0, 13.0, 0.9, "職員の中廊下") door(R, 67.0, 13.0, 0.9, "職員の中廊下") fur(R, "机", (54.0, 3.0, 66.0, 4.6), "事務机の島") fur(R, "机", (54.0, 6.5, 66.0, 8.1), "事務机の島") fur(R, "机", (54.0, 9.6, 66.0, 11.2), "事務机の島") fur(R, "棚", (68.4, 1.0, 69.6, 10.0), "書庫") # ---------------- 中央検査室(x70〜98, y0〜13)---------------- R = "中央検査室" door(R, 80.0, 13.0, 1.6, "職員の中廊下", "検査部の検体受付(届け先)") door(R, 94.0, 13.0, 0.9, "職員の中廊下") fur(R, "置き台", (81.3, 11.6, 85.0, 12.4), "検体受付の台(ロボットから受け取る)") fur(R, "台", (78.0, 8.5, 84.0, 9.5), "検体の仕分け台") fur(R, "分析装置", (72.0, 1.0, 96.0, 2.2), "分析装置(生化学・免疫)") fur(R, "分析装置", (86.0, 5.0, 96.0, 6.4), "分析装置(血液)") fur(R, "分析装置", (86.0, 8.5, 96.0, 9.9), "検体の搬送ライン") fur(R, "棚", (70.4, 8.0, 71.2, 12.0), "冷蔵庫") fur(R, "机", (72.0, 5.0, 78.0, 6.2), "検査技師の机") # ---------------- 生理検査(x70〜98, y15.1〜27)---------------- R = "生理検査" opening(R, "N", 70.0, 98.0, "外来の大廊下") inner(R, "生理検査の待合", (70, 21.9, 98, 27), 5.1) fur(R, "カウンター", (70.3, 22.8, 71.1, 25.8), "生理検査の受付") benches(R, 76.0, 97.5, 24.2, 2, pitch=1.4, length=4.0, gap=0.6) names = ["心電図", "心エコー", "腹部エコー", "肺機能", "脳波"] cw = 28.0 / 6 # 2026-11-11 東の端(もとは血圧脈波の検査室)をトイレ(東)にした。生理検査の待合から入る sub(R, "トイレ(東)", (70 + 5 * cw, 15.1, 98.0, 21.9), [(70 + 5.5 * cw, 21.9, 1.6)], "男女・多目的") for k, nm in enumerate(names): c0 = 70 + k * cw sub(R, f"{nm}の検査室", (c0, 15.1, c0 + cw, 21.9), [(c0 + cw / 2, 21.9, 1.0)]) fur(R, "ベッド", (c0 + 1.0, 16.0, c0 + 3.0, 16.9), "検査ベッド") fur(R, "機器", (c0 + 3.4, 16.0, c0 + 4.3, 17.2), "検査の機器") # ---------------- 救急室(x100.1〜120, y0〜27)---------------- R = "救急室" door(R, 100.1, mh.STAFF_C, 1.6, "職員の中廊下", "救急室の処置室(積む所)") door(R, 110.0, 27.0, 1.6, "外来の大廊下", "歩いて来た人の入口") door(R, 120.0, 5.0, 2.4, "外(救急車)", "救急車の入口") fur(R, "置き台", (100.6, 15.4, 102.6, 16.2), "検体の置き台(ロボットに積む)") sub(R, "初療室", (110, 0, 120, 6), [(115.0, 6.0, 1.8)], "重い人を最初に診る所") fur(R, "ベッド", (113.5, 2.5, 116.5, 3.5), "初療のベッド") sub(R, "X線室", (100.1, 0, 108, 6), [(104.0, 6.0, 1.4)]) fur(R, "機器", (102.0, 1.5, 106.0, 3.5), "X線の機器") for i in range(6): fur(R, "ベッド", (117.5, 8.0 + i * 2.4, 119.5, 8.9 + i * 2.4), "処置ベッド") fur(R, "島", (106.0, 11.0, 111.0, 15.0), "ナースステーション") benches(R, 101.0, 109.5, 22.0, 2, pitch=1.4, length=4.0, gap=0.5) # 車いすの場所(2026-11-10 エイイチと決めた):待合ごとに、通路に面したベンチの列の端に 1.5m 四方を足す。 # 指針(東京都 UD チェックリスト:待合に車椅子使用者が利用しやすいスペースの確保に配慮)にならう。列の端に置く形は仮置き。 # 「ない」条件(車いすの人がいすの間・通路で待つ)は試算・Unity の条件で選ぶ。床の印だけで高さはない WC = 1.5 WAITING = [ # 待合の名前, 部屋, 待合の範囲, 通路(開いている側)の向き, 数 ("内科の中待合", "内科外来", (22, 31, 42, 35.5), "S", 1), ("外科・整形外科の中待合", "ほかの外来", (42, 31, 60.65, 36), "S", 1), ("小児科の中待合", "ほかの外来", (60.65, 31, 79.3, 36), "S", 1), ("眼科・耳鼻科の中待合", "ほかの外来", (79.3, 31, 97.95, 36), "S", 1), ("化学療法の受付・待合", "外来化学療法室", (109.5, 31, 120, 35), "S", 1), ("お薬の窓口の待合", "お薬の窓口", (0, 19.3, 17.5, 27), "N", 1), ("総合受付の待合", "総合受付・会計・待合", (31.0, 17.4, 44, 27), "N", 2), ("会計の待合", "総合受付・会計・待合", (52, 17.4, 70, 27), "N", 2), ("生理検査の待合", "生理検査", (70, 21.9, 98, 27), "N", 1), ("救急の待合", "救急室", (100.1, 20, 120, 27), "N", 1), ] def place_wheelchair_spaces(): placed = [] for wname, room, (zx0, zy0, zx1, zy1), side, n in WAITING: benches_in = [f for f in FUR if f["kind"] == "ベンチ" and f["room"] == room and zx0 <= f["rect"][0] and f["rect"][2] <= zx1 and zy0 <= f["rect"][1] and f["rect"][3] <= zy1] cands = [] for b in benches_in: bx0, by0, bx1, by1 = b["rect"] for x0 in (bx1 + 0.15, bx0 - 0.15 - WC): for y0 in (by0 - (WC - (by1 - by0)) / 2,): cands.append((x0, y0, x0 + WC, y0 + WC)) doors = [d["at"] for d in DOORS] + [dd["at"] for sb in SUB for dd in sb["doors"]] stops = [p for pts in INSIDE.values() for p in pts] ok = [] for c in cands: if c[0] < zx0 or c[1] < zy0 or c[2] > zx1 or c[3] > zy1: continue if any(c[0] < f["rect"][2] + 0.05 and c[2] > f["rect"][0] - 0.05 and c[1] < f["rect"][3] + 0.05 and c[3] > f["rect"][1] - 0.05 for f in FUR): continue if any(c[0] - 1.0 < x < c[2] + 1.0 and c[1] - 1.0 < y < c[3] + 1.0 for x, y in doors + stops): continue if any(c[0] < q[2] and c[2] > q[0] and c[1] < q[3] and c[3] > q[1] for q in placed): continue # 通路(開いている側)に近いほどよい。同じなら外側の端 edge = (c[1] - zy0) if side == "S" else (zy1 - c[3]) ok.append((round(edge, 2), -abs((c[0] + c[2]) / 2 - (zx0 + zx1) / 2), c)) ok.sort(key=lambda t: (t[0], t[1])) got = 0 for _, _, c in ok: if got >= n: break if any(c[0] < q[2] and c[2] > q[0] and c[1] < q[3] and c[3] > q[1] for q in placed): continue fur(room, "車いすの場所", c, f"車いすの場所({wname})") placed.append(c) got += 1 if got < n: print("車いすの場所が置けない:", wname, got, "/", n) # ロボットが部屋に入って止まる所(2026-11-10 エイイチ:搬送ロボットは部屋の中にも入っていく前提) # 扉の点(廊下の側)から、部屋の中の台の前で止まる点までの道。縦横だけ INSIDE = { "内科外来の処置室": [(12.0, 31.0), (12.0, 39.4)], "内科外来の処置室(裏口)": [(12.0, 57.9), (12.0, 46.0), (12.0, 39.4)], "薬剤部の払い出し口": [(13.0, 13.0), (13.0, 11.0), (15.35, 11.0)], "外来化学療法室の受付": [(104.0, 31.0), (104.0, 33.4), (107.1, 33.4)], "検査部の検体受付": [(80.0, 13.0), (80.0, 10.55), (83.15, 10.55)], "救急室の処置室": [(100.1, 14.05), (101.6, 14.05), (101.6, 14.75)], } ROBOT_R = 0.35 # ロボットの半径の目安(W3 の幅 0.7m の半分) def check_inside(): """部屋の中の道が家具に当たらないか(ロボットの半径+0.1m の余白)""" probs = [] for name, pts in INSIDE.items(): for (ax, ay), (bx, by) in zip(pts, pts[1:]): lo_x, hi_x, lo_y, hi_y = min(ax, bx), max(ax, bx), min(ay, by), max(ay, by) for f in FUR: x0, y0, x1, y1 = f["rect"] m = ROBOT_R + (0.0 if f["kind"] == "置き台" else 0.1) if lo_x - m < x1 and hi_x + m > x0 and lo_y - m < y1 and hi_y + m > y0: probs.append(f"部屋の中の道が家具に当たる:{name} {f['name']}") return probs # だれがどこに入るか(2026-11-10 エイイチ:患者・看護師・医師などの入る所・入らない所・座る所を分ける) # 役割ごとに「座る」(座る・寝る・腰を落ち着ける所)/「入る」(通る・立ち寄る)。書いていない役割は入らない ROLES = ["外来の患者", "化学療法の患者", "救急の患者", "看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"] S_, E_ = "座る", "入る" ACCESS = [ # 名前, 部屋, [rect...], {役割: 座る|入る}, 補足 ("内科の処置室", "内科外来", [(0, 31, 22, 46)], {"外来の患者": S_, "看護師": E_, "医師": E_, "検査技師": E_, "ロボット": E_}, "患者は採血のいす・処置ベッド。ロボットは台の前まで"), ("内科の処置室の裏の通路", "内科外来", [(11, 46, 13.1, 57.9)], {"看護師": E_, "医師": E_, "検査技師": E_, "ロボット": E_}, ""), ("内科の器材室", "内科外来", [(0, 46, 11, 57.9)], {"看護師": E_}, ""), ("内科の検体の準備室", "内科外来", [(13.1, 46, 22, 57.9)], {"看護師": E_, "検査技師": E_}, ""), ("内科の中待合", "内科外来", [(22, 31, 42, 35.5)], {"外来の患者": S_, "看護師": E_}, "看護師が呼び入れる"), ("内科の診察室", "内科外来", [(22, 35.5, 42, 41)], {"外来の患者": S_, "看護師": E_, "医師": S_}, ""), ("内科の診察室の裏の通路", "内科外来", [(22, 41, 42, 43.1)], {"外来の患者": E_, "看護師": E_, "医師": E_}, "患者は内視鏡室へ行く人だけ、看護師が付き添って通る(2026-11-11 案A)"), ("内視鏡室", "内科外来", [(22, 43.1, 42, 51)], {"外来の患者": E_, "看護師": E_, "医師": E_}, "検査を受ける患者だけ"), ("回復室", "内科外来", [(22, 51, 42, 57.9)], {"外来の患者": S_, "看護師": E_}, "検査を受けた患者だけ。ベッドで休む"), ("ほかの外来の中待合", "ほかの外来", [(42, 31, 97.95, 36)], {"外来の患者": S_, "看護師": E_, "事務": E_}, "各科の受付は事務"), ("ほかの外来の診察室", "ほかの外来", [(42, 36, 97.95, 41.5)], {"外来の患者": S_, "看護師": E_, "医師": S_}, ""), ("ほかの外来の裏の通路", "ほかの外来", [(42, 41.5, 97.95, 43.6)], {"外来の患者": E_, "看護師": E_, "医師": E_}, "患者は処置・検査室へ行く人だけ、看護師が付き添って通る(2026-11-11 案A)"), ("ほかの外来の処置・検査室", "ほかの外来", [(42, 43.6, 97.95, 57.9)], {"外来の患者": E_, "看護師": E_, "医師": E_}, "処置を受ける患者だけ"), ("化学療法の受付・待合", "外来化学療法室", [(100.05, 31, 120, 35)], {"化学療法の患者": S_, "看護師": E_, "薬剤師": E_, "ロボット": E_}, "ロボットは受付の台の前まで"), ("化学療法の点滴の場所", "外来化学療法室", [(100.05, 35, 120, 52), (100.05, 52, 106, 60), (114, 52, 120, 53), (114, 53, 114.5, 60)], {"化学療法の患者": S_, "看護師": E_, "医師": E_, "薬剤師": E_}, "患者は点滴のいす・ベッドで数時間"), ("化学療法の準備室", "外来化学療法室", [(106, 52, 114, 60)], {"看護師": E_, "薬剤師": E_}, ""), ("化学療法のトイレ", "外来化学療法室", [(114.5, 53, 120, 60)], {"化学療法の患者": E_}, "点滴をつけたまま行く"), ("薬剤部", "薬剤部", [(0, 0, 23, 13)], {"薬剤師": S_, "ロボット": E_}, "ロボットは払い出し台の前まで"), ("お薬の窓口の内側", "お薬の窓口", [(0, 15.1, 17.5, 19.3)], {"薬剤師": S_}, ""), ("お薬の窓口の待合", "お薬の窓口", [(0, 19.3, 17.5, 27)], {"外来の患者": S_}, ""), ("トイレ(西)", "お薬の窓口", [(17.5, 15.1, 22.95, 27)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_}, "付き添いも入る"), ("エレベーター", "総合受付・会計・待合", [(25.1, 15.1, 31, 21)], {r: E_ for r in ROLES if r != "ロボット"}, "ほかの階との出入り口(ここで現れ・消える)"), ("エレベーターホール", "総合受付・会計・待合", [(25.1, 21, 31, 27)], {r: E_ for r in ROLES if r != "ロボット"}, ""), ("総合受付・会計の内側", "総合受付・会計・待合", [(31, 15.1, 44, 17.4), (52, 15.1, 70, 17.4)], {"事務": S_}, ""), ("総合受付・会計の待合", "総合受付・会計・待合", [(31, 17.4, 44, 27), (52, 17.4, 70, 27)], {"外来の患者": S_, "化学療法の患者": S_}, ""), ("倉庫", "倉庫", [(25.1, 0, 44, 13)], {"看護師": E_, "事務": E_}, ""), ("正面玄関", "正面玄関", [(44, 0, 52, 13)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_, "事務": E_}, ""), ("医事・事務", "医事・事務", [(52, 0, 70, 13)], {"事務": S_}, ""), ("中央検査室", "中央検査室", [(70, 0, 98, 13)], {"検査技師": S_, "ロボット": E_}, "ロボットは検体受付の台の前まで"), ("生理検査の待合", "生理検査", [(70, 21.9, 98, 27)], {"外来の患者": S_, "検査技師": E_}, ""), ("トイレ(東)", "生理検査", [(70 + 5 * 28.0 / 6, 15.1, 98, 21.9)], {"外来の患者": E_, "化学療法の患者": E_, "救急の患者": E_}, "付き添いも入る"), ("生理検査の検査室", "生理検査", [(70, 15.1, 70 + 5 * 28.0 / 6, 21.9)], {"外来の患者": E_, "検査技師": S_, "医師": E_}, "患者は検査ベッドに寝る"), ("救急の待合", "救急室", [(100.1, 20, 120, 27)], {"救急の患者": S_, "看護師": E_}, "歩いて来た人"), ("救急の処置の場所", "救急室", [(100.1, 6, 120, 20), (108, 0, 110, 6)], {"救急の患者": S_, "看護師": E_, "医師": E_, "ロボット": E_}, "患者は処置ベッド。ロボットは検体の台の前まで"), ("初療室", "救急室", [(110, 0, 120, 6)], {"救急の患者": E_, "看護師": E_, "医師": E_}, "救急車で来た人"), ("X線室", "救急室", [(100.1, 0, 108, 6)], {"救急の患者": E_, "医師": E_, "検査技師": E_}, ""), ("外来の大廊下", "廊下", [(0, 27, 120, 31)], {r: E_ for r in ROLES}, ""), ("玄関の通り", "廊下", [(44, 15.1, 52, 27)], {r: E_ for r in ROLES if r != "ロボット"}, ""), ("職員の中廊下", "廊下", [(0, 13, 100.1, 15.1)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, "患者は正面玄関の前(x44〜52)を横切るだけ"), ("西の縦廊下", "廊下", [(22.95, 15.1, 25.05, 27)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, ""), ("東の縦廊下", "廊下", [(97.95, 15.1, 100.05, 27)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, ""), ("外来の奥の職員通路", "廊下", [(0, 57.9, 100.05, 60), (97.95, 31, 100.05, 57.9)], {r: E_ for r in ["看護師", "医師", "薬剤師", "検査技師", "事務", "ロボット"]}, "東の縦廊下(北)を含む"), ] CROSS_ONLY = {"職員の中廊下": {"外来の患者": (44, 13, 52, 15.1), "化学療法の患者": (44, 13, 52, 15.1)}} def check(data): """家具が部屋からはみ出していないか・道や扉の前をふさいでいないかを確かめる""" probs = [] rooms = {r["name"]: r["rect"] for r in data["layout"]["rooms"]} for f in FUR: x0, y0, x1, y1 = rooms[f["room"]] a = f["rect"] if a[0] < x0 - 1e-6 or a[1] < y0 - 1e-6 or a[2] > x1 + 1e-6 or a[3] > y1 + 1e-6: probs.append(f"はみ出し:{f['room']} {f['name']} {a}") for d in DOORS + [{"room": s["room"], "at": dd["at"], "w": dd["w"], "name": s["name"]} for s in SUB for dd in s["doors"]]: x, y = d["at"] for f in FUR: a = f["rect"] if a[0] - 0.6 < x < a[2] + 0.6 and a[1] - 0.6 < y < a[3] + 0.6 and f["kind"] != "置き台": probs.append(f"扉の前に家具:{d['room']} {d.get('name','')} {d['at']} ← {f['name']}") return probs def main(out_dir): place_wheelchair_spaces() mh.apply_variant("h0b") edges = mh.build_graph("h0b") out = Path(out_dir) out.mkdir(parents=True, exist_ok=True) rooms = [{"name": n, "rect": [x0, y0, x1, y1], "kind": k} for n, x0, y0, x1, y1, k in mh.ROOMS + EXTRA_ROOMS] routes = {} plan = {"green": ("救急室の処置室", "検査部の検体受付", "検体(救急・病棟)"), "blue": ("内科外来の処置室", "検査部の検体受付", "採血管・PCR/抗原・尿"), "blue_back": ("内科外来の処置室(裏口)", "検査部の検体受付", "採血管・PCR/抗原・尿"), "red": ("薬剤部の払い出し口", "外来化学療法室の受付", "抗がん剤・前投薬")} for key, (a, b, carries) in plan.items(): d, path = mh.shortest(edges, mh.POINTS[a][:2], mh.POINTS[b][:2]) hall_m = sum(mh.crowded_len(p[0], q[0]) for p, q in zip(path, path[1:]) if mh.on_hall(p, q)) ia, ib = INSIDE[a][::-1], INSIDE[b] full = ia[:-1] + path + ib[1:] routes[key] = {"from": a, "to": b, "carries": carries, "distance_m": round(mh.true_len(full), 2), "corridor_m": round(mh.true_len(path), 2), "in_room_m": {"from": round(mh.true_len(INSIDE[a]), 2), "to": round(mh.true_len(INSIDE[b]), 2)}, "crowded_hall_m": round(hall_m, 2), "path": [[round(v, 2) for v in p] for p in full]} base = json.loads(Path(__file__).with_name("hospital_h0b.json").read_text(encoding="utf-8")) data = { "meta": dict(base["meta"], name="仮の病院 h1(外来の階・部屋の中まで)", variant="h1", schema_version="hospital-layout/0.2", based_on="hospital_h0b.json(部屋・廊下・点は同じ)", interior_basis="仮置き(典型の並びをコードで起こした。実在の病院ではない)", wall_h=WALL_H), "layout": dict(base["layout"], rooms=rooms), "interior": {"subrooms": SUB, "furniture": FUR, "openings": OPEN, "doors": DOORS, "inner_passages": INNER}, "routes": routes, "distances": {k: round(v + mh.true_len(INSIDE[k.split("|")[0]]) + mh.true_len(INSIDE[k.split("|")[1]]), 2) for k, v in base["distances"].items()}, "stops": {n: [list(p) for p in pts] for n, pts in INSIDE.items()}, "access": {"roles": ROLES, "levels": {"座る": "座る・寝る・腰を落ち着ける", "入る": "通る・立ち寄る"}, "zones": [{"name": n, "room": r, "rects": [list(x) for x in rs], "who": w, "note": nt} for n, r, rs, w, nt in ACCESS], "cross_only": {k: {r: list(v) for r, v in d.items()} for k, d in CROSS_ONLY.items()}, "basis": "仮置き(典型の動き方。病院ごとに違う)"}, } data["meta"]["distance"] = "部屋の中の台の前で止まる点から、扉 → 廊下の中心線 → 扉 → 台の前まで(縦横だけ)。h0・h0b は扉から扉まで" probs = check(data) + check_inside() (out / "hospital_h1.json").write_text(json.dumps(data, ensure_ascii=False, indent=1), encoding="utf-8") draw(data, out / "hospital_h1.png") for k, r in routes.items(): print(k, r["from"], "→", r["to"], r["distance_m"], "m(廊下", r["corridor_m"], "+部屋の中", r["in_room_m"], ")") print("小部屋", len(SUB), "家具", len(FUR), "扉", len(DOORS) + sum(len(s["doors"]) for s in SUB)) print("確かめ:", "問題なし" if not probs else "\n".join(probs)) views = [("内科外来とお薬の窓口・総合受付の西", (-0.5, 12.5, 44.5, 60.5)), ("ほかの外来", (41.5, 26.0, 98.5, 60.5)), ("外来化学療法室と救急室", (96.0, -0.5, 120.5, 60.5)), ("薬剤部・倉庫・正面玄関(職員の中廊下の西)", (-0.5, -0.5, 53.0, 28.0)), ("医事・中央検査室・生理検査(職員の中廊下の東)", (51.5, -0.5, 101.0, 28.0))] files = [] for i, (t, v) in enumerate(views): f = out / f"_view{i}.png" draw(data, f, view=v, title=t) files.append(f) try: from PIL import Image ims = [Image.open(f) for f in files] wmax = max(im.width for im in ims) canvas = Image.new("RGB", (wmax, sum(im.height for im in ims) + 40 * len(ims)), "white") y = 0 for im in ims: canvas.paste(im, (0, y)) y += im.height + 40 canvas.save(out / "hospital_h1_rooms.png") for f in files: f.unlink() except ImportError: pass def draw(data, path, view=None, title=None): import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from matplotlib import font_manager from matplotlib.patches import Rectangle for f in font_manager.findSystemFonts(): if "NotoSansCJK-Regular" in f or "NotoSansJP" in f: font_manager.fontManager.addfont(f) plt.rcParams["font.family"] = font_manager.FontProperties(fname=f).get_name() break col = {"外来": "#f3f5f8", "届け先": "#e6effc", "出発": "#e8f5eb", "混む所": "#fff4e3", "その他": "#f5f5f5"} fcol = {"ベンチ": "#c9a46a", "いす": "#c9a46a", "机": "#9aa6b2", "ベッド": "#8fc7b8", "カウンター": "#7f8c99", "棚": "#6b7480", "分析装置": "#5d6b7a", "リクライニングチェア": "#8fc7b8", "台": "#9aa6b2", "機器": "#5d6b7a", "精算機": "#5d6b7a", "置き台": "#e0472f", "台車": "#9aa6b2", "島": "#7f8c99", "車いすの場所": "#f2b705"} rc = {"green": "#2e9d4f", "blue": "#2f6fd6", "blue_back": "#6c4fd6", "red": "#d24b3e"} big = view is None fig, ax = plt.subplots(figsize=(17, 9.4) if big else (10, 6.6), dpi=150) for r in data["layout"]["rooms"]: x0, y0, x1, y1 = r["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc=col[r["kind"]], ec="#3c4650", lw=1.6)) for c in data["layout"]["corridors"]: x0, y0, x1, y1 = c["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="#ffffff", ec="none")) for p in data["interior"]["inner_passages"]: x0, y0, x1, y1 = p["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="#fbfbfb", ec="none")) for s in data["interior"]["subrooms"]: x0, y0, x1, y1 = s["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="none", ec="#3c4650", lw=1.0)) for f in data["interior"]["furniture"]: x0, y0, x1, y1 = f["rect"] ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc=fcol[f["kind"]], ec="none")) # 開いている壁と扉は、壁の線の上に白い線を引いて見せる rooms = {r["name"]: r["rect"] for r in data["layout"]["rooms"]} for o in data["interior"]["openings"]: x0, y0, x1, y1 = rooms[o["room"]] a, b = o["span"] if o["side"] in "NS": y = y1 if o["side"] == "N" else y0 ax.plot([a, b], [y, y], color="#ffffff", lw=2.6, solid_capstyle="butt") else: x = x1 if o["side"] == "E" else x0 ax.plot([x, x], [a, b], color="#ffffff", lw=2.6, solid_capstyle="butt") alld = [(d["at"], d["w"]) for d in data["interior"]["doors"]] + \ [(dd["at"], dd["w"]) for s in data["interior"]["subrooms"] for dd in s["doors"]] for (x, y), w in alld: # 扉がどちら向きの壁にあるか:近くの縦の壁か横の壁かで決める horiz = any(abs(y - r[1]) < 1e-3 or abs(y - r[3]) < 1e-3 for r in list(rooms.values()) + [s["rect"] for s in data["interior"]["subrooms"]]) vert = any(abs(x - r[0]) < 1e-3 or abs(x - r[2]) < 1e-3 for r in list(rooms.values()) + [s["rect"] for s in data["interior"]["subrooms"]]) if horiz and not (vert and not horiz): ax.plot([x - w / 2, x + w / 2], [y, y], color="#ffffff", lw=3.0, solid_capstyle="butt") else: ax.plot([x, x], [y - w / 2, y + w / 2], color="#ffffff", lw=3.0, solid_capstyle="butt") off = {"green": 0.35, "blue": 0.0, "blue_back": 0.0, "red": -0.35} for k, r in data["routes"].items(): xs = [p[0] for p in r["path"]] ys = [p[1] + off[k] for p in r["path"]] ax.plot(xs, ys, color=rc[k], lw=2.4 if big else 3.0, solid_capstyle="round", alpha=0.95) ax.plot(xs[0], ys[0], "o", color=rc[k], ms=8) ax.plot(xs[-1], ys[-1], "s", color=rc[k], ms=8) fs = 10 if big else 9 for r in data["layout"]["rooms"]: x0, y0, x1, y1 = r["rect"] if view and not (view[0] <= (x0 + x1) / 2 <= view[2] and view[1] <= (y0 + y1) / 2 <= view[3]): continue if x1 - x0 < 3: # 階段・設備の細い所は斜線だけで名前は出さない ax.add_patch(Rectangle((x0, y0), x1 - x0, y1 - y0, fc="none", ec="#b9c0c7", hatch="///", lw=0)) continue ax.text(x0 + 0.5, y1 - 0.5, r["name"], ha="left", va="top", fontsize=fs, color="#1f2933", weight="bold", bbox=dict(fc="white", ec="none", alpha=0.8, pad=1)) if not big: for s in data["interior"]["subrooms"]: x0, y0, x1, y1 = s["rect"] if view[0] <= (x0 + x1) / 2 <= view[2] and view[1] <= (y0 + y1) / 2 <= view[3]: nm = s["name"] if nm.endswith("の検査室"): nm = nm[:-4] elif "の" in nm and len(nm) > 6: nm = nm.split("の")[-1] ax.text((x0 + x1) / 2, (y0 + y1) / 2, nm, ha="center", va="center", fontsize=7.5 if x1 - x0 > 5.5 else 6.5, color="#3c4650", bbox=dict(fc="white", ec="none", alpha=0.7, pad=0.5)) for f in data["interior"]["furniture"]: if f["kind"] == "置き台": x0, y0, x1, y1 = f["rect"] if view[0] <= x0 <= view[2] and view[1] <= y0 <= view[3]: ax.annotate(f["name"], ((x0 + x1) / 2, (y0 + y1) / 2), xytext=(8, 10), textcoords="offset points", fontsize=8, color="#b8321e") ax.add_patch(Rectangle((0, 0), mh.W, mh.D, fc="none", ec="#1f2933", lw=2.2)) if big: nm = {"green": "緑", "blue": "青(表)", "blue_back": "青(裏口)", "red": "赤"} for i, (k, r) in enumerate(data["routes"].items()): ax.text(1 + (i % 2) * 52, -3 - (i // 2) * 2.3, "●", color=rc[k], fontsize=12, va="center") ax.text(3 + (i % 2) * 52, -3 - (i // 2) * 2.3, f"{nm[k]} {r['from']} → {r['to']} {r['distance_m']:.0f}m", fontsize=10.5, va="center") ax.text(1, -8.2, "■ 赤い小さな台=ロボットに積む所・受け取る所 茶=待合のベンチ 黄=車いすの場所 緑=ベッド・点滴のいす 灰=机・台・機器・棚", fontsize=9.5, color="#555", va="center") ax.set_xlim(-1, mh.W + 1) ax.set_ylim(-9.5, mh.D + 1) else: ax.set_xlim(view[0], view[2]) ax.set_ylim(view[1], view[3]) ax.set_aspect("equal") ax.axis("off") ax.set_title(title or f"{data['meta']['name']}(120m×60m)と搬送の4つの道", fontsize=15 if big else 12, loc="left") fig.savefig(path, bbox_inches="tight", facecolor="white") plt.close(fig) if __name__ == "__main__": main(sys.argv[1] if len(sys.argv) > 1 else ".")