# src/layout_generator/exporter.py """ Шаг пайплайна для экспорта 3D-сцены и Draco-компрессии. """ import logging import os import subprocess from pathlib import Path from .base import BaseStep, LayoutContext logger: logging.Logger = logging.getLogger(__name__) class ExportAndCompressStep(BaseStep): """Сохраняет сцену на жесткий диск и сжимает для веб-рендера.""" def process(self, context: LayoutContext) -> LayoutContext: if not context.final_scene: return context project_dir_path: Path = Path(context.project_dir) data_dir: Path = project_dir_path / "data" data_dir.mkdir(parents=True, exist_ok=True) output_filename: str = str(data_dir / f"inventory_layout_{context.size_n}x{context.size_m}.glb") uncompressed_filename: str = str(data_dir / f"uncompressed_layout_{context.size_n}x{context.size_m}_{os.getpid()}.glb") context.final_scene.export(uncompressed_filename) logger.info("🗜️ Сжимаем геометрию с помощью Draco (gltf-pipeline)...") try: subprocess.run( ["gltf-pipeline", "-i", uncompressed_filename, "-o", output_filename, "-d"], check=True, capture_output=True ) old_size: float = os.path.getsize(uncompressed_filename) / (1024 * 1024) new_size: float = os.path.getsize(output_filename) / (1024 * 1024) logger.info(f"✅ Draco-компрессия завершена! Размер уменьшен с {old_size:.1f} MB до {new_size:.1f} MB.") if os.path.exists(uncompressed_filename): os.remove(uncompressed_filename) except FileNotFoundError: logger.warning("⚠️ Утилита gltf-pipeline не найдена. Сохраняем тяжелый несжатый файл.") os.rename(uncompressed_filename, output_filename) except Exception as e: logger.warning(f"⚠️ Ошибка при сжатии Draco ({e}). Сохраняем несжатый файл как запасной вариант.") if os.path.exists(output_filename): os.remove(output_filename) os.rename(uncompressed_filename, output_filename) context.output_filename = output_filename # --- Генерация финального лог-отчета --- failed_count: int = len(context.tracker.failed_items) if context.tracker else 0 # Безопасный подсчет запланированных товаров (устойчив к изменениям формата отчета) scheduled_total: int = 0 for profile in context.report_matrix.values(): if isinstance(profile, dict): scheduled_total += profile.get("total", 0) elif isinstance(profile, int): scheduled_total += profile logger.info("=" * 60) logger.info("📋 ОТЧЕТ: АНАЛИЗ ПЛАНОГРАММЫ И ВМЕСТИМОСТИ") logger.info(f"Помещение: {context.size_n}x{context.size_m}. Успешно построено стеллажей: {context.actual_shelf_idx} шт.") logger.info(f"Вместилось на полки (запланировано): {scheduled_total} SKU") logger.info(f"Физически размещено без коллизий: {scheduled_total - failed_count} SKU") if failed_count > 0: logger.warning(f"⚠️ Упало с полок (ошибка физики движка): {failed_count} SKU") logger.info("=" * 60) return context