"""
Render a floorplan JSON as an SVG or PNG image.
Supports: plot boundary, buildable boundary, rooms (color-coded by type),
doors, windows, dimension labels, north arrow.
Usage:
python render_floorplan.py --input floorplan.json --output floorplan.svg
python render_floorplan.py --input floorplan.json --output floorplan.png
"""
import json
import argparse
from typing import List, Dict, Any, Tuple
ROOM_COLORS = {
"living": "#E8F4FD", "bedroom": "#FFF3E0", "master_bedroom": "#FFE0B2",
"kitchen": "#F3E5F5", "dining": "#E0F2F1", "toilet": "#FFEBEE",
"bathroom": "#FFEBEE", "pooja": "#FFFDE7", "study": "#E8F5E9",
"balcony": "#F5F5F5", "parking": "#ECEFF1", "staircase": "#FBE9E7",
"corridor": "#ECEFF1", "utility": "#ECEFF1", "store": "#ECEFF1",
}
ROOM_STROKES = {
"living": "#1976D2", "bedroom": "#F57C00", "master_bedroom": "#E65100",
"kitchen": "#7B1FA2", "dining": "#00796B", "toilet": "#C62828",
"bathroom": "#C62828", "pooja": "#F9A825", "study": "#388E3C",
"balcony": "#616161", "parking": "#455A64", "staircase": "#D84315",
"corridor": "#78909C", "utility": "#78909C", "store": "#78909C",
}
def polygon_bbox(poly):
xs = [p[0] for p in poly]; ys = [p[1] for p in poly]
return min(xs), min(ys), max(xs), max(ys)
def polygons_bbox(polys):
all_x, all_y = [], []
for poly in polys:
for p in poly: all_x.append(p[0]); all_y.append(p[1])
return min(all_x), min(all_y), max(all_x), max(all_y)
def render_floorplan_svg(floorplan, width=1200, padding=80, show_dimensions=True, show_labels=True):
plot = floorplan.get("plot", {})
rooms = floorplan.get("rooms", [])
doors = floorplan.get("doors", [])
windows = floorplan.get("windows", [])
all_polys = []
if plot.get("outer_boundary"): all_polys.append(plot["outer_boundary"])
if plot.get("buildable_boundary"): all_polys.append(plot["buildable_boundary"])
for room in rooms:
if room.get("polygon"): all_polys.append(room["polygon"])
if not all_polys:
return ''
minx, miny, maxx, maxy = polygons_bbox(all_polys)
plot_w, plot_h = maxx - minx, maxy - miny
scale = (width - 2 * padding) / max(plot_w, plot_h)
svg_h = int(plot_h * scale + 2 * padding)
svg_w = int(plot_w * scale + 2 * padding)
def tx(x): return (x - minx) * scale + padding
def ty(y): return svg_h - ((y - miny) * scale + padding)
def tpts(poly): return " ".join(f"{tx(p[0])},{ty(p[1])}" for p in poly)
lines = []
lines.append(f'')
return "\n".join(lines)
def render_floorplan_png(floorplan, output_path, width=1200):
try:
import cairosvg
except ImportError:
raise ImportError("cairosvg required for PNG. Install: pip install cairosvg")
svg_content = render_floorplan_svg(floorplan, width=width)
cairosvg.svg2png(bytestring=svg_content.encode('utf-8'), write_to=output_path,
output_width=width, output_height=int(width * 0.75))
print(f"PNG saved to {output_path}")
def main():
parser = argparse.ArgumentParser(description="Render a floorplan JSON to SVG/PNG")
parser.add_argument("--input", type=str, required=True, help="Path to floorplan JSON")
parser.add_argument("--output", type=str, required=True, help="Output path (.svg or .png)")
parser.add_argument("--width", type=int, default=1200)
parser.add_argument("--no-labels", action="store_true")
parser.add_argument("--no-dimensions", action="store_true")
args = parser.parse_args()
with open(args.input) as f:
floorplan = json.load(f)
show_labels = not args.no_labels
show_dimensions = not args.no_dimensions
if args.output.lower().endswith(".svg"):
svg = render_floorplan_svg(floorplan, width=args.width, show_labels=show_labels, show_dimensions=show_dimensions)
with open(args.output, "w") as f:
f.write(svg)
print(f"SVG saved to {args.output}")
elif args.output.lower().endswith(".png"):
render_floorplan_png(floorplan, args.output, width=args.width)
else:
print("Error: output must be .svg or .png")
if __name__ == "__main__":
main()