Spaces:
Sleeping
Sleeping
| # 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 |