#!/usr/bin/env python3 """Plan d'étage — murs en traits épais, ouvertures nettes.""" import matplotlib matplotlib.use('Agg') import matplotlib.pyplot as plt from matplotlib.patches import FancyBboxPatch, Arc, Circle, Rectangle import numpy as np fig, ax = plt.subplots(1, 1, figsize=(26, 20)) ax.set_aspect('equal') ax.axis('off') # ── Palette ──────────────────────────────────────────────────── MUR = '#3a3a5c' MUR_EXT = '#1a1a2e' PIECE = { 'salon': '#fff8e7', 'cuisine': '#eef5e8', 'chambre': '#eaf0f8', 'sdb': '#e0ecf2', 'wc': '#f5ecec', 'bureau': '#f0f0f5', 'it': '#e8e8f0', 'couloir': '#f5f3ee', } MEUBLE = '#8b7355' MEUBLE_IT = '#4a4a6a' PORTE_C = '#8b4513' FENETRE_C = '#7ec8e3' COTE = '#888888' TEXTE = '#1a1a2e' LW = 8.0 # linewidth pour murs (épais, bien visible) LW_EXT = 10.0 # murs extérieurs plus épais # ═══════════════════════════════════════════════════════════════ # LAYOUT # ═══════════════════════════════════════════════════════════════ # Bas (y=0..8) : Salon(0-8) | Cuisine(8-13) | SalleIT(13-15.5) | Ch1(15.5-20) # Couloir (8..9.5) # Haut (9.5..15) : SDB(0-4) | WC(4-6) | Ch2(6-11) | Ch3(11-16) | Bureau(16-20) # ── Fond des pièces ──────────────────────────────────────────── for (x, y, w, h, c) in [ (0, 0, 8, 8, PIECE['salon']), (8, 0, 5, 8, PIECE['cuisine']), (13, 0, 2.5, 8, PIECE['it']), (15.5, 0, 4.5, 8, PIECE['chambre']), (0, 8, 20, 1.5, PIECE['couloir']), (0, 9.5, 4, 5.5, PIECE['sdb']), (4, 9.5, 2, 5.5, PIECE['wc']), (6, 9.5, 5, 5.5, PIECE['chambre']), (11, 9.5, 5, 5.5, PIECE['chambre']), (16, 9.5, 4, 5.5, PIECE['bureau']), ]: ax.add_patch(Rectangle((x, y), w, h, facecolor=c, edgecolor='none', zorder=1)) # ── Helpers ──────────────────────────────────────────────────── def mur_ligne(x1, y1, x2, y2, lw=LW): """Mur en trait épais avec bouts plats (solid_capstyle='butt').""" ax.plot([x1, x2], [y1, y2], color=MUR, linewidth=lw, solid_capstyle='butt', zorder=3) def mur_ext_ligne(x1, y1, x2, y2): """Mur extérieur en trait très épais.""" ax.plot([x1, x2], [y1, y2], color=MUR_EXT, linewidth=LW_EXT, solid_capstyle='butt', zorder=3) def porte_h(x, y, largeur=0.9): """Porte dans un mur horizontal — battant vers le haut.""" ax.plot([x, x], [y, y+largeur], color=PORTE_C, linewidth=2.5, zorder=5) arc = Arc((x, y), largeur*2, largeur*2, angle=90, theta1=0, theta2=90, color=PORTE_C, linewidth=1.2, linestyle='--', zorder=4) ax.add_patch(arc) ax.add_patch(Circle((x, y), 0.06, facecolor=PORTE_C, edgecolor='none', zorder=5)) def porte_v(x, y, largeur=0.9): """Porte dans un mur vertical — battant vers la droite.""" ax.plot([x, x+largeur], [y, y], color=PORTE_C, linewidth=2.5, zorder=5) arc = Arc((x, y), largeur*2, largeur*2, angle=0, theta1=0, theta2=90, color=PORTE_C, linewidth=1.2, linestyle='--', zorder=4) ax.add_patch(arc) ax.add_patch(Circle((x, y), 0.06, facecolor=PORTE_C, edgecolor='none', zorder=5)) def fenetre_h(x1, x2, y): """Fenêtre dans un mur horizontal extérieur.""" # Petits traits de mur aux extrémités for x in [x1, x2]: ax.plot([x, x], [y-0.15, y+0.15], color=MUR_EXT, linewidth=2, zorder=4) ax.plot([x1, x2], [y, y], color=FENETRE_C, linewidth=4, solid_capstyle='butt', zorder=4) def fenetre_v(y1, y2, x): """Fenêtre dans un mur vertical extérieur.""" for y in [y1, y2]: ax.plot([x-0.15, x+0.15], [y, y], color=MUR_EXT, linewidth=2, zorder=4) ax.plot([x, x], [y1, y2], color=FENETRE_C, linewidth=4, solid_capstyle='butt', zorder=4) def meuble(x, y, w, h, label='', couleur=MEUBLE, txt_color='white'): ax.add_patch(FancyBboxPatch((x, y), w, h, boxstyle="round,pad=0.04", facecolor=couleur, edgecolor='#3d2e1a', linewidth=0.8, alpha=0.75, zorder=6)) if label: ax.text(x+w/2, y+h/2, label, ha='center', va='center', fontsize=6.5, color=txt_color, fontweight='bold', zorder=7) def label_piece(x, y, nom, surface, sz_nom=11, sz_surf=8): ax.text(x, y+0.35, nom, ha='center', va='center', fontsize=sz_nom, color=TEXTE, fontweight='bold', zorder=8) ax.text(x, y-0.35, surface, ha='center', va='center', fontsize=sz_surf, color=COTE, zorder=8) def cote(x1, y1, x2, y2, label, decal=0.5): ax.plot([x1, x2], [y1, y2], color=COTE, linewidth=0.7, zorder=2) dx, dy = x2-x1, y2-y1 L = np.sqrt(dx*dx+dy*dy) if L > 0: nx, ny = -dy/L*0.12, dx/L*0.12 ax.plot([x1+nx, x1-nx], [y1+ny, y1-ny], color=COTE, linewidth=0.7, zorder=2) ax.plot([x2+nx, x2-nx], [y2+ny, y2-ny], color=COTE, linewidth=0.7, zorder=2) mx, my = (x1+x2)/2, (y1+y2)/2 px, py = (-dy/L*decal, dx/L*decal) if L > 0 else (0, decal) ax.text(mx+px, my+py, label, ha='center', va='center', fontsize=7.5, color=COTE, zorder=2) # ═══════════════════════════════════════════════════════════════ # MURS EXTÉRIEURS (traits épais, segments avec gaps) # ═══════════════════════════════════════════════════════════════ # Bas (y=0) mur_ext_ligne(0, 0, 2, 0) # coin → fenêtre salon mur_ext_ligne(6, 0, 9.1, 0) # fenêtre salon → porte entrée mur_ext_ligne(10, 0, 13, 0) # porte entrée → fenêtre cuisine mur_ext_ligne(13, 0, 14, 0) # → fenêtre IT mur_ext_ligne(15, 0, 17, 0) # fenêtre IT → fenêtre ch1 mur_ext_ligne(19, 0, 20, 0) # fenêtre ch1 → coin # Droite (x=20) mur_ext_ligne(20, 0, 20, 2) mur_ext_ligne(20, 6, 20, 11) mur_ext_ligne(20, 13.5, 20, 15) # Haut (y=15) mur_ext_ligne(0, 15, 5, 15) mur_ext_ligne(5.5, 15, 8, 15) mur_ext_ligne(10, 15, 13, 15) mur_ext_ligne(15, 15, 20, 15) # Gauche (x=0) mur_ext_ligne(0, 0, 0, 2) mur_ext_ligne(0, 5, 0, 11) mur_ext_ligne(0, 13.5, 0, 15) # ═══════════════════════════════════════════════════════════════ # MURS INTÉRIEURS (traits épais, segments avec gaps pour portes) # ═══════════════════════════════════════════════════════════════ # Salon | Cuisine (x=8) mur_ligne(8, 0, 8, 8) # Cuisine | Salle IT (x=13) mur_ligne(13, 0, 13, 8) # Salle IT | Chambre 1 (x=15.5) mur_ligne(15.5, 0, 15.5, 8) # Couloir BAS (y=8) — segments avec gaps pour portes mur_ligne(0, 8, 3.05, 8) # gauche → porte salon mur_ligne(3.95, 8, 9.55, 8) # porte salon → porte cuisine mur_ligne(10.45, 8, 13.55, 8) # porte cuisine → porte IT mur_ligne(14.45, 8, 17.05, 8) # porte IT → porte ch1 mur_ligne(17.95, 8, 20, 8) # porte ch1 → droite # Couloir HAUT (y=9.5) — segments avec gaps pour portes mur_ligne(0, 9.5, 1.55, 9.5) # gauche → porte SDB mur_ligne(2.45, 9.5, 4.55, 9.5) # porte SDB → porte WC mur_ligne(5.45, 9.5, 8.05, 9.5) # porte WC → porte ch2 mur_ligne(8.95, 9.5, 13.05, 9.5) # porte ch2 → porte ch3 mur_ligne(13.95, 9.5, 17.55, 9.5) # porte ch3 → porte bureau mur_ligne(18.45, 9.5, 20, 9.5) # porte bureau → droite # Murs verticaux partie haute mur_ligne(4, 9.5, 4, 15) # SDB | WC mur_ligne(6, 9.5, 6, 15) # WC | Ch2 mur_ligne(11, 9.5, 11, 15) # Ch2 | Ch3 mur_ligne(16, 9.5, 16, 15) # Ch3 | Bureau # ═══════════════════════════════════════════════════════════════ # PORTES # ═══════════════════════════════════════════════════════════════ # Entrée (mur du bas) porte_v(9.55, 0) # Couloir bas → pièces porte_h(3.5, 8) # salon porte_h(10, 8) # cuisine porte_h(14, 8) # salle IT porte_h(17.5, 8) # chambre 1 # Couloir haut → pièces porte_h(2, 9.5) # SDB porte_h(5, 9.5) # WC porte_h(8.5, 9.5) # chambre 2 porte_h(13.5, 9.5) # chambre 3 porte_h(18, 9.5) # bureau # ═══════════════════════════════════════════════════════════════ # FENÊTRES # ═══════════════════════════════════════════════════════════════ fenetre_h(2, 6, 0) # salon — baie vitrée fenetre_v(2, 5, 0) # salon — côté gauche fenetre_h(10, 13, 0) # cuisine fenetre_h(14, 15, 0) # salle IT fenetre_h(17, 19, 0) # chambre 1 fenetre_v(2, 6, 20) # chambre 1 — côté droit fenetre_v(11, 13.5, 0) # SDB fenetre_h(5, 5.5, 15) # WC — haut fenetre_h(8, 10, 15) # chambre 2 — haut fenetre_h(13, 15, 15) # chambre 3 — haut fenetre_v(11, 13.5, 20) # bureau — côté droit # ═══════════════════════════════════════════════════════════════ # MEUBLES # ═══════════════════════════════════════════════════════════════ # Salon meuble(1, 1.5, 3, 1, 'Canapé d\'angle') meuble(1.5, 3.2, 1.2, 0.6, 'Table basse') meuble(5.5, 1.5, 1.8, 0.7, 'Meuble TV') meuble(1, 5, 1.5, 0.6, 'Bibliothèque') meuble(3, 5, 1.5, 0.6, 'Bibliothèque') # Cuisine meuble(9, 1.5, 2.5, 0.6, 'Plan de travail') meuble(9, 2.5, 1, 0.6, 'Évier') meuble(10.5, 2.5, 1, 0.6, 'Plaques') meuble(9, 3.8, 2.5, 1, 'Îlot central') meuble(12, 1.5, 0.8, 0.6, 'Frigo') meuble(9, 5.5, 1.5, 0.6, 'Cellier') # Salle IT meuble(13.5, 1, 1.5, 0.6, 'Rack 42U', couleur=MEUBLE_IT) meuble(13.5, 2.2, 1.5, 0.6, 'Rack 24U', couleur=MEUBLE_IT) meuble(13.5, 3.5, 1, 0.6, 'Onduleur', couleur=MEUBLE_IT) meuble(14.5, 3.5, 0.8, 0.6, 'NAS', couleur=MEUBLE_IT) meuble(13.5, 5, 1.2, 0.5, 'Bureau IT', couleur=MEUBLE_IT) # Chambre 1 meuble(16.5, 1.5, 1.6, 2, 'Lit 160') meuble(18.5, 1.5, 0.5, 0.5, '') meuble(19.5, 1.5, 0.5, 0.5, '') meuble(16.5, 5, 1.2, 0.5, 'Commode') meuble(18.5, 5, 1, 0.5, 'Bureau') # Salle de bain meuble(0.5, 10.5, 1.5, 0.8, 'Douche ital.') meuble(2.5, 10.5, 1, 0.6, 'Lavabo') meuble(0.5, 12, 1.5, 0.7, 'Baignoire') meuble(2.5, 12.5, 0.8, 0.6, 'WC') # WC meuble(4.5, 10.5, 0.8, 0.6, 'WC') meuble(4.5, 12, 0.6, 0.5, 'Lavabo') # Chambre 2 meuble(7, 10.5, 1.4, 1.9, 'Lit 140') meuble(8.5, 10.5, 0.5, 0.5, '') meuble(7, 13, 1.2, 0.5, 'Commode') meuble(9, 13, 1, 0.5, 'Bureau') # Chambre 3 meuble(12, 10.5, 1.6, 2, 'Lit 160') meuble(14, 10.5, 0.5, 0.5, '') meuble(15, 10.5, 0.5, 0.5, '') meuble(12, 13.5, 1.2, 0.5, 'Armoire') # Bureau meuble(17, 10.5, 1.8, 0.7, 'Bureau') meuble(17, 12, 1, 0.5, 'Étagère') meuble(18.5, 12, 0.8, 0.5, 'Imprimante') # ═══════════════════════════════════════════════════════════════ # ÉTIQUETTES # ═══════════════════════════════════════════════════════════════ label_piece(4, 4, 'SALON / SÉJOUR', '32 m²') label_piece(10.5, 4, 'CUISINE', '20 m²') label_piece(14.25,4, 'SALLE IT', '10 m²', sz_nom=10) label_piece(17.75,4, 'CHAMBRE 1', '18 m²') label_piece(2, 12.5, 'SALLE DE BAIN', '11 m²', sz_nom=9) label_piece(5, 12.5, 'WC', '5.5 m²', sz_nom=9) label_piece(8.5, 12.5, 'CHAMBRE 2', '13.5 m²') label_piece(13.5, 12.5, 'CHAMBRE 3', '13.5 m²') label_piece(18, 12.5, 'BUREAU', '11 m²') # Entrée ax.text(9.55, -0.5, 'ENTRÉE', ha='center', fontsize=10, fontweight='bold', color=TEXTE, zorder=8) ax.plot(9.55, -0.25, marker='v', color=PORTE_C, markersize=8, zorder=5) # ═══════════════════════════════════════════════════════════════ # COTES # ═══════════════════════════════════════════════════════════════ cote(0, -1.2, 20, -1.2, '20.00 m') cote(-1.2, 0, -1.2, 15, '15.00 m') cote(0, -0.7, 8, -0.7, '8.00 m') cote(8, -0.7, 13, -0.7, '5.00 m') cote(13, -0.7, 15.5, -0.7, '2.50 m') cote(15.5, -0.7, 20, -0.7, '4.50 m') # ═══════════════════════════════════════════════════════════════ # LÉGENDE # ═══════════════════════════════════════════════════════════════ lx, ly = 21.5, 14 ax.text(lx, ly, 'LÉGENDE', fontsize=10, fontweight='bold', color=TEXTE, zorder=9) items = [ (MUR_EXT, 'Mur extérieur'), (MUR, 'Mur intérieur'), (FENETRE_C, 'Fenêtre'), (PORTE_C, 'Porte'), (MEUBLE_IT, 'Équipement IT'), ] for i, (c, lbl) in enumerate(items): y = ly - 0.7 - i*0.6 if lbl == 'Fenêtre': ax.plot([lx, lx+1.5], [y, y], color=c, linewidth=4, zorder=9) elif lbl == 'Porte': ax.plot([lx, lx+0.8], [y, y], color=c, linewidth=2.5, zorder=9) ax.add_patch(Circle((lx, y), 0.06, facecolor=c, edgecolor='none', zorder=9)) elif lbl == 'Mur extérieur': ax.plot([lx, lx+1.5], [y, y], color=c, linewidth=LW_EXT, solid_capstyle='butt', zorder=9) elif lbl == 'Mur intérieur': ax.plot([lx, lx+1.5], [y, y], color=c, linewidth=LW, solid_capstyle='butt', zorder=9) else: ax.add_patch(Rectangle((lx, y-0.15), 1.5, 0.3, facecolor=c, edgecolor='none', zorder=9)) ax.text(lx+1.8, y, lbl, fontsize=7.5, color=TEXTE, va='center', zorder=9) # ═══════════════════════════════════════════════════════════════ # TITRE # ═══════════════════════════════════════════════════════════════ ax.text(10, 16.2, 'PLAN D\'ÉTAGE — MAISON FAMILIALE AVEC SALLE IT', ha='center', fontsize=16, fontweight='bold', color=TEXTE, zorder=9) ax.text(10, 15.7, 'Rez-de-chaussée | Surface totale : ~150 m² | Échelle indicative', ha='center', fontsize=9, color=COTE, zorder=9) ax.set_facecolor('#f0ede5') fig.patch.set_facecolor('#e8e4dc') ax.set_xlim(-2, 24) ax.set_ylim(-2, 17.5) output = '/root/workspace/plan_grande_maison.pdf' plt.savefig(output, format='pdf', dpi=200, bbox_inches='tight', facecolor=fig.get_facecolor(), edgecolor='none') plt.close() print(f"✅ PDF : {output}")