Spyspook's picture
initial commit
ce82348 verified
# src/layout_generator/pipeline.py
"""
Оркестратор генерации 3D-планировок.
Собирает этапы в единый объектно-ориентированный пайплайн и прогоняет контекст.
Включает поддержку потоковой передачи статусов (streaming) для UI.
"""
import logging
import time
from pathlib import Path
from typing import List, Generator, Tuple, Optional, Dict, Any
from hydra import compose, initialize_config_dir
from hydra.core.config_store import ConfigStore
from omegaconf import DictConfig, OmegaConf
from dsynth.scene_gen.hydra_configs import DsConfig, ShelfConfig
from utils.logger import CollisionTracker
from .base import BaseStep, LayoutContext
from .assets import LoadAssetsStep
from .planner import CalculatePlanogramStep
from .topology import GenerateTopologyStep
from .assembler import AssembleSceneStep
from .exporter import ExportAndCompressStep
logger: logging.Logger = logging.getLogger(__name__)
def create_layout_pipeline() -> List[BaseStep]:
"""Фабрика для сборки стандартного пайплайна генерации."""
return [
LoadAssetsStep(),
CalculatePlanogramStep(),
GenerateTopologyStep(),
AssembleSceneStep(),
ExportAndCompressStep()
]
def generate_layout_stream(
project_dir: Path,
tracker: CollisionTracker,
size_n: int = 10,
size_m: int = 10,
layout_graph: Optional[Dict[str, Any]] = None
) -> Generator[Tuple[Optional[str], Dict[str, Any]], None, None]:
"""
Потоковая версия оркестратора.
Возвращает кортежи: (Путь_к_модели_или_None, Словарь_статуса_для_UI).
Работает исключительно на базе ИИ-графа (layout_graph).
"""
# Шаг 1: Инициализация (Шаг 0 зарезервирован для LLM в интерфейсе)
yield None, {"step_idx": 1, "status": "running"}
start_init = time.time()
conf_dir: str = str(project_dir / 'configs')
assets_dir: str = str(project_dir / 'assets')
cs: ConfigStore = ConfigStore.instance()
try:
cs.store(group="shelves", name="base_shelf_config", node=ShelfConfig)
cs.store(group="ds", name="main_darkstore_config_base", node=DsConfig)
except Exception:
pass
logger.info(f"🏗 ЗАДАЧА ПЛАНОГРАММИРОВАНИЯ: Помещение {size_n}x{size_m}")
with initialize_config_dir(version_base=None, config_dir=conf_dir):
cfg: DictConfig = compose(config_name="main_config", overrides=[f"ds.size_n={size_n}", f"ds.size_m={size_m}"])
OmegaConf.set_struct(cfg, False)
cfg.assets.assets_dir_path = assets_dir
OmegaConf.resolve(cfg)
context = LayoutContext(
project_dir=str(project_dir),
size_n=size_n, size_m=size_m,
layout_graph=layout_graph,
cfg=cfg, tracker=tracker
)
elapsed_init = time.time() - start_init
yield None, {"step_idx": 1, "status": "done", "time": elapsed_init}
pipeline = create_layout_pipeline()
# Шаги 2-6: Прогоняем пайплайн с замером времени
for idx, step in enumerate(pipeline):
step_idx = idx + 2
yield None, {"step_idx": step_idx, "status": "running"}
start_t = time.time()
context = step.process(context)
elapsed_t = time.time() - start_t
# Обработка критических ошибок на лету
if not context.product_assets_lib:
yield None, {"step_idx": step_idx, "status": "error", "message": "В кэше отсутствуют модели"}
return
if isinstance(step, GenerateTopologyStep) and not context.is_gen:
yield None, {"step_idx": step_idx, "status": "error", "message": "Помещение слишком мало"}
return
yield None, {"step_idx": step_idx, "status": "done", "time": elapsed_t}
yield context.output_filename, {"status": "finished"}
def generate_inventory_driven_layout(
project_dir: Path,
tracker: CollisionTracker,
size_n: int = 10,
size_m: int = 10
) -> str:
"""Обычная функция для обратной совместимости."""
final_path = ""
for path, _ in generate_layout_stream(
project_dir, tracker, size_n, size_m
):
if path:
final_path = path
return final_path