feat: 算法输出文件measurement.json接口调试
This commit is contained in:
parent
db8ba53589
commit
595e144a13
|
@ -58,4 +58,10 @@ pnpm config set virtual-store-dir-max-length 70
|
|||
- CORONAL(冠状位):
|
||||
• 方向:冠状面,与矢状面垂直,沿身体的左右方向。
|
||||
• 描述:将身体分为前(腹侧)和后(背侧)部分。
|
||||
• 应用:适用于查看从前面到后面的结构,如面部和脑部的解剖。
|
||||
• 应用:适用于查看从前面到后面的结构,如面部和脑部的解剖。
|
||||
|
||||
## 区分片子的类型
|
||||
|
||||
头部扫描:通常总长度在100毫米以下。
|
||||
主动脉根部平扫:总长度通常在100毫米到200毫米之间。
|
||||
腹部扫描:总长度通常超过200毫米。
|
|
@ -179,3 +179,57 @@ export const downloadSeriesDicomFiles = async (
|
|||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export /**
|
||||
* 根据SeriesInstanceUID获取总的扫描长度
|
||||
* @param seriesInstanceUID 序列实例UID
|
||||
* @returns 返回总的扫描长度(单位:毫米)
|
||||
*/
|
||||
const getTotalScanLength = async (
|
||||
seriesInstanceUID: string
|
||||
): Promise<number | null> => {
|
||||
try {
|
||||
// 1. 查找序列ID
|
||||
const findResponse = await axios.post(`${OrthancServerRoot}/tools/find`, {
|
||||
Level: "Series",
|
||||
Query: {
|
||||
SeriesInstanceUID: seriesInstanceUID,
|
||||
},
|
||||
});
|
||||
const seriesIds = findResponse.data;
|
||||
if (seriesIds.length === 0) {
|
||||
console.error("Series not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
const seriesId = seriesIds[0];
|
||||
|
||||
// 2. 获取共享标签,提取SliceThickness
|
||||
const tagsResponse = await axios.get(
|
||||
`${OrthancServerRoot}/series/${seriesId}/shared-tags`
|
||||
);
|
||||
const tags = tagsResponse.data;
|
||||
const sliceThickness = parseFloat(tags["0018,0050"]["Value"]); // (0018,0050) SliceThickness
|
||||
|
||||
if (isNaN(sliceThickness)) {
|
||||
console.error("SliceThickness not found");
|
||||
return null;
|
||||
}
|
||||
|
||||
// 3. 获取实例列表,计算层数
|
||||
const instancesResponse = await axios.get(
|
||||
`${OrthancServerRoot}/series/${seriesId}/instances`
|
||||
);
|
||||
const instances = instancesResponse.data;
|
||||
const numberOfSlices = instances.length;
|
||||
console.log("numberOfSlices", numberOfSlices);
|
||||
console.log("sliceThickness", sliceThickness);
|
||||
|
||||
// 4. 计算总的扫描长度
|
||||
const totalLength = Number(sliceThickness) * numberOfSlices;
|
||||
return totalLength;
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import { downloadSeriesDicomFiles } from "../../core/pacs";
|
||||
import { downloadSeriesDicomFiles, getTotalScanLength } from "../../core/pacs";
|
||||
import { executeInferTask } from "../../core/alg";
|
||||
import { InferDeviceEnum, InferStructuralEnum } from "../../core/alg.type";
|
||||
import { db } from "../../core/db";
|
||||
import log from "electron-log";
|
||||
import path from "node:path";
|
||||
import { app, ipcMain } from "electron";
|
||||
import { findSTLFiles } from "./util";
|
||||
import { readFileSync } from "node:fs";
|
||||
|
||||
export const registerAlgHandler = () => {
|
||||
ipcMain.handle("device:infer:set", async (_event, inferDevice) => {
|
||||
|
@ -21,11 +23,16 @@ export const registerAlgHandler = () => {
|
|||
ipcMain.on("model:infer", async (event, SeriesInstanceUIDs) => {
|
||||
// 构造推理任务参数列表
|
||||
const pu = InferDeviceEnum.GPU;
|
||||
const module = InferStructuralEnum.AORTA;
|
||||
|
||||
const turbo = true;
|
||||
const seg_schedule = true;
|
||||
for (let i = 0; i < SeriesInstanceUIDs.length; i++) {
|
||||
const SeriesInstanceUID = SeriesInstanceUIDs[i];
|
||||
const physicalLength = await getTotalScanLength(SeriesInstanceUID);
|
||||
const module =
|
||||
physicalLength && physicalLength < 200
|
||||
? InferStructuralEnum.AORTA
|
||||
: InferStructuralEnum.PERI;
|
||||
// 下载dicom到本地,获取文件夹路径
|
||||
const img_path = await downloadSeriesDicomFiles(SeriesInstanceUID);
|
||||
const save_path = path.join(
|
||||
|
@ -42,12 +49,38 @@ export const registerAlgHandler = () => {
|
|||
}
|
||||
});
|
||||
|
||||
ipcMain.handle("alg:assets", (_event, SeriesInstanceUID) => {
|
||||
const assetsPath = path.join(
|
||||
ipcMain.handle("alg:assets", async (_event, SeriesInstanceUID) => {
|
||||
const physicalLength = await getTotalScanLength(SeriesInstanceUID);
|
||||
const module =
|
||||
physicalLength && physicalLength < 200
|
||||
? InferStructuralEnum.AORTA
|
||||
: InferStructuralEnum.PERI;
|
||||
const rootPath = path.join(
|
||||
app.getPath("userData"),
|
||||
"output",
|
||||
SeriesInstanceUID
|
||||
);
|
||||
console.log("assetsPath", assetsPath);
|
||||
const stlsPath = path.join(rootPath, module, "visualization");
|
||||
const stls = await findSTLFiles(stlsPath);
|
||||
if (stls.length > 0) {
|
||||
try {
|
||||
// 读取测量json
|
||||
const measurementPath = path.join(rootPath, module, "measurement.json");
|
||||
const measurementData = readFileSync(measurementPath, "utf-8");
|
||||
// 读取 STL 文件并转换为 ArrayBuffer
|
||||
const stlFiles = stls.map((file) => {
|
||||
const filePath = path.join(stlsPath, file);
|
||||
const data = readFileSync(filePath);
|
||||
return {
|
||||
fileName: file,
|
||||
data: data.buffer,
|
||||
};
|
||||
});
|
||||
return { stlFiles, measurement: JSON.parse(measurementData) };
|
||||
} catch (error) {
|
||||
console.error("Error reading measurement.json:", error);
|
||||
throw new Error("Failed to read or parse measurement.json");
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
30
apps/desktop/electron/ipcEvent/alg/util.ts
Normal file
30
apps/desktop/electron/ipcEvent/alg/util.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
/**
|
||||
* 读取指定路径下的所有.stl文件
|
||||
* @param dirPath 要搜索的目录路径
|
||||
* @returns 返回包含所有.stl文件名的数组
|
||||
*/
|
||||
export const findSTLFiles = (dirPath: string): Promise<string[]> => {
|
||||
return new Promise((resolve) => {
|
||||
// 检查路径是否存在
|
||||
if (!fs.existsSync(dirPath)) {
|
||||
resolve([]); // 路径不存在时返回空数组
|
||||
return;
|
||||
}
|
||||
|
||||
// 异步读取目录内容
|
||||
fs.readdir(dirPath, (err, files) => {
|
||||
if (err) {
|
||||
resolve([]); // 读取错误时返回空数组
|
||||
} else {
|
||||
// 过滤出.stl文件
|
||||
const stlFiles = files.filter(
|
||||
(file) => path.extname(file).toLowerCase() === ".stl"
|
||||
);
|
||||
resolve(stlFiles);
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
|
@ -77,9 +77,11 @@
|
|||
"tailwindcss-animate": "^1.0.7",
|
||||
"zod": "3.23.8",
|
||||
"mitt": "3.0.1",
|
||||
"p-limit": "6.1.0"
|
||||
"p-limit": "6.1.0",
|
||||
"three": "0.164.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/three": "0.164.0",
|
||||
"@radix-ui/react-icons": "^1.3.0",
|
||||
"@types/lodash": "4.17.7",
|
||||
"@types/node": "22.5.2",
|
||||
|
|
133
apps/desktop/src/pages/Viewer/ModelViewer/AortaViewer.tsx
Normal file
133
apps/desktop/src/pages/Viewer/ModelViewer/AortaViewer.tsx
Normal file
|
@ -0,0 +1,133 @@
|
|||
import React, { useEffect, useRef } from "react";
|
||||
import * as THREE from "three";
|
||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
||||
import { loadModels } from "./util";
|
||||
|
||||
interface AlgAssets {
|
||||
stlFiles: { fileName: string; data: ArrayBuffer }[];
|
||||
measurement: Record<string, any>;
|
||||
}
|
||||
|
||||
export const AortaViewer: React.FC<{ SeriesInstanceUID: string }> = ({
|
||||
SeriesInstanceUID,
|
||||
}) => {
|
||||
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
||||
const rendererRef = useRef<THREE.WebGLRenderer>();
|
||||
const sceneRef = useRef<THREE.Scene>(new THREE.Scene());
|
||||
const cameraRef = useRef<THREE.PerspectiveCamera>();
|
||||
const controlsRef = useRef<OrbitControls>();
|
||||
const groupRef = useRef(new THREE.Group());
|
||||
|
||||
useEffect(() => {
|
||||
if (SeriesInstanceUID) {
|
||||
window.ipcRenderer
|
||||
.invoke("alg:assets", SeriesInstanceUID)
|
||||
.then((assets: AlgAssets) => {
|
||||
const { stlFiles, measurement } = assets;
|
||||
console.log(measurement);
|
||||
Promise.all(loadModels(stlFiles)).then((meshes) => {
|
||||
meshes.forEach((mesh) => {
|
||||
if (mesh) groupRef.current.add(mesh);
|
||||
});
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.error("Error invoking alg:assets:", error);
|
||||
});
|
||||
}
|
||||
}, [SeriesInstanceUID]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!SeriesInstanceUID || !canvasRef.current) return;
|
||||
|
||||
const { clientWidth, clientHeight } = canvasRef.current;
|
||||
|
||||
if (!rendererRef.current) {
|
||||
// 初始化渲染器
|
||||
initRenderer(clientWidth, clientHeight);
|
||||
initCamera(clientWidth, clientHeight);
|
||||
initControls();
|
||||
initLights();
|
||||
initScene();
|
||||
|
||||
// 开始渲染循环
|
||||
startRenderLoop();
|
||||
}
|
||||
|
||||
const handleResize = () => {
|
||||
if (rendererRef.current && cameraRef.current) {
|
||||
const { clientWidth, clientHeight } = canvasRef.current!;
|
||||
rendererRef.current.setSize(clientWidth, clientHeight);
|
||||
cameraRef.current.aspect = clientWidth / clientHeight;
|
||||
cameraRef.current.updateProjectionMatrix();
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener("resize", handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener("resize", handleResize);
|
||||
};
|
||||
}, [SeriesInstanceUID]);
|
||||
|
||||
// 初始化渲染器
|
||||
const initRenderer = (width: number, height: number) => {
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
canvas: canvasRef.current!,
|
||||
});
|
||||
renderer.setSize(width, height);
|
||||
renderer.setClearColor(0x000000);
|
||||
rendererRef.current = renderer;
|
||||
};
|
||||
|
||||
// 初始化相机
|
||||
const initCamera = (width: number, height: number) => {
|
||||
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 10000);
|
||||
camera.position.set(5, 5, 5);
|
||||
camera.lookAt(0, 0, 0);
|
||||
cameraRef.current = camera;
|
||||
};
|
||||
|
||||
// 初始化控制器
|
||||
const initControls = () => {
|
||||
const controls = new OrbitControls(cameraRef.current!, canvasRef.current!);
|
||||
controls.target.set(0, 0, 0); // 初始目标点
|
||||
controls.update();
|
||||
controlsRef.current = controls;
|
||||
};
|
||||
|
||||
// 初始化光源
|
||||
const initLights = () => {
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff);
|
||||
const directionalLight = new THREE.DirectionalLight(0xffffff);
|
||||
directionalLight.position.set(1, 1, 1);
|
||||
sceneRef.current.add(ambientLight, directionalLight);
|
||||
};
|
||||
|
||||
// 初始化场景
|
||||
const initScene = () => {
|
||||
const axesHelper = new THREE.AxesHelper(1000);
|
||||
sceneRef.current.add(axesHelper);
|
||||
sceneRef.current.add(groupRef.current);
|
||||
};
|
||||
|
||||
// 开始渲染循环
|
||||
const startRenderLoop = () => {
|
||||
const animate = () => {
|
||||
requestAnimationFrame(animate);
|
||||
controlsRef.current?.update();
|
||||
rendererRef.current!.render(sceneRef.current!, cameraRef.current!);
|
||||
};
|
||||
animate();
|
||||
};
|
||||
|
||||
return <canvas ref={canvasRef} style={{ width: "100%", height: "100%" }} />;
|
||||
};
|
||||
|
||||
// if (canvasRef.current && cameraRef.current && rendererRef.current) {
|
||||
// const { clientWidth, clientHeight } = canvasRef.current;
|
||||
// cameraRef.current.aspect = clientWidth / clientHeight;
|
||||
// rendererRef.current.setPixelRatio(window.devicePixelRatio * 2);
|
||||
// cameraRef.current.updateProjectionMatrix();
|
||||
// }
|
|
@ -1,5 +1,6 @@
|
|||
import { useEffect, useState } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { AortaViewer } from "./AortaViewer";
|
||||
|
||||
interface Model3DViewerProps {}
|
||||
|
||||
|
@ -9,13 +10,11 @@ export const Model3DViewer = (props: Model3DViewerProps) => {
|
|||
const queryParams = new URLSearchParams(location.search);
|
||||
const SeriesInstanceUID = queryParams.get("SeriesInstanceUID");
|
||||
|
||||
useEffect(() => {
|
||||
window.ipcRenderer.invoke("alg:assets", SeriesInstanceUID).then((res) => {
|
||||
console.log(res);
|
||||
});
|
||||
});
|
||||
|
||||
return (
|
||||
<div>{SeriesInstanceUID ? <div>3d</div> : <div>启动AI分析该数据</div>}</div>
|
||||
<div className="w-full h-full">
|
||||
{SeriesInstanceUID && (
|
||||
<AortaViewer SeriesInstanceUID={SeriesInstanceUID} />
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
55
apps/desktop/src/pages/Viewer/ModelViewer/util.tsx
Normal file
55
apps/desktop/src/pages/Viewer/ModelViewer/util.tsx
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
|
||||
import * as THREE from "three";
|
||||
|
||||
const createObjectURLFromData = (data: ArrayBuffer): string => {
|
||||
const blob = new Blob([data], { type: "application/octet-stream" });
|
||||
return URL.createObjectURL(blob);
|
||||
};
|
||||
|
||||
const loadSTLFile = (
|
||||
stlLoader: STLLoader,
|
||||
url: string,
|
||||
fileName: string
|
||||
): Promise<THREE.Mesh> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
stlLoader.load(
|
||||
url,
|
||||
(geometry: THREE.BufferGeometry) => {
|
||||
const material = new THREE.MeshLambertMaterial({
|
||||
// color: "red",
|
||||
transparent: true,
|
||||
side: THREE.DoubleSide,
|
||||
});
|
||||
const mesh = new THREE.Mesh(geometry, material);
|
||||
mesh.name = fileName;
|
||||
resolve(mesh);
|
||||
},
|
||||
undefined,
|
||||
(error: Error) => {
|
||||
console.error(`Error loading STL file ${fileName}:`, error);
|
||||
reject(error);
|
||||
}
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
export const loadModels = (
|
||||
stlFiles: { fileName: string; data: ArrayBuffer }[]
|
||||
) => {
|
||||
const loadingManager = new THREE.LoadingManager();
|
||||
const stlLoader = new STLLoader(loadingManager);
|
||||
|
||||
const loadPromises = stlFiles.map((file) => {
|
||||
const { fileName, data } = file;
|
||||
const url = createObjectURLFromData(data);
|
||||
try {
|
||||
return loadSTLFile(stlLoader, url, fileName);
|
||||
} catch (error) {
|
||||
console.error(`Failed to load STL file: ${fileName}`, error);
|
||||
} finally {
|
||||
URL.revokeObjectURL(url); // Clean up URL after use
|
||||
}
|
||||
});
|
||||
|
||||
return loadPromises;
|
||||
};
|
|
@ -208,6 +208,9 @@ importers:
|
|||
tailwindcss-animate:
|
||||
specifier: ^1.0.7
|
||||
version: 1.0.7(tailwindcss@3.4.10)
|
||||
three:
|
||||
specifier: 0.164.1
|
||||
version: 0.164.1
|
||||
zod:
|
||||
specifier: 3.23.8
|
||||
version: 3.23.8
|
||||
|
@ -227,6 +230,9 @@ importers:
|
|||
'@types/react-dom':
|
||||
specifier: ^18.2.21
|
||||
version: 18.3.0
|
||||
'@types/three':
|
||||
specifier: 0.164.0
|
||||
version: 0.164.0
|
||||
'@typescript-eslint/eslint-plugin':
|
||||
specifier: ^7.1.1
|
||||
version: 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.5.4))(eslint@8.57.0)(typescript@5.5.4)
|
||||
|
@ -2125,6 +2131,9 @@ packages:
|
|||
resolution: {integrity: sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==}
|
||||
engines: {node: '>= 10'}
|
||||
|
||||
'@tweenjs/tween.js@23.1.3':
|
||||
resolution: {integrity: sha512-vJmvvwFxYuGnF2axRtPYocag6Clbb5YS7kLL+SO/TeVFzHqDIWrNKYtcsPMibjDx9O+bu+psAy9NKfWklassUA==}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==}
|
||||
|
||||
|
@ -2222,6 +2231,12 @@ packages:
|
|||
'@types/stack-trace@0.0.33':
|
||||
resolution: {integrity: sha512-O7in6531Bbvlb2KEsJ0dq0CHZvc3iWSR5ZYMtvGgnHA56VgriAN/AU2LorfmcvAl2xc9N5fbCTRyMRRl8nd74g==}
|
||||
|
||||
'@types/stats.js@0.17.3':
|
||||
resolution: {integrity: sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==}
|
||||
|
||||
'@types/three@0.164.0':
|
||||
resolution: {integrity: sha512-SFDofn9dJVrE+1DKta7xj7lc4ru7B3S3yf10NsxOserW57aQlB6GxtAS1UK5To3LfEMN5HUHMu3n5v+M5rApgA==}
|
||||
|
||||
'@types/tough-cookie@4.0.5':
|
||||
resolution: {integrity: sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==}
|
||||
|
||||
|
@ -3315,6 +3330,9 @@ packages:
|
|||
fflate@0.7.3:
|
||||
resolution: {integrity: sha512-0Zz1jOzJWERhyhsimS54VTqOteCNwRtIlh8isdL0AXLo0g7xNTfTL7oWrkmCnPhZGocKIkWHBistBrrpoNH3aw==}
|
||||
|
||||
fflate@0.8.2:
|
||||
resolution: {integrity: sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==}
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
|
@ -3967,6 +3985,9 @@ packages:
|
|||
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
|
||||
engines: {node: '>= 8'}
|
||||
|
||||
meshoptimizer@0.18.1:
|
||||
resolution: {integrity: sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==}
|
||||
|
||||
micromatch@4.0.8:
|
||||
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
|
||||
engines: {node: '>=8.6'}
|
||||
|
@ -5197,6 +5218,9 @@ packages:
|
|||
thenify@3.3.1:
|
||||
resolution: {integrity: sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==}
|
||||
|
||||
three@0.164.1:
|
||||
resolution: {integrity: sha512-iC/hUBbl1vzFny7f5GtqzVXYjMJKaTPxiCxXfrvVdBi1Sf+jhd1CAkitiFwC7mIBFCo3MrDLJG97yisoaWig0w==}
|
||||
|
||||
throttle-debounce@5.0.2:
|
||||
resolution: {integrity: sha512-B71/4oyj61iNH0KeCamLuE2rmKuTO5byTOSVwECM5FA7TiAiAW+UqTKZ9ERueC4qvgSttUhdmq1mXC3kJqGX7A==}
|
||||
engines: {node: '>=12.22'}
|
||||
|
@ -7594,6 +7618,8 @@ snapshots:
|
|||
|
||||
'@tootallnate/once@2.0.0': {}
|
||||
|
||||
'@tweenjs/tween.js@23.1.3': {}
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
'@babel/parser': 7.25.4
|
||||
|
@ -7713,6 +7739,16 @@ snapshots:
|
|||
|
||||
'@types/stack-trace@0.0.33': {}
|
||||
|
||||
'@types/stats.js@0.17.3': {}
|
||||
|
||||
'@types/three@0.164.0':
|
||||
dependencies:
|
||||
'@tweenjs/tween.js': 23.1.3
|
||||
'@types/stats.js': 0.17.3
|
||||
'@types/webxr': 0.5.20
|
||||
fflate: 0.8.2
|
||||
meshoptimizer: 0.18.1
|
||||
|
||||
'@types/tough-cookie@4.0.5': {}
|
||||
|
||||
'@types/verror@1.10.10':
|
||||
|
@ -9131,6 +9167,8 @@ snapshots:
|
|||
|
||||
fflate@0.7.3: {}
|
||||
|
||||
fflate@0.8.2: {}
|
||||
|
||||
file-entry-cache@6.0.1:
|
||||
dependencies:
|
||||
flat-cache: 3.2.0
|
||||
|
@ -9802,6 +9840,8 @@ snapshots:
|
|||
|
||||
merge2@1.4.1: {}
|
||||
|
||||
meshoptimizer@0.18.1: {}
|
||||
|
||||
micromatch@4.0.8:
|
||||
dependencies:
|
||||
braces: 3.0.3
|
||||
|
@ -11260,6 +11300,8 @@ snapshots:
|
|||
dependencies:
|
||||
any-promise: 1.3.0
|
||||
|
||||
three@0.164.1: {}
|
||||
|
||||
throttle-debounce@5.0.2: {}
|
||||
|
||||
through2@2.0.5:
|
||||
|
|
Loading…
Reference in New Issue
Block a user