import { Canvas } from "@react-three/fiber"; import { OrbitControls, Stage, useGLTF } from "@react-three/drei"; import { useEffect, Suspense } from "react"; import { Box, CircularProgress, Typography } from "@mui/material"; // Утилита для очистки кеша GLTF const clearGLTFCache = (url?: string) => { try { if (url) { // Если это blob URL, очищаем его из кеша if (url.startsWith("blob:")) { useGLTF.clear(url); } else { useGLTF.clear(url); } } } catch (error) { console.warn("⚠️ clearGLTFCache: Ошибка при очистке кеша", error); } }; // Утилита для проверки типа файла const isValid3DFile = (url: string): boolean => { try { const urlObj = new URL(url); const pathname = urlObj.pathname.toLowerCase(); const searchParams = urlObj.searchParams; // Проверяем расширение файла в пути const validExtensions = [".glb", ".gltf"]; const hasValidExtension = validExtensions.some((ext) => pathname.endsWith(ext) ); // Проверяем параметры запроса на наличие типа файла const fileType = searchParams.get("type") || searchParams.get("format"); const hasValidType = fileType && ["glb", "gltf"].includes(fileType.toLowerCase()); // Если это blob URL, считаем его валидным (пользователь выбрал файл) const isBlobUrl = url.startsWith("blob:"); // Если это URL с токеном и нет явного расширения, считаем валидным // (предполагаем что сервер вернет правильный файл) const hasToken = searchParams.has("token"); const isServerUrl = hasToken && !hasValidExtension; const isValid = hasValidExtension || hasValidType || isBlobUrl || isServerUrl; return isValid; } catch (error) { console.warn("⚠️ isValid3DFile: Ошибка при проверке URL", error); // В случае ошибки парсинга URL, считаем валидным (пусть useGLTF сам разберется) return true; } }; type ModelViewerProps = { width?: string; fileUrl: string; height?: string; }; const Model = ({ fileUrl }: { fileUrl: string }) => { // Очищаем кеш перед загрузкой новой модели useEffect(() => { // Очищаем кеш для текущего URL clearGLTFCache(fileUrl); }, [fileUrl]); // Проверяем валидность файла перед загрузкой (только для blob URL) if (fileUrl.startsWith("blob:") && !isValid3DFile(fileUrl)) { console.warn("⚠️ Model: Попытка загрузить невалидный 3D файл", { fileUrl }); throw new Error(`Файл не является корректной 3D моделью: ${fileUrl}`); } const { scene } = useGLTF(fileUrl); return ; }; const LoadingFallback = () => { return ( Загрузка 3D модели... ); }; export const ThreeView = ({ fileUrl, height = "100%", width = "100%", }: ModelViewerProps) => { // Проверяем валидность файла (только для blob URL) useEffect(() => { if (fileUrl.startsWith("blob:") && !isValid3DFile(fileUrl)) { console.warn("⚠️ ThreeView: Невалидный 3D файл", { fileUrl }); } }, [fileUrl]); // Очищаем кеш при размонтировании и при смене URL useEffect(() => { // Очищаем кеш сразу при монтировании компонента clearGLTFCache(fileUrl); return () => { clearGLTFCache(fileUrl); }; }, [fileUrl]); return ( }> ); };