いいね、そのまま実装レイヤまで降ろそう。
最小限だけど、そのまま動かしながら育てられる Flask+Shapely 構成で書くね。

from flask import Flask, request, jsonify
from shapely.geometry import Polygon
from shapely.ops import unary_union

app = Flask(__name__)


def rooms_to_polygons(rooms):
    polys = []
    for room in rooms:
        b = room["bounds"]
        x = b["x"]
        y = b["y"]
        w = b["width"]
        d = b["depth"]
        poly = Polygon([
            (x, y),
            (x + w, y),
            (x + w, y + d),
            (x, y + d)
        ])
        polys.append(poly)
    return polys


def compute_exterior_outline(rooms):
    polys = rooms_to_polygons(rooms)
    merged = unary_union(polys)
    # 単一ポリゴン前提(複数島になる場合は要拡張)
    outline_coords = list(merged.exterior.coords)
    # Shapely は最後に始点を重ねるので、閉ループの重複を落とす
    if len(outline_coords) > 1 and outline_coords[0] == outline_coords[-1]:
        outline_coords = outline_coords[:-1]
    return outline_coords


def outline_to_walls(outline_coords, total_height):
    walls = []
    n = len(outline_coords)
    for i in range(n):
        x1, y1 = outline_coords[i]
        x2, y2 = outline_coords[(i + 1) % n]
        walls.append({
            "from": [x1, y1],
            "to": [x2, y2],
            "height": total_height
        })
    return walls


def compute_total_height(rooms, floors=None):
    # とりあえず最初の room の height を floor_height とみなす
    if not rooms:
        return 0
    floor_height = rooms[0]["bounds"].get("height", 3)
    if floors is None:
        floors = 1
    return floor_height * floors


@app.route("/generate_exterior", methods=["POST"])
def generate_exterior():
    """
    期待する JSON 例:
    {
      "rooms": [
        {"id": "living", "bounds": {"x":0,"y":0,"width":6,"depth":4,"height":3}},
        {"id": "kitchen","bounds": {"x":6,"y":0,"width":3,"depth":3,"height":3}},
        {"id": "hall","bounds": {"x":0,"y":4,"width":9,"depth":1,"height":3}}
      ],
      "floors": 2,
      "openings": {
        "windows": [
          {
            "id": "w1",
            "room": "living",
            "direction": "south",
            "offset": 2.0,
            "width": 1.5
          }
        ],
        "doors": []
      }
    }
    """
    data = request.get_json()

    rooms = data.get("rooms", [])
    floors = data.get("floors", 1)
    openings = data.get("openings", {"windows": [], "doors": []})

    # ①〜③ 外周ポリゴン
    outline_coords = compute_exterior_outline(rooms)

    # ⑥ 高さ
    total_height = compute_total_height(rooms, floors)

    # ④ 壁セグメント
    walls = outline_to_walls(outline_coords, total_height)

    # ⑤ openings の投影はここで拡張(今はそのまま返す)
    exterior = {
        "outline": [[x, y] for (x, y) in outline_coords],
        "walls": walls,
        "openings": openings
    }

    return jsonify({"exterior": exterior})


if __name__ == "__main__":
    app.run(debug=True)

このコードは:

  • Structure Layer の rooms を受け取る
  • Union→外周ポリゴン→壁セグメント→高さ付与までやる
  • openings 投影は「フックだけ用意しておいて、あとで Roblox 側と相談しながら詰める」構造

にしてある。

次の一手としては:

  • openings を「room+direction→外周セグメント」にマッピングするロジック
  • Roblox に渡すための JSON 形状(座標系・単位)の固定

どっちから詰めたい?