import gradio as gr import pandas as pd import ezdxf import matplotlib.pyplot as plt import numpy as np import io import tempfile import json from pathlib import Path from PIL import Image from datetime import date import copy import math import random # ============================================================ # ۰. تنظیمات ثابت # ============================================================ PAPER_SIZES = { "A4_portrait": (210, 297), "A4_landscape": (297, 210), "A3_portrait": (297, 420), "A3_landscape": (420, 297), "A2_portrait": (420, 594), "A2_landscape": (594, 420), "A1_portrait": (594, 841), "A1_landscape": (841, 594), "A0_portrait": (841, 1189), "A0_landscape": (1189, 841), } # ============================================================ # ۱. Title Block Manager # ============================================================ class TitleBlockManager: LEVEL_CONFIGS = { 1: { "name": "BASIC (دانشجویی)", "description": "مناسب تمرین‌های کلاسی و اسکیس", "fields": [ {"id": "project_name", "label": "نام پروژه", "width_ratio": 1.0}, {"id": "drawing_title", "label": "عنوان نقشه", "width_ratio": 1.0}, {"id": "scale", "label": "مقیاس", "width_ratio": 0.5}, {"id": "date", "label": "تاریخ", "width_ratio": 0.5}, {"id": "designer", "label": "طراح", "width_ratio": 1.0}, ] }, 2: { "name": "STANDARD (مسکونی ۱-۲ طبقه)", "description": "مناسب ساختمان‌های کوچک و شخصی", "fields": [ {"id": "project_name", "label": "نام پروژه", "width_ratio": 1.0}, {"id": "client", "label": "کارفرما", "width_ratio": 0.5}, {"id": "drawing_title", "label": "عنوان نقشه", "width_ratio": 0.5}, {"id": "drawing_code", "label": "کد نقشه", "width_ratio": 0.5}, {"id": "sheet_number", "label": "شماره شیت", "width_ratio": 0.5}, {"id": "scale", "label": "مقیاس", "width_ratio": 0.5}, {"id": "date", "label": "تاریخ", "width_ratio": 0.5}, {"id": "designer", "label": "طراح", "width_ratio": 1.0}, ] }, 3: { "name": "PROFESSIONAL (ساختمان تیپ ۳)", "description": "مناسب ساختمان‌های ۳-۶ طبقه تجاری-مسکونی", "fields": [ {"id": "project_name", "label": "نام پروژه", "width_ratio": 1.0}, {"id": "client", "label": "کارفرما", "width_ratio": 0.5}, {"id": "project_address", "label": "آدرس پروژه", "width_ratio": 0.5}, {"id": "drawing_title", "label": "عنوان نقشه", "width_ratio": 1.0}, {"id": "drawing_code", "label": "کد نقشه", "width_ratio": 0.5}, {"id": "sheet_number", "label": "شماره شیت", "width_ratio": 0.5}, {"id": "scale", "label": "مقیاس", "width_ratio": 0.5}, {"id": "date", "label": "تاریخ", "width_ratio": 0.5}, {"id": "phase", "label": "فاز طراحی", "width_ratio": 0.5}, {"id": "revision", "label": "بازبینی", "width_ratio": 0.5}, {"id": "designer", "label": "طراح", "width_ratio": 0.5}, {"id": "supervisor", "label": "ناظر", "width_ratio": 0.5}, ] }, 4: { "name": "ENTERPRISE (پروژه بزرگ)", "description": "مناسب برج‌ها و مجتمع‌های بزرگ", "fields": [ {"id": "project_name", "label": "نام پروژه", "width_ratio": 1.0}, {"id": "client", "label": "کارفرما", "width_ratio": 0.5}, {"id": "consultant", "label": "مهندس مشاور", "width_ratio": 0.5}, {"id": "project_address", "label": "آدرس پروژه", "width_ratio": 1.0}, {"id": "drawing_title", "label": "عنوان نقشه", "width_ratio": 1.0}, {"id": "drawing_code", "label": "کد نقشه", "width_ratio": 0.5}, {"id": "sheet_number", "label": "شماره شیت", "width_ratio": 0.5}, {"id": "scale", "label": "مقیاس", "width_ratio": 0.5}, {"id": "date", "label": "تاریخ", "width_ratio": 0.5}, {"id": "phase", "label": "فاز طراحی", "width_ratio": 0.5}, {"id": "revision", "label": "بازبینی", "width_ratio": 0.5}, {"id": "permit_number", "label": "شماره پروانه", "width_ratio": 0.5}, {"id": "municipality", "label": "شهرداری منطقه", "width_ratio": 0.5}, {"id": "designer", "label": "طراح", "width_ratio": 0.5}, {"id": "supervisor", "label": "ناظر", "width_ratio": 0.5}, {"id": "owner_signature", "label": "امضای مالک", "width_ratio": 0.5}, ] }, 5: { "name": "GOVERNMENT (دولتی/مناقصات)", "description": "مناسب پروژه‌های دولتی و بیمارستان‌ها", "fields": [ {"id": "project_name", "label": "نام پروژه", "width_ratio": 1.0}, {"id": "client", "label": "کارفرما", "width_ratio": 0.5}, {"id": "government_body", "label": "سازمان مربوطه", "width_ratio": 0.5}, {"id": "consultant", "label": "مهندس مشاور", "width_ratio": 0.5}, {"id": "contractor", "label": "پیمانکار", "width_ratio": 0.5}, {"id": "project_address", "label": "آدرس پروژه", "width_ratio": 1.0}, {"id": "drawing_title", "label": "عنوان نقشه", "width_ratio": 1.0}, {"id": "drawing_code", "label": "کد نقشه", "width_ratio": 0.5}, {"id": "sheet_number", "label": "شماره شیت", "width_ratio": 0.5}, {"id": "scale", "label": "مقیاس", "width_ratio": 0.5}, {"id": "date", "label": "تاریخ", "width_ratio": 0.5}, {"id": "phase", "label": "فاز طراحی", "width_ratio": 0.5}, {"id": "revision", "label": "بازبینی", "width_ratio": 0.5}, {"id": "permit_number", "label": "شماره پروانه", "width_ratio": 0.5}, {"id": "tender_number", "label": "شماره مناقصه", "width_ratio": 0.5}, {"id": "contract_number", "label": "شماره قرارداد", "width_ratio": 0.5}, {"id": "municipality", "label": "شهرداری منطقه", "width_ratio": 0.5}, {"id": "designer", "label": "طراح", "width_ratio": 0.5}, {"id": "supervisor", "label": "ناظر", "width_ratio": 0.5}, {"id": "owner_signature", "label": "امضای مالک", "width_ratio": 0.5}, {"id": "government_stamp", "label": "مهر سازمان", "width_ratio": 0.5}, ] } } def __init__(self, level=3, paper_size="A3_landscape"): self.level = level self.config = self.LEVEL_CONFIGS.get(level, self.LEVEL_CONFIGS[3]) self.fields = self.config["fields"] self.paper_size = paper_size self.paper_width, self.paper_height = PAPER_SIZES.get(paper_size, (420, 297)) def create_title_block(self, msp, project_info, sheet_info): if "TITLE_BLOCK" not in [l.dxf.name for l in msp.doc.layers]: msp.doc.layers.add(name="TITLE_BLOCK", color=5) msp.doc.layers.add(name="TITLE_LINES", color=15) msp.doc.layers.add(name="TITLE_TEXT", color=7) margin = 10 inner_w = self.paper_width - 2 * margin inner_h = self.paper_height - 2 * margin msp.add_line((margin, margin), (self.paper_width - margin, margin), dxfattribs={'layer': 'TITLE_LINES'}) msp.add_line((self.paper_width - margin, margin), (self.paper_width - margin, self.paper_height - margin), dxfattribs={'layer': 'TITLE_LINES'}) msp.add_line((self.paper_width - margin, self.paper_height - margin), (margin, self.paper_height - margin), dxfattribs={'layer': 'TITLE_LINES'}) msp.add_line((margin, self.paper_height - margin), (margin, margin), dxfattribs={'layer': 'TITLE_LINES'}) tb_width = min(180, inner_w * 0.4) tb_height = min(60, inner_h * 0.25) tb_x = self.paper_width - margin - tb_width tb_y = margin msp.add_line((tb_x, tb_y), (tb_x + tb_width, tb_y), dxfattribs={'layer': 'TITLE_LINES'}) msp.add_line((tb_x + tb_width, tb_y), (tb_x + tb_width, tb_y + tb_height), dxfattribs={'layer': 'TITLE_LINES'}) msp.add_line((tb_x + tb_width, tb_y + tb_height), (tb_x, tb_y + tb_height), dxfattribs={'layer': 'TITLE_LINES'}) msp.add_line((tb_x, tb_y + tb_height), (tb_x, tb_y), dxfattribs={'layer': 'TITLE_LINES'}) num_fields = len(self.fields) num_rows = (num_fields + 1) // 2 row_height = tb_height / num_rows for i in range(1, num_rows): y = tb_y + i * row_height msp.add_line((tb_x, y), (tb_x + tb_width, y), dxfattribs={'layer': 'TITLE_LINES'}) mid_x = tb_x + tb_width * 0.5 msp.add_line((mid_x, tb_y), (mid_x, tb_y + tb_height), dxfattribs={'layer': 'TITLE_LINES'}) merged_info = {**project_info, **sheet_info} for i, field in enumerate(self.fields): row = i // 2 col = i % 2 x_label = tb_x + 2 + col * (tb_width / 2) x_value = x_label + 25 y = tb_y + tb_height - (row + 1) * row_height + row_height * 0.7 msp.add_text(field["label"], dxfattribs={'layer': 'TITLE_TEXT', 'height': 2.0, 'color': 5}).set_placement((x_label, y)) value = merged_info.get(field["id"], "___") msp.add_text(str(value), dxfattribs={'layer': 'TITLE_TEXT', 'height': 2.5, 'color': 7}).set_placement((x_value, y - 3)) return True # ============================================================ # ۲. Sheet Series Generator # ============================================================ class SheetSeriesGenerator: SHEET_TEMPLATES = { "A_COVER": { "title": "مدارک عمومی", "sheets": [ {"code": "A-00", "title": "کاور و شناسنامه پروژه", "scale": "-"}, {"code": "A-01", "title": "فهرست نقشه‌ها", "scale": "-"}, {"code": "A-02", "title": "راهنمای علائم و اختصارات", "scale": "-"}, {"code": "A-03", "title": "نقشه موقعیت سایت", "scale": "1/500"}, ] }, "B_SITE": { "title": "نقشه‌های سایت", "sheets": [ {"code": "B-01", "title": "پلان موقعیت و همجواری‌ها", "scale": "1/200"}, {"code": "B-02", "title": "پلان محوطه‌سازی و دسترسی‌ها", "scale": "1/200"}, ] }, "C_ARCHITECTURE": { "title": "نقشه‌های معماری", "sheets": [ {"code": "C-01", "title": "پلان زیرزمین — پارکینگ و انباری", "scale": "1/100"}, {"code": "C-02", "title": "پلان همکف — تجاری", "scale": "1/100"}, {"code": "C-03", "title": "پلان تیپ طبقات مسکونی", "scale": "1/100"}, {"code": "C-04", "title": "پلان بام — خرپشته و شیب‌بندی", "scale": "1/100"}, {"code": "C-05", "title": "نماهای اصلی (شمالی و جنوبی)", "scale": "1/100"}, {"code": "C-06", "title": "نماهای فرعی (شرقی و غربی)", "scale": "1/100"}, {"code": "C-07", "title": "برش A-A (طول ساختمان)", "scale": "1/100"}, {"code": "C-08", "title": "برش B-B (عرض ساختمان)", "scale": "1/100"}, ] }, "D_STRUCTURE": { "title": "نقشه‌های سازه", "sheets": [ {"code": "D-01", "title": "پلان فونداسیون و ستون‌گذاری", "scale": "1/100"}, {"code": "D-02", "title": "پلان تیرریزی تیپ طبقات", "scale": "1/100"}, {"code": "D-03", "title": "جزئیات اتصالات و دتایل‌های سازه", "scale": "1/20"}, ] }, "E_MECHANICAL": { "title": "نقشه‌های تأسیسات مکانیکی", "sheets": [ {"code": "E-01", "title": "پلان لوله‌کشی آب سرد و گرم — همکف", "scale": "1/100"}, {"code": "E-02", "title": "پلان لوله‌کشی آب سرد و گرم — طبقات", "scale": "1/100"}, {"code": "E-03", "title": "پلان فاضلاب و آب باران — زیرزمین", "scale": "1/100"}, {"code": "E-04", "title": "پلان فاضلاب و آب باران — طبقات", "scale": "1/100"}, {"code": "E-05", "title": "پلان گرمایش و شوفاژ", "scale": "1/100"}, {"code": "E-06", "title": "نقشه موتورخانه و تجهیزات", "scale": "1/50"}, ] }, "F_ELECTRICAL": { "title": "نقشه‌های تأسیسات برقی", "sheets": [ {"code": "F-01", "title": "پلان روشنایی — همکف تجاری", "scale": "1/100"}, {"code": "F-02", "title": "پلان روشنایی — طبقات مسکونی", "scale": "1/100"}, {"code": "F-03", "title": "پلان پریزها و سیم‌کشی", "scale": "1/100"}, {"code": "F-04", "title": "نقشه تابلوهای برق و رایزر دیاگرام", "scale": "1/50"}, ] } } @classmethod def get_all_sheets(cls): sheets = [] sheet_num = 1 for cat_key, cat_data in cls.SHEET_TEMPLATES.items(): for s in cat_data["sheets"]: sheets.append({**s, "category": cat_key, "category_title": cat_data["title"], "sheet_number": sheet_num, "phase": "فاز یک — طراحی"}) sheet_num += 1 return sheets @classmethod def get_total_sheets_count(cls): return len(cls.get_all_sheets()) # ============================================================ # ۳. Floorplan Engine # ============================================================ class FloorplanEngine: def __init__(self, site_width, site_depth, setback_front, setback_back, setback_left, setback_right, corridor_width=1.2): self.site_width = site_width self.site_depth = site_depth self.corridor_width = corridor_width self.min_x = setback_right self.max_x = site_width - setback_left self.min_y = setback_front self.max_y = site_depth - setback_back self.buildable_width = self.max_x - self.min_x self.buildable_depth = self.max_y - self.min_y self.grid_resolution = 0.1 self.grid_cols = int(self.buildable_width / self.grid_resolution) + 1 self.grid_rows = int(self.buildable_depth / self.grid_resolution) + 1 self.grid = np.zeros((self.grid_rows, self.grid_cols), dtype=bool) def _world_to_grid(self, x, y): col = int((x - self.min_x) / self.grid_resolution) row = int((y - self.min_y) / self.grid_resolution) return max(0, min(row, self.grid_rows - 1)), max(0, min(col, self.grid_cols - 1)) def _is_space_free(self, x, y, width, height): r1, c1 = self._world_to_grid(x, y) r2, c2 = self._world_to_grid(x + width, y + height) if r2 >= self.grid_rows or c2 >= self.grid_cols: return False margin = int(0.3 / self.grid_resolution) r1 = max(0, r1 - margin) c1 = max(0, c1 - margin) r2 = min(self.grid_rows - 1, r2 + margin) c2 = min(self.grid_cols - 1, c2 + margin) return not np.any(self.grid[r1:r2+1, c1:c2+1]) def _mark_occupied(self, x, y, width, height): r1, c1 = self._world_to_grid(x, y) r2, c2 = self._world_to_grid(x + width, y + height) self.grid[r1:r2+1, c1:c2+1] = True def _find_best_position(self, width, height, prefer_facade='south'): best_pos = None best_score = float('inf') step = int(0.5 / self.grid_resolution) for r in range(0, self.grid_rows, step): y = self.min_y + r * self.grid_resolution if y + height > self.max_y: break for c in range(0, self.grid_cols, step): x = self.min_x + c * self.grid_resolution if x + width > self.max_x: break if self._is_space_free(x, y, width, height): score = r * 10 if prefer_facade == 'north': score = (self.grid_rows - r) * 10 score += c if score < best_score: best_score = score best_pos = (x, y) return best_pos def layout(self, spaces): def sort_key(s): return (s.get('daylit', True) == False, -s['area']) sorted_spaces = sorted(spaces, key=sort_key) result = [] for space in sorted_spaces: area = space['area'] min_w = space.get('min_width', 2.0) daylit = space.get('daylit', True) best_w, best_h = self._optimal_dimensions(area, min_w) placed = False for scale in [1.0, 0.9, 1.1, 0.8, 1.2]: w = best_w * scale h = area / w pos = self._find_best_position(w, h, 'south' if daylit else 'north') if pos: x, y = pos self._mark_occupied(x, y, w, h) result.append({'name': space['name'], 'x': x, 'y': y, 'width': w, 'height': h, 'type': space.get('type', 'unknown')}) placed = True break if not placed: w = max(min_w, np.sqrt(area)) h = area / w pos = self._find_best_position(w, h, 'any') if pos: x, y = pos self._mark_occupied(x, y, w, h) result.append({'name': space['name'], 'x': x, 'y': y, 'width': w, 'height': h, 'type': space.get('type', 'unknown')}) return result def _optimal_dimensions(self, area, min_width): w = np.sqrt(area * 1.5) h = area / w if w < min_width: w = min_width h = area / w return w, h # ============================================================ # ۴. DXF Generator # ============================================================ def create_dxf_with_titleblock(layout, site_width, site_depth, setbacks, project_info, sheet_info, title_block_level, paper_size): doc = ezdxf.new(setup=True) doc.units = ezdxf.units.M msp = doc.modelspace() doc.layers.add(name="SITE", color=8) doc.layers.add(name="BUILDABLE", color=9) doc.layers.add(name="WALLS", color=1) doc.layers.add(name="TEXT", color=7) msp.add_line((0, 0), (site_width, 0), dxfattribs={'layer': 'SITE'}) msp.add_line((site_width, 0), (site_width, site_depth), dxfattribs={'layer': 'SITE'}) msp.add_line((site_width, site_depth), (0, site_depth), dxfattribs={'layer': 'SITE'}) msp.add_line((0, site_depth), (0, 0), dxfattribs={'layer': 'SITE'}) bx = setbacks['left'] by = setbacks['front'] bw = site_width - setbacks['left'] - setbacks['right'] bd = site_depth - setbacks['front'] - setbacks['back'] msp.add_line((bx, by), (bx + bw, by), dxfattribs={'layer': 'BUILDABLE'}) msp.add_line((bx + bw, by), (bx + bw, by + bd), dxfattribs={'layer': 'BUILDABLE'}) msp.add_line((bx + bw, by + bd), (bx, by + bd), dxfattribs={'layer': 'BUILDABLE'}) msp.add_line((bx, by + bd), (bx, by), dxfattribs={'layer': 'BUILDABLE'}) for space in layout: x, y, w, h = space['x'], space['y'], space['width'], space['height'] name = str(space['name']) msp.add_line((x, y), (x + w, y), dxfattribs={'layer': 'WALLS'}) msp.add_line((x + w, y), (x + w, y + h), dxfattribs={'layer': 'WALLS'}) msp.add_line((x + w, y + h), (x, y + h), dxfattribs={'layer': 'WALLS'}) msp.add_line((x, y + h), (x, y), dxfattribs={'layer': 'WALLS'}) mtext = msp.add_mtext(name, dxfattribs={'layer': 'TEXT', 'char_height': 0.35, 'width': w * 0.8, 'attachment_point': ezdxf.lldxf.const.MTEXT_MIDDLE_CENTER}) mtext.set_location((x + w/2, y + h/2)) if project_info: tb = TitleBlockManager(level=title_block_level, paper_size=paper_size) tb.create_title_block(msp, project_info, sheet_info) with tempfile.NamedTemporaryFile(suffix='.dxf', delete=False) as tmp: doc.saveas(tmp.name) return tmp.name # ============================================================ # ۵. Preview Generator # ============================================================ def create_preview_image(layout, site_width, site_depth, setbacks): fig, ax = plt.subplots(figsize=(14, 11)) ax.set_facecolor('#FAFAFA') site_rect = plt.Rectangle((0, 0), site_width, site_depth, linewidth=3, edgecolor='#333333', facecolor='#E8E8E8', alpha=0.5) ax.add_patch(site_rect) bx = setbacks['left'] by = setbacks['front'] bw = site_width - setbacks['left'] - setbacks['right'] bd = site_depth - setbacks['front'] - setbacks['back'] buildable_rect = plt.Rectangle((bx, by), bw, bd, linewidth=2, edgecolor='#FF9800', facecolor='none', linestyle='--') ax.add_patch(buildable_rect) colors = {'living_dining': '#FFE0B2', 'living': '#FFE0B2', 'bedroom': '#C8E6C9', 'kitchen': '#FFCCBC', 'bathroom': '#B3E5FC', 'wc': '#B3E5FC', 'corridor': '#EEEEEE', 'balcony': '#E1BEE7', 'parking': '#CFD8DC'} for space in layout: x, y, w, h = space['x'], space['y'], space['width'], space['height'] space_type = str(space.get('type', '')).lower() color = colors.get(space_type, '#FFF9C4') rect = plt.Rectangle((x, y), w, h, linewidth=2.5, edgecolor='#424242', facecolor=color, alpha=0.8) ax.add_patch(rect) name = str(space['name']) area = w * h ax.text(x + w/2, y + h/2 + 0.2, name, ha='center', va='center', fontsize=9, fontweight='bold', bbox=dict(boxstyle='round,pad=0.3', facecolor='white', alpha=0.85)) ax.text(x + w/2, y + h/2 - 0.5, f"{w:.1f}×{h:.1f}m | {area:.1f}m²", ha='center', va='center', fontsize=7, color='#666666') margin = 3 ax.set_xlim(-margin, site_width + margin) ax.set_ylim(-margin, site_depth + margin) ax.set_aspect('equal') ax.set_title('پیش‌نمایش پلان معماری', fontsize=15, fontweight='bold', pad=15) ax.grid(True, alpha=0.2, linestyle='--') from matplotlib.patches import Patch legend_elements = [Patch(facecolor='#FFE0B2', label='پذیرایی/نشیمن'), Patch(facecolor='#C8E6C9', label='اتاق خواب'), Patch(facecolor='#FFCCBC', label='آشپزخانه'), Patch(facecolor='#B3E5FC', label='سرویس'), Patch(facecolor='#E1BEE7', label='بالکن'), Patch(facecolor='#EEEEEE', label='راهرو')] ax.legend(handles=legend_elements, loc='upper right', fontsize=9, framealpha=0.9) buf = io.BytesIO() fig.savefig(buf, format='png', dpi=130, bbox_inches='tight') buf.seek(0) plt.close(fig) return Image.open(buf) # ============================================================ # ۶. Process Functions # ============================================================ def process_form(site_width, site_depth, setback_front, setback_back, setback_left, setback_right, corridor_width, spaces_json, project_name, client, project_address, drawing_title, drawing_code, sheet_number, scale, revision, designer, supervisor, title_block_level, paper_size): if not spaces_json or spaces_json.strip() == '' or spaces_json == '[]': return "❌ لطفاً حداقل یک فضا تعریف کنید.", None, None, None try: spaces = json.loads(spaces_json) except json.JSONDecodeError as e: return f"❌ خطا: {str(e)}", None, None, None if len(spaces) == 0: return "❌ لیست فضاها خالی است.", None, None, None total_area = sum(s.get('area', 0) for s in spaces) buildable_area = (site_width - setback_left - setback_right) * (site_depth - setback_front - setback_back) occupancy = (total_area / buildable_area * 100) if buildable_area > 0 else 0 if site_width <= 0 or site_depth <= 0: return "❌ ابعاد زمین باید مثبت باشد.", None, None, None if occupancy > 95: return f"❌ سطح اشغال {occupancy:.0f}٪ — فضای راهرو کافی نیست.", None, None, None try: engine = FloorplanEngine(site_width, site_depth, setback_front, setback_back, setback_left, setback_right, corridor_width) layout = engine.layout(spaces) if not layout: return "❌ نتونستم فضاها رو بچینم.", None, None, None setbacks = {'front': setback_front, 'back': setback_back, 'left': setback_left, 'right': setback_right} project_info = {'project_name': project_name or '___', 'client': client or '___', 'project_address': project_address or '___', 'designer': designer or '___', 'supervisor': supervisor or '___'} total_sheets = SheetSeriesGenerator.get_total_sheets_count() sheet_info = {'drawing_title': drawing_title or 'پلان معماری طبقه همکف', 'drawing_code': drawing_code or 'C-02', 'sheet_number': f"{sheet_number} از {total_sheets}", 'scale': scale or '1/100', 'date': date.today().strftime('%Y/%m/%d'), 'phase': 'فاز یک — طراحی معماری', 'revision': revision or 'Rev A'} dxf_path = create_dxf_with_titleblock(layout, site_width, site_depth, setbacks, project_info, sheet_info, int(title_block_level), paper_size) preview = create_preview_image(layout, site_width, site_depth, setbacks) output_data = {'site': {'width': site_width, 'depth': site_depth, 'setbacks': setbacks}, 'spaces': spaces, 'layout': layout, 'project_info': project_info, 'sheet_info': sheet_info, 'title_block_level': title_block_level, 'paper_size': paper_size, 'stats': {'total_area': total_area, 'buildable_area': buildable_area, 'occupancy_rate': round(occupancy, 1)}} with tempfile.NamedTemporaryFile(suffix='.json', delete=False, mode='w', encoding='utf-8') as tmp: json.dump(output_data, tmp, ensure_ascii=False, indent=2) json_path = tmp.name status = f"✅ چیدمان با موفقیت انجام شد.\n\n📐 {len(layout)} فضا چیده شد\n📏 مساحت کل: {total_area:.1f} m²\n🏠 سطح اشغال: {occupancy:.1f}٪\n📄 سایز کاغذ: {paper_size}\n🏷️ سطح کادر: {TitleBlockManager.LEVEL_CONFIGS[int(title_block_level)]['name']}\n💾 خروجی‌ها: DXF + JSON" return status, preview, dxf_path, json_path except Exception as e: import traceback error_details = traceback.format_exc() print(error_details) return f"❌ خطا: {str(e)}", None, None, None, None # ============================================================ # ۷. Space Management # ============================================================ def add_space(space_name, space_type, area, min_width, daylit, spaces_state): if not space_name or area <= 0: return spaces_state, "⚠️ نام فضا و مساحت را وارد کنید." try: spaces = json.loads(spaces_state) if spaces_state else [] except: spaces = [] spaces.append({'name': space_name, 'type': space_type, 'area': float(area), 'min_width': float(min_width), 'daylit': daylit}) summary = f"📋 {len(spaces)} فضا | مجموع: {sum(s['area'] for s in spaces):.1f} m²" return json.dumps(spaces, ensure_ascii=False), summary def clear_spaces(): return "[]", "📋 ۰ فضا" def get_spaces_table(spaces_json): if not spaces_json or spaces_json == '[]': return pd.DataFrame(columns=['نام فضا', 'نوع', 'مساحت', 'حداقل عرض', 'نورگیر']) try: spaces = json.loads(spaces_json) data = [[s.get('name', ''), s.get('type', ''), s.get('area', 0), s.get('min_width', 0), '✅' if s.get('daylit', True) else '❌'] for s in spaces] return pd.DataFrame(data, columns=['نام فضا', 'نوع', 'مساحت', 'حداقل عرض', 'نورگیر']) except: return pd.DataFrame(columns=['نام فضا', 'نوع', 'مساحت', 'حداقل عرض', 'نورگیر']) def update_occupancy(site_w, site_d, sf, sb, sl, sr, spaces_json): if not spaces_json or spaces_json == '[]': return "📋 هنوز فضایی تعریف نشده." try: spaces = json.loads(spaces_json) total = sum(s['area'] for s in spaces) buildable = (site_w - sl - sr) * (site_d - sf - sb) if buildable <= 0: return "⚠️ ابعاد نامعتبر" occ = total / buildable * 100 if occ > 90: return f"⚠️ سطح اشغال: {occ:.1f}٪ — بسیار زیاد!" elif occ > 70: return f"⚡ سطح اشغال: {occ:.1f}٪ — نسبتاً زیاد" else: return f"✅ سطح اشغال: {occ:.1f}٪ | کل: {total:.1f} m² | محدوده ساخت: {buildable:.1f} m²" except: return "⚠️ خطا در محاسبه" def get_sheet_preview(title_block_level, paper_size): sheets = SheetSeriesGenerator.get_all_sheets() total = len(sheets) level_name = TitleBlockManager.LEVEL_CONFIGS[int(title_block_level)]['name'] paper_w, paper_h = PAPER_SIZES[paper_size] text = f"📄 **تنظیمات شیت:**\n• سطح کادر: {level_name}\n• سایز کاغذ: {paper_size} ({paper_w}×{paper_h} mm)\n• تعداد کل شیت‌ها: {total}\n\n📋 **لیست شیت‌های فاز یک:**\n" for cat_key, cat_data in SheetSeriesGenerator.SHEET_TEMPLATES.items(): text += f"\n**{cat_data['title']}:**\n" for s in cat_data['sheets']: text += f" • {s['code']} — {s['title']} (مقیاس: {s['scale']})\n" return text # ============================================================ # ۸. Gradio Interface # ============================================================ with gr.Blocks(title="پلتفرم طراحی پلان معماری — نسخه حرفه‌ای") as demo: gr.Markdown(""" # 🏗️ پلتفرم تولید پلان معماری — نسخه حرفه‌ای ### ✨ امکانات: ۵ سطح کادر نقشه | سایز کاغذ قابل تنظیم | شیت‌بندی کامل فاز یک """) spaces_state = gr.State(value="[]") with gr.Tabs(): # تب ۱: زمین with gr.Tab("🏠 زمین و ضوابط"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### ابعاد زمین") site_width = gr.Number(label="عرض زمین (متر)", value=15.0, minimum=5, maximum=50) site_depth = gr.Number(label="عمق زمین (متر)", value=20.0, minimum=5, maximum=50) with gr.Column(scale=1): gr.Markdown("### عقب‌نشینی‌ها") setback_front = gr.Number(label="عقب‌نشینی جلو (متر)", value=3.0, minimum=0, maximum=10) setback_back = gr.Number(label="عقب‌نشینی عقب (متر)", value=2.0, minimum=0, maximum=10) setback_left = gr.Number(label="عقب‌نشینی چپ (متر)", value=1.5, minimum=0, maximum=10) setback_right = gr.Number(label="عقب‌نشینی راست (متر)", value=1.5, minimum=0, maximum=10) with gr.Column(scale=1): gr.Markdown("### تنظیمات") corridor_width = gr.Slider(label="عرض راهرو (متر)", minimum=0.8, maximum=2.5, value=1.2, step=0.1) occupancy_info = gr.Textbox(label="اطلاعات سطح اشغال", value="📋 هنوز فضایی تعریف نشده.", interactive=False) # تب ۲: فضاها with gr.Tab("🚪 فضاها"): with gr.Row(): with gr.Column(scale=2): gr.Markdown("### افزودن فضای جدید") space_name = gr.Textbox(label="نام فضا", placeholder="مثلاً: پذیرایی", scale=3) space_type = gr.Dropdown(label="نوع فضا", choices=['living_dining', 'bedroom', 'kitchen', 'bathroom', 'corridor', 'balcony', 'parking'], value='living_dining', scale=2) with gr.Row(): area = gr.Number(label="مساحت (m²)", value=25.0, minimum=2, maximum=200) min_width = gr.Number(label="حداقل عرض (m)", value=3.0, minimum=1, maximum=20) daylit = gr.Checkbox(label="نیاز به نورگیری", value=True) with gr.Row(): add_btn = gr.Button("➕ افزودن", variant="primary") clear_btn = gr.Button("🗑️ پاک کردن همه") space_message = gr.Textbox(label="وضعیت", interactive=False) with gr.Column(scale=3): gr.Markdown("### فضاهای تعریف شده") spaces_table = gr.Dataframe(headers=['نام فضا', 'نوع', 'مساحت', 'حداقل عرض', 'نورگیر'], interactive=False) spaces_summary = gr.Textbox(label="خلاصه", value="📋 ۰ فضا", interactive=False) # تب ۳: پروژه و شیت with gr.Tab("📋 پروژه و شیت"): with gr.Row(): with gr.Column(scale=1): gr.Markdown("### اطلاعات پروژه") project_name = gr.Textbox(label="نام پروژه", value="ساختمان تجاری-مسکونی ستاره") client = gr.Textbox(label="کارفرما", value="شرکت آتیه‌سازان") project_address = gr.Textbox(label="آدرس پروژه", value="تهران، خیابان ولیعصر") designer = gr.Textbox(label="طراح", value="مهندس رضایی") supervisor = gr.Textbox(label="ناظر", value="مهندس حسینی") with gr.Column(scale=1): gr.Markdown("### اطلاعات شیت") drawing_title = gr.Textbox(label="عنوان نقشه", value="پلان معماری طبقه همکف") all_codes = [s['code'] for s in SheetSeriesGenerator.get_all_sheets()] drawing_code = gr.Dropdown(label="کد نقشه", choices=all_codes, value="C-02") sheet_number = gr.Number(label="شماره شیت", value=5, minimum=1) scale = gr.Dropdown(label="مقیاس", choices=["1/20", "1/50", "1/100", "1/200", "1/500"], value="1/100") revision = gr.Textbox(label="بازبینی", value="Rev A") with gr.Column(scale=1): gr.Markdown("### تنظیمات کادر و کاغذ") title_block_level = gr.Radio( choices=[ (f"۱ - {TitleBlockManager.LEVEL_CONFIGS[1]['name']}", 1), (f"۲ - {TitleBlockManager.LEVEL_CONFIGS[2]['name']}", 2), (f"۳ - {TitleBlockManager.LEVEL_CONFIGS[3]['name']} ⭐", 3), (f"۴ - {TitleBlockManager.LEVEL_CONFIGS[4]['name']}", 4), (f"۵ - {TitleBlockManager.LEVEL_CONFIGS[5]['name']}", 5), ], value=3, label="سطح کادر نقشه" ) paper_size = gr.Dropdown(label="سایز کاغذ", choices=list(PAPER_SIZES.keys()), value="A3_landscape") sheet_preview_btn = gr.Button("📋 مشاهده شیت‌های پروژه") sheet_preview_text = gr.Textbox(label="راهنمای شیت‌بندی", interactive=False, lines=15, value="برای مشاهده لیست شیت‌ها، روی دکمه بالا کلیک کنید.") # تب ۴: خروجی with gr.Tab("📊 خروجی"): with gr.Row(): with gr.Column(scale=1): submit_btn = gr.Button("🚀 تولید پلان نهایی", variant="primary", size="lg") status_output = gr.Textbox(label="وضعیت", interactive=False, lines=8) with gr.Row(): dxf_output = gr.File(label="📥 DXF") json_output = gr.File(label="📥 JSON پروژه") with gr.Column(scale=2): preview_output = gr.Image(label="پیش‌نمایش", type="pil") # Events add_btn.click(fn=add_space, inputs=[space_name, space_type, area, min_width, daylit, spaces_state], outputs=[spaces_state, space_message] ).then(fn=get_spaces_table, inputs=[spaces_state], outputs=[spaces_table] ).then(fn=lambda s: f"📋 {len(json.loads(s))} فضا | مجموع: {sum(sp['area'] for sp in json.loads(s)):.1f} m²" if s and s != '[]' else "📋 ۰ فضا", inputs=[spaces_state], outputs=[spaces_summary]) clear_btn.click(fn=clear_spaces, inputs=[], outputs=[spaces_state, space_message] ).then(fn=lambda: (pd.DataFrame(columns=['نام فضا', 'نوع', 'مساحت', 'حداقل عرض', 'نورگیر']), "📋 ۰ فضا"), inputs=[], outputs=[spaces_table, spaces_summary]) for inp in [site_width, site_depth, setback_front, setback_back, setback_left, setback_right]: inp.change(fn=update_occupancy, inputs=[site_width, site_depth, setback_front, setback_back, setback_left, setback_right, spaces_state], outputs=[occupancy_info]) spaces_state.change(fn=update_occupancy, inputs=[site_width, site_depth, setback_front, setback_back, setback_left, setback_right, spaces_state], outputs=[occupancy_info]) sheet_preview_btn.click(fn=get_sheet_preview, inputs=[title_block_level, paper_size], outputs=[sheet_preview_text]) submit_btn.click(fn=process_form, inputs=[site_width, site_depth, setback_front, setback_back, setback_left, setback_right, corridor_width, spaces_state, project_name, client, project_address, drawing_title, drawing_code, sheet_number, scale, revision, designer, supervisor, title_block_level, paper_size], outputs=[status_output, preview_output, dxf_output, json_output]) if __name__ == "__main__": demo.launch(server_name="0.0.0.0", server_port=7860, theme=gr.themes.Soft())