From 46045f0b0a646424ce9918c961140b3b92f493cd Mon Sep 17 00:00:00 2001 From: iv_vuytsik Date: Tue, 11 Nov 2025 18:32:36 +0300 Subject: [PATCH] Added more detectors to DB --- frontend/app/(protected)/navigation/page.tsx | 51 ++++++-- frontend/app/api/big-models/list/route.ts | 3 +- frontend/components/model/ModelViewer.tsx | 100 ++++++++++++--- .../components/navigation/DetectorMenu.tsx | 6 +- frontend/components/navigation/Monitoring.tsx | 114 ++++++++++-------- .../notifications/Notifications.tsx | 2 +- 6 files changed, 192 insertions(+), 84 deletions(-) diff --git a/frontend/app/(protected)/navigation/page.tsx b/frontend/app/(protected)/navigation/page.tsx index 69b5c03..36a7edb 100644 --- a/frontend/app/(protected)/navigation/page.tsx +++ b/frontend/app/(protected)/navigation/page.tsx @@ -95,10 +95,17 @@ const NavigationPage: React.FC = () => { }, []) const handleModelError = useCallback((error: string) => { - console.error('Model loading error:', error) + console.error('[NavigationPage] Model loading error:', error) setModelError(error) setIsModelReady(false) }, []) + + useEffect(() => { + if (selectedModelPath) { + setIsModelReady(false); + setModelError(null); + } + }, [selectedModelPath]); useEffect(() => { if (urlObjectId && urlObjectTitle && (!currentObject.id || currentObject.id !== urlObjectId)) { @@ -206,6 +213,7 @@ const NavigationPage: React.FC = () => { objectId={objectId || undefined} onClose={closeMonitoring} onSelectModel={(path) => { + console.log('[NavigationPage] Model selected:', path); setSelectedModelPath(path) setModelError(null) setIsModelReady(false) @@ -291,20 +299,37 @@ const NavigationPage: React.FC = () => {
{modelError ? ( -
-
-
- Ошибка загрузки 3D модели -
-
- {modelError} -
-
- Используйте навигацию по этажам для просмотра детекторов -
+ <> + {console.log('[NavigationPage] Rendering error message, modelError:', modelError)} +
+
+
+ Ошибка загрузки 3D модели +
+
+ {modelError} +
+
+ Используйте навигацию по этажам для просмотра детекторов
- ) : ( +
+ + ) : !selectedModelPath ? ( +
+
+
+ 3D модель не загружена +
+
+ Модель не готова к отображению +
+
+ Выберите модель из навигации по этажам +
+
+
+ ) : ( = ({ if (!isInitializedRef.current || isDisposedRef.current) { return } - - // Check if modelPath is provided - if (!modelPath) { + + if (!modelPath || modelPath.trim() === '') { console.warn('[ModelViewer] No model path provided') - onError?.('Путь к 3D модели не задан') + // Не вызываем onError для пустого пути - это нормальное состояние при инициализации + setIsLoading(false) return } @@ -158,13 +158,24 @@ const ModelViewer: React.FC = ({ return } - const oldMeshes = sceneRef.current.meshes.slice(); - oldMeshes.forEach(m => m.dispose()); - + const currentModelPath = modelPath; + console.log('[ModelViewer] Starting model load:', currentModelPath); + setIsLoading(true) setLoadingProgress(0) setShowModel(false) - console.log('Loading GLTF model:', modelPath) + setModelReady(false) + + const oldMeshes = sceneRef.current.meshes.slice(); + const activeCameraId = sceneRef.current.activeCamera?.uniqueId; + console.log('[ModelViewer] Cleaning up old meshes. Total:', oldMeshes.length); + oldMeshes.forEach(m => { + if (m.uniqueId !== activeCameraId) { + m.dispose(); + } + }); + + console.log('[ModelViewer] Loading GLTF model:', currentModelPath) // UI элемент загрузчика (есть эффект замедленности) const progressInterval = setInterval(() => { @@ -178,15 +189,44 @@ const ModelViewer: React.FC = ({ }, 100) try { - const result = await ImportMeshAsync(modelPath, sceneRef.current) - + console.log('[ModelViewer] Calling ImportMeshAsync with path:', currentModelPath); + + // Проверим доступность файла через fetch + try { + const testResponse = await fetch(currentModelPath, { method: 'HEAD' }); + console.log('[ModelViewer] File availability check:', { + url: currentModelPath, + status: testResponse.status, + statusText: testResponse.statusText, + ok: testResponse.ok + }); + } catch (fetchError) { + console.error('[ModelViewer] File fetch error:', fetchError); + } + + const result = await ImportMeshAsync(currentModelPath, sceneRef.current) + console.log('[ModelViewer] ImportMeshAsync completed successfully'); + console.log('[ModelViewer] Import result:', { + meshesCount: result.meshes.length, + particleSystemsCount: result.particleSystems.length, + skeletonsCount: result.skeletons.length, + animationGroupsCount: result.animationGroups.length + }); + + if (isDisposedRef.current || modelPath !== currentModelPath) { + console.log('[ModelViewer] Model loading aborted - model changed during load') + clearInterval(progressInterval) + setIsLoading(false) + return; + } + importedMeshesRef.current = result.meshes clearInterval(progressInterval) setLoadingProgress(100) - console.log('GLTF Model loaded successfully!') - console.log('ImportMeshAsync result:', result) + console.log('[ModelViewer] GLTF Model loaded successfully!', result) + if (result.meshes.length > 0) { const boundingBox = result.meshes[0].getHierarchyBoundingVectors() @@ -210,9 +250,11 @@ const ModelViewer: React.FC = ({ // Плавное появление модели setTimeout(() => { - if (!isDisposedRef.current) { + if (!isDisposedRef.current && modelPath === currentModelPath) { setShowModel(true) setIsLoading(false) + } else { + console.log('Model display aborted - model changed during animation') } }, 500) } else { @@ -222,9 +264,14 @@ const ModelViewer: React.FC = ({ } } catch (error) { clearInterval(progressInterval) - console.error('Error loading GLTF model:', error) - const errorMessage = error instanceof Error ? error.message : String(error) - onError?.(`Ошибка загрузки модели: ${errorMessage}`) + // Only report error if this loading is still relevant + if (!isDisposedRef.current && modelPath === currentModelPath) { + console.error('Error loading GLTF model:', error) + const errorMessage = error instanceof Error ? error.message : String(error) + onError?.(`Ошибка загрузки модели: ${errorMessage}`) + } else { + console.log('Error occurred but loading was aborted - model changed') + } setIsLoading(false) } } @@ -271,9 +318,26 @@ const ModelViewer: React.FC = ({ try { const meta: any = (m as any)?.metadata const extras: any = meta?.gltf?.extras ?? meta?.extras ?? (m as any)?.extras + const sid = extras?.Sensor_ID ?? extras?.sensor_id ?? extras?.SERIAL_NUMBER ?? extras?.serial_number - if (sid == null) return false - return String(sid).trim() === sensorId + if (sid != null) { + return String(sid).trim() === sensorId + } + + const monitoringSensorInstance = extras?.bonsaiPset_ARBM_PSet_MonitoringSensor_Instance + if (monitoringSensorInstance && typeof monitoringSensorInstance === 'string') { + try { + const parsedInstance = JSON.parse(monitoringSensorInstance) + const instanceSensorId = parsedInstance?.Sensor_ID + if (instanceSensorId != null) { + return String(instanceSensorId).trim() === sensorId + } + } catch (parseError) { + console.warn('[ModelViewer] Error parsing MonitoringSensor_Instance JSON:', parseError) + } + } + + return false } catch (error) { console.warn('[ModelViewer] Error matching sensor mesh:', error) return false diff --git a/frontend/components/navigation/DetectorMenu.tsx b/frontend/components/navigation/DetectorMenu.tsx index 2c6da2a..69a5b2d 100644 --- a/frontend/components/navigation/DetectorMenu.tsx +++ b/frontend/components/navigation/DetectorMenu.tsx @@ -166,7 +166,7 @@ const DetectorMenu: React.FC = ({ detector, isOpen, onClose, @@ -187,13 +187,13 @@ const DetectorMenu: React.FC = ({ detector, isOpen, onClose,
diff --git a/frontend/components/navigation/Monitoring.tsx b/frontend/components/navigation/Monitoring.tsx index 5ebebf4..fa8679b 100644 --- a/frontend/components/navigation/Monitoring.tsx +++ b/frontend/components/navigation/Monitoring.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useCallback } from 'react'; import Image from 'next/image'; interface MonitoringProps { @@ -12,6 +12,13 @@ const Monitoring: React.FC = ({ onClose, onSelectModel }) => { const [models, setModels] = useState<{ title: string; path: string }[]>([]); const [loadError, setLoadError] = useState(null); + const handleSelectModel = useCallback((modelPath: string) => { + console.log(`[NavigationPage] Model selected: ${modelPath}`); + onSelectModel?.(modelPath); + }, [onSelectModel]); + + console.log('[Monitoring] Models:', models, 'Error:', loadError); + // Загружаем список доступных моделей из assets/big-models через API useEffect(() => { const fetchModels = async () => { @@ -64,53 +71,29 @@ const Monitoring: React.FC = ({ onClose, onSelectModel }) => { )}
-
-
- {objectImageError ? ( -
-
- Предпросмотр 3D недоступен -
-
- Изображение модели не найдено -
-
- ) : ( - Object Model setObjectImageError(true)} - /> - )} -
-
- {loadError && (
Ошибка загрузки списка моделей: {loadError}
)} -
- {models.length > 0 ? ( - models.map((model, idx) => ( + {models.length > 0 && ( + <> + {/* Большая панорамная карточка для приоритетной модели */} + {models[0] && ( - )) - ) : ( -
-
- Список моделей пуст. Добавьте файлы в assets/big-models или проверьте API /api/big-models/list. + )} + + {/* Сетка маленьких карточек для остальных моделей */} + {models.length > 1 && ( +
+ {models.slice(1).map((model, idx) => ( + + ))}
+ )} + + )} + + {models.length === 0 && ( +
+
+ Список моделей пуст. Добавьте файлы в assets/big-models или проверьте API /api/big-models/list.
- )} -
+
+ )}
); diff --git a/frontend/components/notifications/Notifications.tsx b/frontend/components/notifications/Notifications.tsx index 93e3a33..a6f2116 100644 --- a/frontend/components/notifications/Notifications.tsx +++ b/frontend/components/notifications/Notifications.tsx @@ -71,7 +71,7 @@ const Notifications: React.FC = ({ objectId, detectorsData, // сортировка по objectId const filteredNotifications = objectId - ? allNotifications.filter(notification => notification.object.toString() === objectId.toString()) + ? allNotifications.filter(notification => notification.object && notification.object.toString() === objectId.toString()) : allNotifications // сортировка по timestamp