Improved authentication; added fallbacks to 3D; cleaner dashboard charts
This commit is contained in:
@@ -141,7 +141,14 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
}, [])
|
||||
|
||||
useEffect(() => {
|
||||
if (!isInitializedRef.current || !modelPath || isDisposedRef.current) {
|
||||
if (!isInitializedRef.current || isDisposedRef.current) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check if modelPath is provided
|
||||
if (!modelPath) {
|
||||
console.warn('[ModelViewer] No model path provided')
|
||||
onError?.('Путь к 3D модели не задан')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -209,13 +216,14 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
}, 500)
|
||||
} else {
|
||||
console.warn('No meshes found in model')
|
||||
onError?.('No geometry found in model')
|
||||
onError?.('В модели не найдена геометрия')
|
||||
setIsLoading(false)
|
||||
}
|
||||
} catch (error) {
|
||||
clearInterval(progressInterval)
|
||||
console.error('Error loading GLTF model:', error)
|
||||
onError?.(`Failed to load model: ${error}`)
|
||||
const errorMessage = error instanceof Error ? error.message : String(error)
|
||||
onError?.(`Ошибка загрузки модели: ${errorMessage}`)
|
||||
setIsLoading(false)
|
||||
}
|
||||
}
|
||||
@@ -239,7 +247,25 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
}
|
||||
|
||||
const allMeshes = importedMeshesRef.current || []
|
||||
const sensorMeshes = allMeshes.filter((m: any) => ((m.id ?? '').includes('IfcSensor') || (m.name ?? '').includes('IfcSensor')))
|
||||
|
||||
// Safeguard: Check if we have any meshes at all
|
||||
if (allMeshes.length === 0) {
|
||||
console.warn('[ModelViewer] No meshes available for sensor matching')
|
||||
highlightLayerRef.current?.removeAllMeshes()
|
||||
chosenMeshRef.current = null
|
||||
setOverlayPos(null)
|
||||
setOverlayData(null)
|
||||
return
|
||||
}
|
||||
|
||||
const sensorMeshes = allMeshes.filter((m: any) => {
|
||||
try {
|
||||
return ((m.id ?? '').includes('IfcSensor') || (m.name ?? '').includes('IfcSensor'))
|
||||
} catch (error) {
|
||||
console.warn('[ModelViewer] Error filtering sensor mesh:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
const chosen = sensorMeshes.find((m: any) => {
|
||||
try {
|
||||
@@ -248,7 +274,8 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
const sid = extras?.Sensor_ID ?? extras?.sensor_id ?? extras?.SERIAL_NUMBER ?? extras?.serial_number
|
||||
if (sid == null) return false
|
||||
return String(sid).trim() === sensorId
|
||||
} catch {
|
||||
} catch (error) {
|
||||
console.warn('[ModelViewer] Error matching sensor mesh:', error)
|
||||
return false
|
||||
}
|
||||
})
|
||||
@@ -264,44 +291,52 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
const scene = sceneRef.current!
|
||||
|
||||
if (chosen) {
|
||||
const camera = scene.activeCamera as ArcRotateCamera
|
||||
const bbox = (typeof chosen.getHierarchyBoundingVectors === 'function')
|
||||
? chosen.getHierarchyBoundingVectors()
|
||||
: { min: chosen.getBoundingInfo().boundingBox.minimumWorld, max: chosen.getBoundingInfo().boundingBox.maximumWorld }
|
||||
const center = bbox.min.add(bbox.max).scale(0.5)
|
||||
const size = bbox.max.subtract(bbox.min)
|
||||
const maxDimension = Math.max(size.x, size.y, size.z)
|
||||
const targetRadius = Math.max(camera.lowerRadiusLimit ?? 2, maxDimension * 1.5)
|
||||
try {
|
||||
const camera = scene.activeCamera as ArcRotateCamera
|
||||
const bbox = (typeof chosen.getHierarchyBoundingVectors === 'function')
|
||||
? chosen.getHierarchyBoundingVectors()
|
||||
: { min: chosen.getBoundingInfo().boundingBox.minimumWorld, max: chosen.getBoundingInfo().boundingBox.maximumWorld }
|
||||
const center = bbox.min.add(bbox.max).scale(0.5)
|
||||
const size = bbox.max.subtract(bbox.min)
|
||||
const maxDimension = Math.max(size.x, size.y, size.z)
|
||||
const targetRadius = Math.max(camera.lowerRadiusLimit ?? 2, maxDimension * 1.5)
|
||||
|
||||
scene.stopAnimation(camera)
|
||||
scene.stopAnimation(camera)
|
||||
|
||||
const ease = new CubicEase()
|
||||
ease.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT)
|
||||
const frameRate = 60
|
||||
const durationMs = 600
|
||||
const totalFrames = Math.round((durationMs / 1000) * frameRate)
|
||||
const ease = new CubicEase()
|
||||
ease.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT)
|
||||
const frameRate = 60
|
||||
const durationMs = 600
|
||||
const totalFrames = Math.round((durationMs / 1000) * frameRate)
|
||||
|
||||
Animation.CreateAndStartAnimation('camTarget', camera, 'target', frameRate, totalFrames, camera.target.clone(), center.clone(), Animation.ANIMATIONLOOPMODE_CONSTANT, ease)
|
||||
Animation.CreateAndStartAnimation('camRadius', camera, 'radius', frameRate, totalFrames, camera.radius, targetRadius, Animation.ANIMATIONLOOPMODE_CONSTANT, ease)
|
||||
Animation.CreateAndStartAnimation('camTarget', camera, 'target', frameRate, totalFrames, camera.target.clone(), center.clone(), Animation.ANIMATIONLOOPMODE_CONSTANT, ease)
|
||||
Animation.CreateAndStartAnimation('camRadius', camera, 'radius', frameRate, totalFrames, camera.radius, targetRadius, Animation.ANIMATIONLOOPMODE_CONSTANT, ease)
|
||||
|
||||
const hl = highlightLayerRef.current
|
||||
if (hl) {
|
||||
hl.removeAllMeshes()
|
||||
if (chosen instanceof Mesh) {
|
||||
hl.addMesh(chosen, new Color3(1, 1, 0))
|
||||
} else if (chosen instanceof InstancedMesh) {
|
||||
hl.addMesh(chosen.sourceMesh, new Color3(1, 1, 0))
|
||||
} else {
|
||||
const children = typeof (chosen as any)?.getChildMeshes === 'function' ? (chosen as any).getChildMeshes() : []
|
||||
for (const cm of children) {
|
||||
if (cm instanceof Mesh) {
|
||||
hl.addMesh(cm, new Color3(1, 1, 0))
|
||||
const hl = highlightLayerRef.current
|
||||
if (hl) {
|
||||
hl.removeAllMeshes()
|
||||
if (chosen instanceof Mesh) {
|
||||
hl.addMesh(chosen, new Color3(1, 1, 0))
|
||||
} else if (chosen instanceof InstancedMesh) {
|
||||
hl.addMesh(chosen.sourceMesh, new Color3(1, 1, 0))
|
||||
} else {
|
||||
const children = typeof (chosen as any)?.getChildMeshes === 'function' ? (chosen as any).getChildMeshes() : []
|
||||
for (const cm of children) {
|
||||
if (cm instanceof Mesh) {
|
||||
hl.addMesh(cm, new Color3(1, 1, 0))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
chosenMeshRef.current = chosen
|
||||
setOverlayData({ name: chosen.name, sensorId })
|
||||
} catch (error) {
|
||||
console.error('[ModelViewer] Error focusing on sensor mesh:', error)
|
||||
highlightLayerRef.current?.removeAllMeshes()
|
||||
chosenMeshRef.current = null
|
||||
setOverlayPos(null)
|
||||
setOverlayData(null)
|
||||
}
|
||||
chosenMeshRef.current = chosen
|
||||
setOverlayData({ name: chosen.name, sensorId })
|
||||
} else {
|
||||
highlightLayerRef.current?.removeAllMeshes()
|
||||
chosenMeshRef.current = null
|
||||
@@ -316,15 +351,22 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
const observer = scene.onAfterRenderObservable.add(() => {
|
||||
const chosen = chosenMeshRef.current
|
||||
if (!chosen) return
|
||||
const engine = scene.getEngine()
|
||||
const cam = scene.activeCamera
|
||||
if (!cam) return
|
||||
const center = chosen.getBoundingInfo().boundingBox.centerWorld
|
||||
const world = Matrix.IdentityReadOnly
|
||||
const transform = scene.getTransformMatrix()
|
||||
const viewport = new Viewport(0, 0, engine.getRenderWidth(), engine.getRenderHeight())
|
||||
const projected = Vector3.Project(center, world, transform, viewport)
|
||||
setOverlayPos({ left: projected.x, top: projected.y })
|
||||
|
||||
try {
|
||||
const engine = scene.getEngine()
|
||||
const cam = scene.activeCamera
|
||||
if (!cam) return
|
||||
|
||||
const center = chosen.getBoundingInfo().boundingBox.centerWorld
|
||||
const world = Matrix.IdentityReadOnly
|
||||
const transform = scene.getTransformMatrix()
|
||||
const viewport = new Viewport(0, 0, engine.getRenderWidth(), engine.getRenderHeight())
|
||||
const projected = Vector3.Project(center, world, transform, viewport)
|
||||
setOverlayPos({ left: projected.x, top: projected.y })
|
||||
} catch (error) {
|
||||
console.warn('[ModelViewer] Error updating overlay position:', error)
|
||||
setOverlayPos(null)
|
||||
}
|
||||
})
|
||||
return () => {
|
||||
scene.onAfterRenderObservable.remove(observer)
|
||||
@@ -333,20 +375,50 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
|
||||
return (
|
||||
<div className="w-full h-screen relative bg-gray-900 overflow-hidden">
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className={`w-full h-full outline-none block transition-opacity duration-500 ${
|
||||
showModel ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
/>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 bg-gray-900 flex items-center justify-center z-50">
|
||||
<LoadingSpinner
|
||||
progress={loadingProgress}
|
||||
size={120}
|
||||
strokeWidth={8}
|
||||
/>
|
||||
{!modelPath ? (
|
||||
<div className="h-full flex items-center justify-center">
|
||||
<div className="text-center p-8 bg-[#161824] rounded-lg border border-gray-700 max-w-md">
|
||||
<div className="text-amber-400 text-lg font-semibold mb-4">
|
||||
3D модель недоступна
|
||||
</div>
|
||||
<div className="text-gray-300 mb-4">
|
||||
Путь к 3D модели не задан
|
||||
</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
Обратитесь к администратору для настройки модели
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className={`w-full h-full outline-none block transition-opacity duration-500 ${
|
||||
showModel ? 'opacity-100' : 'opacity-0'
|
||||
}`}
|
||||
/>
|
||||
{isLoading && (
|
||||
<div className="absolute inset-0 bg-gray-900 flex items-center justify-center z-50">
|
||||
<LoadingSpinner
|
||||
progress={loadingProgress}
|
||||
size={120}
|
||||
strokeWidth={8}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
{!modelReady && !isLoading && (
|
||||
<div className="absolute inset-0 bg-gray-900 flex items-center justify-center z-40">
|
||||
<div className="text-center p-8 bg-[#161824] rounded-lg border border-gray-700 max-w-md">
|
||||
<div className="text-gray-400 text-lg font-semibold mb-4">
|
||||
3D модель не загружена
|
||||
</div>
|
||||
<div className="text-sm text-gray-400">
|
||||
Модель не готова к отображению
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
{renderOverlay
|
||||
? renderOverlay({ anchor: overlayPos, info: overlayData })
|
||||
@@ -357,7 +429,8 @@ const ModelViewer: React.FC<ModelViewerProps> = ({
|
||||
{overlayData.sensorId && <div className="opacity-80">ID: {overlayData.sensorId}</div>}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user