File size: 4,748 Bytes
ce82348
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# 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