Розробка AI-системи генеративного проектування будівель Generative Design

Проектуємо та впроваджуємо системи штучного інтелекту: від прототипу до production-ready рішення. Наша команда поєднує експертизу в машинному навчанні, дата-інжинірингу та MLOps, щоб AI працював не в лабораторії, а в реальному бізнесі.
Показано 1 з 1Усі 1566 послуг
Розробка AI-системи генеративного проектування будівель Generative Design
Складний
від 2 тижнів до 3 місяців
Часті запитання

Напрямки AI-розробки

Етапи розробки AI-рішення

Останні роботи

  • image_website-b2b-advance_0.webp
    Розробка сайту компанії B2B ADVANCE
    1284
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1196
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    901
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1119
  • image_logo-advance_0.webp
    Розробка логотипу компанії B2B Advance
    586
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    853

AI-система генеративного проектування будинків

Генеративне проектування застосовує алгоритми оптимізації та нейронні мережі для автоматичного створення планувальних рішень, які задовольняють набір обмежень: площу, сонячну радіацію, будівельні норми, вартість. Архітектор визначає цілі та обмеження — система генерує десятки варіантів плану та ранжує їх за вказаними критеріями.

Архітектура генеративної системи

Параметри ділянки + будівельні норми + програма приміщень
    ↓ Задоволення обмежень → валідний простір рішень
    ↓ Генеративні алгоритми (еволюція / 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:
        """Кодуємо обмеження як вектор умовної обробки"""
        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:
        """Конвертуємо растровий план у векторні полігони приміщень через пошук контурів"""
        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 місяці.