AI-система генеративного проектирования зданий
Генеративное проектирование применяет алгоритмы оптимизации и нейронные сети для автоматического создания планировочных решений, удовлетворяющих набору ограничений: площадь, инсоляция, нормативы, стоимость. Архитектор задаёт цели и ограничения — система генерирует десятки вариантов плана и ранжирует по заданным критериям.
Архитектура генеративной системы
Параметры участка + нормативы + программа помещений
↓ Constraint satisfaction → допустимое пространство вариантов
↓ Генеративные алгоритмы (эволюция / GAN / диффузия)
↓ Оценщик (инсоляция, площади, циркуляция, стоимость)
↓ Парето-фронт лучших вариантов
↓ Экспорт в IFC / DXF / Revit API
Параметрическая генерация планировки
from dataclasses import dataclass, field
from typing import Callable
import numpy as np
from scipy.optimize import differential_evolution
@dataclass
class BuildingConstraints:
site_polygon: list[tuple] # координаты участка
total_area: float # общая площадь, кв.м
floors: int
rooms: list[dict] # [{"name": "living", "min_area": 20, "max_area": 40, "count": 1}]
orientation_north: float # угол севера в градусах
setbacks: dict # {"front": 5, "side": 3, "rear": 5} — отступы, м
max_height: float # макс. высота, м
accessibility: bool = True # нормы доступности (пандусы, лифты)
@dataclass
class OptimizationWeights:
daylight: float = 0.3 # инсоляция
area_efficiency: float = 0.25 # КПД площади (жилая / общая)
circulation: float = 0.2 # удобство циркуляции
cost: float = 0.25 # удельная стоимость строительства
class FloorPlanOptimizer:
def __init__(
self,
constraints: BuildingConstraints,
weights: OptimizationWeights
):
self.constraints = constraints
self.weights = weights
def decode_chromosome(self, x: np.ndarray) -> dict:
"""Преобразуем вектор параметров в план этажа"""
rooms = []
idx = 0
for room_spec in self.constraints.rooms:
rooms.append({
"name": room_spec["name"],
"x": x[idx] * self.constraints.total_area**0.5,
"y": x[idx+1] * self.constraints.total_area**0.5,
"width": room_spec["min_area"]**0.5 + x[idx+2] * (
room_spec["max_area"]**0.5 - room_spec["min_area"]**0.5
),
"height": room_spec["min_area"]**0.5 + x[idx+3] * (
room_spec["max_area"]**0.5 - room_spec["min_area"]**0.5
)
})
idx += 4
return {"rooms": rooms}
def evaluate_daylight(self, plan: dict) -> float:
"""Оцениваем инсоляцию через упрощённую модель ориентации окон"""
score = 0.0
south_angle = (180 - self.constraints.orientation_north) % 360
for room in plan["rooms"]:
# Комнаты с южной ориентацией получают больший вес
room_angle = np.degrees(np.arctan2(
room["y"] - self.constraints.total_area**0.5 / 2,
room["x"] - self.constraints.total_area**0.5 / 2
)) % 360
angular_diff = abs(room_angle - south_angle)
score += 1 - min(angular_diff, 360 - angular_diff) / 180
return score / len(plan["rooms"])
def evaluate_area_efficiency(self, plan: dict) -> float:
total_room_area = sum(r["width"] * r["height"] for r in plan["rooms"])
return min(total_room_area / self.constraints.total_area, 1.0)
def fitness(self, x: np.ndarray) -> float:
"""Целевая функция — минимизируем (инвертируем максимизацию)"""
plan = self.decode_chromosome(x)
w = self.weights
score = (
w.daylight * self.evaluate_daylight(plan) +
w.area_efficiency * self.evaluate_area_efficiency(plan)
# + w.circulation * evaluate_circulation(plan)
# + w.cost * (1 - evaluate_cost(plan))
)
return -score # scipy минимизирует
def generate_variants(self, n_variants: int = 20) -> list[dict]:
n_params = len(self.constraints.rooms) * 4
bounds = [(0, 1)] * n_params
# Дифференциальная эволюция — надёжно для многомодальных задач
results = []
for seed in range(n_variants):
result = differential_evolution(
self.fitness, bounds,
seed=seed, maxiter=500, tol=0.001,
popsize=15, mutation=(0.5, 1.0), recombination=0.7
)
plan = self.decode_chromosome(result.x)
plan["score"] = -result.fun
plan["seed"] = seed
results.append(plan)
return sorted(results, key=lambda p: p["score"], reverse=True)
Нейросетевой генератор планов (диффузионная модель)
import torch
from diffusers import UNet2DConditionModel, DDPMScheduler
class FloorPlanDiffusion:
"""
Диффузионная модель, обученная на датасете планировок (RPLAN, LifeHD).
Генерирует растровое представление плана 256×256, затем векторизуем.
"""
def __init__(self, model_path: str):
self.unet = UNet2DConditionModel.from_pretrained(model_path)
self.scheduler = DDPMScheduler.from_pretrained(model_path)
self.device = "cuda" if torch.cuda.is_available() else "cpu"
self.unet.to(self.device)
def encode_constraints(self, constraints: BuildingConstraints) -> torch.Tensor:
"""Кодируем ограничения как conditioning вектор"""
features = [
constraints.total_area / 1000, # нормализуем
constraints.floors / 10,
constraints.orientation_north / 360,
len(constraints.rooms) / 20,
float(constraints.accessibility)
]
return torch.tensor(features, dtype=torch.float32).unsqueeze(0).to(self.device)
@torch.no_grad()
def generate(self, constraints: BuildingConstraints, num_samples: int = 8) -> list[np.ndarray]:
condition = self.encode_constraints(constraints)
noise = torch.randn(num_samples, 1, 256, 256).to(self.device)
for t in self.scheduler.timesteps:
noise_pred = self.unet(noise, t, encoder_hidden_states=condition.expand(num_samples, -1, -1)).sample
noise = self.scheduler.step(noise_pred, t, noise).prev_sample
plans = noise.squeeze(1).cpu().numpy()
return [self.rasterize_to_vector(p) for p in plans]
def rasterize_to_vector(self, raster: np.ndarray) -> dict:
"""Конвертируем растровый план в векторные полигоны комнат через contour finding"""
import cv2
binary = (raster > 0.5).astype(np.uint8) * 255
contours, _ = cv2.findContours(binary, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
rooms = []
for cnt in contours:
approx = cv2.approxPolyDP(cnt, 0.02 * cv2.arcLength(cnt, True), True)
rooms.append({"polygon": approx.reshape(-1, 2).tolist()})
return {"rooms": rooms}
Оценка инсоляции через EnergyPlus
import subprocess
import json
def evaluate_solar_radiation(
plan: dict,
location: dict, # {"lat": 55.75, "lon": 37.62, "timezone": "Europe/Moscow"}
month: int = 6 # Июнь — расчётный месяц для инсоляции
) -> dict:
"""
Запускаем EnergyPlus IDF симуляцию для оценки инсоляции.
Альтернатива: LADYBUG Tools / Grasshopper API для Rhino-интеграции.
"""
idf_content = generate_energyplus_idf(plan, location, month)
with open("/tmp/floorplan.idf", "w") as f:
f.write(idf_content)
result = subprocess.run([
"energyplus", "-w", f"weather/{location['timezone']}.epw",
"-d", "/tmp/ep_output", "/tmp/floorplan.idf"
], capture_output=True)
return parse_energyplus_results("/tmp/ep_output/floorplan.csv")
Экспорт в IFC и DXF
import ifcopenshell
import ifcopenshell.api
def export_to_ifc(plan: dict, project_name: str) -> bytes:
"""BIM-совместимый экспорт по стандарту IFC 4.3"""
model = ifcopenshell.file()
project = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcProject", name=project_name)
site = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSite")
building = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcBuilding")
for room_data in plan["rooms"]:
space = ifcopenshell.api.run("root.create_entity", model, ifc_class="IfcSpace", name=room_data["name"])
# Добавляем геометрию из полигона
coordinates = [(p[0], p[1], 0.0) for p in room_data["polygon"]]
ifcopenshell.api.run("geometry.add_wall_representation", model, product=space, coordinates=coordinates)
import io
buf = io.BytesIO()
model.write(buf)
return buf.getvalue()
def export_to_dxf(plan: dict) -> bytes:
import ezdxf
doc = ezdxf.new(dxfversion="R2010")
msp = doc.modelspace()
for room_data in plan["rooms"]:
polygon = room_data["polygon"]
msp.add_lwpolyline(polygon, close=True, dxfattribs={"layer": room_data.get("name", "ROOMS")})
buf = io.BytesIO()
doc.write(buf)
return buf.getvalue()
Интеграция с Revit через Dynamo / Revit API
Для команд, работающих в Autodesk Revit, генерируемые планы импортируются через Dynamo-скрипты: система передаёт координаты помещений через JSON-файл, Dynamo-скрипт создаёт комнаты, стены и перегородки в модели BIM. Альтернатива — Rhino + Grasshopper с компонентом GhPython, где оптимизатор запускается прямо из параметрического окружения.
Сравнение подходов к генерации
| Подход | Скорость генерации | Качество | Обучение | Применение |
|---|---|---|---|---|
| Дифференциальная эволюция | 30–60 сек | Высокое | Нет | Параметрическая оптимизация |
| GAN (LayoutGAN++) | 0.5–2 сек | Среднее | Датасет 10k планов | Быстрые варианты |
| Диффузионная модель | 10–30 сек | Высокое | Датасет 50k планов | Детализированные планы |
| LLM + SVG | 5–15 сек | Низкое | Нет | Концептуальные схемы |
Система генерации планировок с дифференциальной эволюцией и IFC-экспортом — 4–6 недель. Платформа с диффузионной моделью, расчётом инсоляции через EnergyPlus и интеграцией с Revit — 3–4 месяца.







