feat: test
This commit is contained in:
parent
2ec9c401ba
commit
3d1afd7a18
|
@ -23,11 +23,13 @@
|
||||||
"js-cookie": "3.0.5",
|
"js-cookie": "3.0.5",
|
||||||
"three": "0.156.1",
|
"three": "0.156.1",
|
||||||
"path-to-regexp": "6.2.1",
|
"path-to-regexp": "6.2.1",
|
||||||
"@cornerstonejs/core": "1.16.5",
|
"cornerstone-core": "2.6.1",
|
||||||
"dicom-parser": "1.8.21",
|
"dicom-parser": "1.8.21",
|
||||||
"cornerstone-wado-image-loader": "4.13.2",
|
|
||||||
"@msgpack/msgpack": "3.0.0-beta2",
|
"@msgpack/msgpack": "3.0.0-beta2",
|
||||||
"pako": "2.1.0"
|
"pako": "2.1.0",
|
||||||
|
"@kitware/vtk.js": "29.2.0",
|
||||||
|
"@cornerstonejs/dicom-image-loader": "1.41.0",
|
||||||
|
"cornerstone-wado-image-loader": "4.13.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.21.8",
|
"@babel/core": "^7.21.8",
|
||||||
|
|
|
@ -40,11 +40,6 @@ const baseConfig: Configuration = {
|
||||||
"@": path.join(__dirname, "../src"),
|
"@": path.join(__dirname, "../src"),
|
||||||
"@@": path.join(__dirname, "../core"),
|
"@@": path.join(__dirname, "../core"),
|
||||||
},
|
},
|
||||||
// fallback: {
|
|
||||||
// crypto: require.resolve('crypto-browserify'),
|
|
||||||
// buffer: require.resolve("buffer/"),
|
|
||||||
// http: require.resolve("stream-http"),
|
|
||||||
// },
|
|
||||||
},
|
},
|
||||||
// plugins
|
// plugins
|
||||||
plugins: [
|
plugins: [
|
||||||
|
|
|
@ -83,8 +83,8 @@ export const LoginForm = (props: LoginFormProps) => {
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
pattern: REG.password,
|
// pattern: REG.password,
|
||||||
min: 6,
|
// min: 6,
|
||||||
max: 20,
|
max: 20,
|
||||||
message: "6-20位,包含 大写、小写、字母、特殊字符",
|
message: "6-20位,包含 大写、小写、字母、特殊字符",
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,8 +14,10 @@
|
||||||
position: relative;
|
position: relative;
|
||||||
background-size: cover;
|
background-size: cover;
|
||||||
background-position: center center;
|
background-position: center center;
|
||||||
min-height: 6rem;
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
img{
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
&::after {
|
&::after {
|
||||||
|
|
|
@ -38,18 +38,20 @@ export const ImageItem = (props: ImageItemProps) => {
|
||||||
|
|
||||||
const handleFileChange = (event: any) => {
|
const handleFileChange = (event: any) => {
|
||||||
if (event.target.files && event.target.files[0]) {
|
if (event.target.files && event.target.files[0]) {
|
||||||
const fd = new FormData();
|
|
||||||
fd.append("file", event.target.files[0]);
|
|
||||||
axios
|
// const fd = new FormData();
|
||||||
.post("/api/report/upload", fd, {
|
// fd.append("file", event.target.files[0]);
|
||||||
headers: { "Content-Type": "multipart/form-data" },
|
// axios
|
||||||
})
|
// .post("/api/report/upload", fd, {
|
||||||
.then((res) => {
|
// headers: { "Content-Type": "multipart/form-data" },
|
||||||
const { objectName } = res.data;
|
// })
|
||||||
setImgSrc(`/api/report/img/${objectName}`);
|
// .then((res) => {
|
||||||
});
|
// const { objectName } = res.data;
|
||||||
|
// setImgSrc(`/api/report/img/${objectName}`);
|
||||||
|
// });
|
||||||
//TODO API接口上传,THEN
|
//TODO API接口上传,THEN
|
||||||
setImgSrc(event.target.result);
|
setImgSrc(URL.createObjectURL(event.target.files[0]));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -63,8 +65,10 @@ export const ImageItem = (props: ImageItemProps) => {
|
||||||
onClick={() => setActionVisible(true)}
|
onClick={() => setActionVisible(true)}
|
||||||
className={`img ${actionVisible ? "active" : ""}`}
|
className={`img ${actionVisible ? "active" : ""}`}
|
||||||
ref={imgRef}
|
ref={imgRef}
|
||||||
style={{ ...props.imgStyle, backgroundImage: `url(${imgSrc})` }}
|
style={{ ...props.imgStyle }}
|
||||||
></div>
|
>
|
||||||
|
<img src={imgSrc} />
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
{actionVisible && (
|
{actionVisible && (
|
||||||
<section ref={actionRef}>
|
<section ref={actionRef}>
|
||||||
|
|
|
@ -1,13 +1,18 @@
|
||||||
.page-head {
|
.page-head {
|
||||||
text-align: right;
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
padding-bottom: 0.57rem;
|
padding-bottom: 0.57rem;
|
||||||
border-bottom: 0.07rem solid var(--color-bg-primary);
|
border-bottom: 0.07rem solid var(--color-bg-primary);
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
|
|
||||||
|
>span{
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
.logo-group {
|
.logo-group {
|
||||||
display: inline-flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ interface PaperHeadProps {
|
||||||
export const PaperHead = (props: PaperHeadProps) => {
|
export const PaperHead = (props: PaperHeadProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="page-head">
|
<div className="page-head">
|
||||||
|
<span>本报告为demo版本,仅供参考,可提供定制</span>
|
||||||
<div className="logo-group">
|
<div className="logo-group">
|
||||||
<svg
|
<svg
|
||||||
viewBox="0 0 45 47"
|
viewBox="0 0 45 47"
|
||||||
|
|
|
@ -31,14 +31,14 @@ interface DoctorProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ReportFullVersion = (props: DoctorProps) => {
|
export const ReportFullVersion = (props: DoctorProps) => {
|
||||||
const [date, totalPageNum] = ["2023-06-14", 21];
|
const [date, totalPageNum] = ["2023-12-12", 18];
|
||||||
const [completed, setCompleted] = useState(0);
|
const [completed, setCompleted] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
axios.get("/api/aorta/report/root").then((res) => {
|
// axios.get("/api/aorta/report/root").then((res) => {
|
||||||
console.log(res);
|
// console.log(res);
|
||||||
});
|
// });
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 下载pdf
|
* 下载pdf
|
||||||
|
|
|
@ -48,7 +48,6 @@ export const LeftCoronarySinus = (props: LeftCoronarySinusProps) => {
|
||||||
<ImageItem
|
<ImageItem
|
||||||
title="左冠窦瓣叶长度"
|
title="左冠窦瓣叶长度"
|
||||||
src={src}
|
src={src}
|
||||||
imgStyle={{ height: "6.43rem" }}
|
|
||||||
/>
|
/>
|
||||||
<Reference
|
<Reference
|
||||||
style={{ position: "absolute", bottom: "3rem" }}
|
style={{ position: "absolute", bottom: "3rem" }}
|
||||||
|
|
|
@ -31,7 +31,6 @@ export const NoCoronarySinus = (props: NoCoronarySinusProps) => {
|
||||||
<ImageItem
|
<ImageItem
|
||||||
title="无冠窦瓣叶长度"
|
title="无冠窦瓣叶长度"
|
||||||
src={src}
|
src={src}
|
||||||
imgStyle={{ height: "6.43rem" }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -32,7 +32,6 @@ export const RightCoronarySinus = (props: RightCoronarySinusProps) => {
|
||||||
<ImageItem
|
<ImageItem
|
||||||
title="左冠窦瓣叶长度"
|
title="左冠窦瓣叶长度"
|
||||||
src={src}
|
src={src}
|
||||||
imgStyle={{ height: "6.43rem" }}
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
133
apps/aorta/src/modules/Root/Viewer/MprViewer/index.tsx
Normal file
133
apps/aorta/src/modules/Root/Viewer/MprViewer/index.tsx
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
import { relative } from "path";
|
||||||
|
import React, { useEffect, useRef, useState } from "react";
|
||||||
|
import * as THREE from "three";
|
||||||
|
import { TrackballControls } from "three/examples/jsm/controls/TrackballControls";
|
||||||
|
|
||||||
|
type ThreeJSComponentProps = {
|
||||||
|
// 你可以在这里添加任何需要的props
|
||||||
|
};
|
||||||
|
|
||||||
|
export const MprViewer: React.FC<ThreeJSComponentProps> = () => {
|
||||||
|
const mountRef = useRef<HTMLDivElement>(null);
|
||||||
|
const [imageSrc, setImageSrc] = useState<string>("");
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const width = window.innerWidth - 300;
|
||||||
|
const height = window.innerHeight;
|
||||||
|
const scene = new THREE.Scene();
|
||||||
|
scene.background = new THREE.Color(0x808080);
|
||||||
|
|
||||||
|
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
||||||
|
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
||||||
|
renderer.setSize(width, height);
|
||||||
|
|
||||||
|
// 添加一个可移动和旋转的平面
|
||||||
|
const planeGeometry = new THREE.PlaneGeometry(200, 200);
|
||||||
|
const planeMaterial = new THREE.MeshBasicMaterial({
|
||||||
|
color: 0xffff00,
|
||||||
|
side: THREE.DoubleSide,
|
||||||
|
});
|
||||||
|
const plane = new THREE.Mesh(planeGeometry, planeMaterial);
|
||||||
|
plane.position.set(0, 0, 0); // 初始位置
|
||||||
|
scene.add(plane);
|
||||||
|
|
||||||
|
// 创建一个与平面对齐的剪裁平面
|
||||||
|
const clipPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);
|
||||||
|
renderer.clippingPlanes = [clipPlane];
|
||||||
|
|
||||||
|
// 使剪裁平面跟随你的移动平面
|
||||||
|
const updateClipPlane = () => {
|
||||||
|
clipPlane.normal.copy(plane.position).normalize();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 添加多个3D物体
|
||||||
|
const geometry1 = new THREE.BoxGeometry(100, 100, 100);
|
||||||
|
const material1 = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0x00ff00,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
});
|
||||||
|
const cube = new THREE.Mesh(geometry1, material1);
|
||||||
|
cube.position.x = -50; // 调整位置以确保重叠
|
||||||
|
scene.add(cube);
|
||||||
|
|
||||||
|
const geometry2 = new THREE.SphereGeometry(50, 32, 32);
|
||||||
|
const material2 = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0xff0000,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
});
|
||||||
|
const sphere = new THREE.Mesh(geometry2, material2);
|
||||||
|
sphere.position.x = 0; // 调整位置以确保重叠
|
||||||
|
scene.add(sphere);
|
||||||
|
|
||||||
|
const geometry3 = new THREE.ConeGeometry(50, 100, 32);
|
||||||
|
const material3 = new THREE.MeshPhongMaterial({
|
||||||
|
color: 0x0000ff,
|
||||||
|
transparent: true,
|
||||||
|
opacity: 0.5,
|
||||||
|
});
|
||||||
|
const cone = new THREE.Mesh(geometry3, material3);
|
||||||
|
cone.position.x = 50; // 调整位置以确保重叠
|
||||||
|
scene.add(cone);
|
||||||
|
|
||||||
|
camera.position.set(0, 0, 500);
|
||||||
|
camera.lookAt(scene.position);
|
||||||
|
|
||||||
|
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
|
||||||
|
scene.add(ambientLight);
|
||||||
|
|
||||||
|
const mount = mountRef.current;
|
||||||
|
if (mount) {
|
||||||
|
mount.appendChild(renderer.domElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const controls = new TrackballControls(camera, renderer.domElement);
|
||||||
|
|
||||||
|
const animate = () => {
|
||||||
|
requestAnimationFrame(animate);
|
||||||
|
controls.update(); // 只有在使用 TrackballControls 时才需要
|
||||||
|
// camera.position.y += 5;
|
||||||
|
camera.updateMatrixWorld();
|
||||||
|
|
||||||
|
updateClipPlane(); // 更新剪裁平面的位置和方向
|
||||||
|
|
||||||
|
|
||||||
|
renderer.render(scene, camera);
|
||||||
|
|
||||||
|
// 从渲染器的 canvas 中获取图像
|
||||||
|
const canvas = renderer.domElement;
|
||||||
|
canvas.toBlob((blob) => {
|
||||||
|
if (blob) {
|
||||||
|
const imageUrl = URL.createObjectURL(blob);
|
||||||
|
setImageSrc(imageUrl);
|
||||||
|
}
|
||||||
|
}, "image/jpeg");
|
||||||
|
};
|
||||||
|
|
||||||
|
animate();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
controls.dispose();
|
||||||
|
mount?.removeChild(renderer.domElement);
|
||||||
|
URL.revokeObjectURL(imageSrc);
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<div ref={mountRef} />
|
||||||
|
<img
|
||||||
|
style={{
|
||||||
|
position: "absolute",
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
width: "200px",
|
||||||
|
height: "200px",
|
||||||
|
}}
|
||||||
|
src={imageSrc}
|
||||||
|
alt="Rendered Scene"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
63
apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx
Normal file
63
apps/aorta/src/modules/Root/Viewer/VolumeViewer/index.tsx
Normal file
|
@ -0,0 +1,63 @@
|
||||||
|
import axios from "axios";
|
||||||
|
import { useEffect, useRef } from "react";
|
||||||
|
import * as cornerstone from "cornerstone-core";
|
||||||
|
import dicomParser from "dicom-parser";
|
||||||
|
import cornerstoneDICOMImageLoader from "@cornerstonejs/dicom-image-loader";
|
||||||
|
|
||||||
|
interface VolumeViewerProps {
|
||||||
|
children?: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
const StudyInstanceUID =
|
||||||
|
"1.2.840.113619.2.416.200043823236217877797891016883696407563"; // 你的 studyInstanceUID
|
||||||
|
const SeriesInstanceUID =
|
||||||
|
"1.2.840.113619.6.80.114374075765625.22940.1553237925965.1"; // 你的 seriesInstanceUID
|
||||||
|
|
||||||
|
export const VolumeViewer = (props: VolumeViewerProps) => {
|
||||||
|
const dicomRef = useRef(null);
|
||||||
|
const url = `/dicom-web/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances`;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cornerstoneDICOMImageLoader.external.cornerstone = cornerstone;
|
||||||
|
cornerstoneDICOMImageLoader.external.dicomParser = dicomParser;
|
||||||
|
|
||||||
|
// cornerstoneWADOImageLoader.configure({ useWebWorkers: true });
|
||||||
|
|
||||||
|
const config = {
|
||||||
|
maxWebWorkers: navigator.hardwareConcurrency || 1,
|
||||||
|
startWebWorkersOnDemand: true,
|
||||||
|
taskConfiguration: {
|
||||||
|
decodeTask: {
|
||||||
|
initializeCodecsOnStartup: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
cornerstoneDICOMImageLoader.webWorkerManager.initialize(config);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cornerstone.enable(dicomRef.current!);
|
||||||
|
axios
|
||||||
|
.get(url)
|
||||||
|
.then((response) => {
|
||||||
|
const objectUIDs = response.data.map(
|
||||||
|
(i: any) => i["00080018"].Value[0]
|
||||||
|
);
|
||||||
|
objectUIDs.forEach((i: any) => {
|
||||||
|
const imageId = `wadors:/dicom-web/studies/${StudyInstanceUID}/series/${SeriesInstanceUID}/instances/${i}/frames/1`;
|
||||||
|
cornerstone.loadImage(imageId).then((image: any) => {
|
||||||
|
cornerstone.displayImage(dicomRef.current!, image);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error("Error fetching instances:", error);
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div ref={dicomRef} style={{ width: 200, height: 200 }}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
|
@ -1,4 +1,6 @@
|
||||||
import { DiffViewer } from "./DiffViewer";
|
import { DiffViewer } from "./DiffViewer";
|
||||||
|
import { MprViewer } from "./MprViewer";
|
||||||
|
import { VolumeViewer } from "./VolumeViewer";
|
||||||
|
|
||||||
interface RootViewerProps {
|
interface RootViewerProps {
|
||||||
children?: JSX.Element;
|
children?: JSX.Element;
|
||||||
|
@ -7,7 +9,7 @@ interface RootViewerProps {
|
||||||
export const RootViewer = (props: RootViewerProps) => {
|
export const RootViewer = (props: RootViewerProps) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<DiffViewer />
|
<VolumeViewer />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,106 +0,0 @@
|
||||||
import React, { useEffect, useRef } from "react";
|
|
||||||
import * as THREE from "three";
|
|
||||||
import { STLLoader } from "three/examples/jsm/loaders/STLLoader";
|
|
||||||
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
|
|
||||||
import {
|
|
||||||
autoScale,
|
|
||||||
createEnclosedGeometry,
|
|
||||||
getOffsetTriangles,
|
|
||||||
getTriangleVertexs,
|
|
||||||
} from "./util";
|
|
||||||
|
|
||||||
export const STLViewer: React.FC = () => {
|
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (containerRef.current) {
|
|
||||||
const width = containerRef.current.clientWidth;
|
|
||||||
const height = containerRef.current.clientHeight;
|
|
||||||
|
|
||||||
// 创建场景、相机和渲染器
|
|
||||||
const scene = new THREE.Scene();
|
|
||||||
const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
|
||||||
camera.position.z = 5;
|
|
||||||
|
|
||||||
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
||||||
renderer.setSize(width, height);
|
|
||||||
containerRef.current.appendChild(renderer.domElement);
|
|
||||||
|
|
||||||
// 加载STL文件
|
|
||||||
const loader = new STLLoader();
|
|
||||||
loader.load("/1.stl", (geometry) => {
|
|
||||||
const s = autoScale(geometry);
|
|
||||||
// geometry.center();
|
|
||||||
// const material = new THREE.MeshPhongMaterial({
|
|
||||||
// color: "lightgrey", // 使用更亮的颜色
|
|
||||||
// shininess: 100, // 增加亮度
|
|
||||||
// specular: 0x222222, // 调整镜面高光的颜色
|
|
||||||
// // side: THREE.DoubleSide,
|
|
||||||
// // wireframe: true,
|
|
||||||
// });
|
|
||||||
// const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
// mesh.scale.set(scale, scale, scale);
|
|
||||||
// scene.add(mesh);
|
|
||||||
|
|
||||||
// 输出三角形顶点信息
|
|
||||||
const triangles = getTriangleVertexs(geometry);
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
const newTriangles = getOffsetTriangles(triangles, -2);
|
|
||||||
|
|
||||||
console.log("newTriangles", newTriangles);
|
|
||||||
|
|
||||||
// 使用上面定义的函数
|
|
||||||
const enclosedGeometry = createEnclosedGeometry(
|
|
||||||
triangles,
|
|
||||||
newTriangles
|
|
||||||
);
|
|
||||||
|
|
||||||
enclosedGeometry.center();
|
|
||||||
|
|
||||||
const material = new THREE.MeshBasicMaterial({
|
|
||||||
color: "lightgrey",
|
|
||||||
side: THREE.DoubleSide,
|
|
||||||
});
|
|
||||||
|
|
||||||
const mesh = new THREE.Mesh(enclosedGeometry, material);
|
|
||||||
mesh.scale.set(s, s, s);
|
|
||||||
scene.add(mesh);
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
////////////////////////////////////////////////////////////////
|
|
||||||
});
|
|
||||||
|
|
||||||
// 添加环境光源
|
|
||||||
const ambientLight = new THREE.AmbientLight(0xffffff, 1); // 第二个参数为强度
|
|
||||||
scene.add(ambientLight);
|
|
||||||
|
|
||||||
// 添加方向光源
|
|
||||||
const directionalLight = new THREE.DirectionalLight(0xffffff, 2);
|
|
||||||
directionalLight.position.set(1, 1, 1).normalize();
|
|
||||||
scene.add(directionalLight);
|
|
||||||
|
|
||||||
// 添加XYZ轴
|
|
||||||
const axesHelper = new THREE.AxesHelper(2);
|
|
||||||
scene.add(axesHelper);
|
|
||||||
|
|
||||||
// 添加OrbitControls
|
|
||||||
const controls = new OrbitControls(camera, renderer.domElement);
|
|
||||||
|
|
||||||
// 渲染函数
|
|
||||||
const animate = () => {
|
|
||||||
requestAnimationFrame(animate);
|
|
||||||
renderer.render(scene, camera);
|
|
||||||
};
|
|
||||||
|
|
||||||
animate();
|
|
||||||
}
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return <div ref={containerRef} style={{ width: "100vw", height: "100vh" }} />;
|
|
||||||
};
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { STLViewer } from "./STLViewer";
|
|
||||||
|
|
||||||
interface RootViewerProps {
|
|
||||||
children?: JSX.Element;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RootViewer = (props: RootViewerProps) => {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
123
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
|
@ -1,202 +0,0 @@
|
||||||
import * as THREE from "three";
|
|
||||||
|
|
||||||
export const autoScale = (geometry: THREE.BufferGeometry) => {
|
|
||||||
// 计算模型的边界
|
|
||||||
geometry.computeBoundingBox();
|
|
||||||
// 获取模型的大小
|
|
||||||
const modelSize = geometry.boundingBox!.getSize(new THREE.Vector3());
|
|
||||||
const maxSize = Math.max(modelSize.x, modelSize.y, modelSize.z);
|
|
||||||
// 根据模型的大小设置一个缩放因子
|
|
||||||
const scaleFactor = 2 / maxSize; // 假设我们希望模型最大为2单位
|
|
||||||
return scaleFactor;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getTriangleVertexs = (
|
|
||||||
geometry: THREE.BufferGeometry
|
|
||||||
): THREE.Vector3[][] => {
|
|
||||||
const positions = geometry.attributes.position;
|
|
||||||
const triangles: THREE.Vector3[][] = [];
|
|
||||||
|
|
||||||
const getTriangleVertices = (
|
|
||||||
index1: number,
|
|
||||||
index2: number,
|
|
||||||
index3: number
|
|
||||||
) => [
|
|
||||||
new THREE.Vector3(
|
|
||||||
positions.getX(index1),
|
|
||||||
positions.getY(index1),
|
|
||||||
positions.getZ(index1)
|
|
||||||
),
|
|
||||||
new THREE.Vector3(
|
|
||||||
positions.getX(index2),
|
|
||||||
positions.getY(index2),
|
|
||||||
positions.getZ(index2)
|
|
||||||
),
|
|
||||||
new THREE.Vector3(
|
|
||||||
positions.getX(index3),
|
|
||||||
positions.getY(index3),
|
|
||||||
positions.getZ(index3)
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
if (geometry.index) {
|
|
||||||
const indices = geometry.index.array;
|
|
||||||
for (let i = 0; i < indices.length; i += 3) {
|
|
||||||
triangles.push(
|
|
||||||
getTriangleVertices(indices[i], indices[i + 1], indices[i + 2])
|
|
||||||
);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (let i = 0; i < positions.count; i += 3) {
|
|
||||||
triangles.push(getTriangleVertices(i, i + 1, i + 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return triangles;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getOffsetTriangles = (
|
|
||||||
triangles: THREE.Vector3[][],
|
|
||||||
offset: number
|
|
||||||
): THREE.Vector3[][] => {
|
|
||||||
const newTriangles: THREE.Vector3[][] = [];
|
|
||||||
|
|
||||||
triangles.forEach((triangle) => {
|
|
||||||
// 计算三角形的法线
|
|
||||||
const normal = new THREE.Vector3();
|
|
||||||
const edge1 = new THREE.Vector3().subVectors(triangle[1], triangle[0]);
|
|
||||||
const edge2 = new THREE.Vector3().subVectors(triangle[2], triangle[0]);
|
|
||||||
normal.crossVectors(edge1, edge2).normalize();
|
|
||||||
|
|
||||||
// 使用法线和偏移量计算新的三角形顶点
|
|
||||||
const offsetNormal = normal.clone().multiplyScalar(offset);
|
|
||||||
const newTriangle = triangle.map((vertex) =>
|
|
||||||
vertex.clone().sub(offsetNormal)
|
|
||||||
);
|
|
||||||
newTriangles.push(newTriangle);
|
|
||||||
});
|
|
||||||
|
|
||||||
return newTriangles;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const createEnclosedGeometry = (
|
|
||||||
triangles: THREE.Vector3[][],
|
|
||||||
newTriangles: THREE.Vector3[][]
|
|
||||||
): THREE.BufferGeometry => {
|
|
||||||
const geometry = new THREE.BufferGeometry();
|
|
||||||
const vertices: THREE.Vector3[] = [];
|
|
||||||
const indices: number[] = [];
|
|
||||||
|
|
||||||
const processedEdges: Set<string> = new Set();
|
|
||||||
|
|
||||||
function edgeId(v1: THREE.Vector3, v2: THREE.Vector3): string {
|
|
||||||
return `${v1.x},${v1.y},${v1.z}->${v2.x},${v2.y},${v2.z}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
triangles.forEach((triangle, index) => {
|
|
||||||
const innerTriangle = newTriangles[index];
|
|
||||||
|
|
||||||
triangle.forEach((vertex) => {
|
|
||||||
vertices.push(vertex);
|
|
||||||
});
|
|
||||||
|
|
||||||
innerTriangle.forEach((vertex) => {
|
|
||||||
vertices.push(vertex);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Process the three edges for each triangle
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
const next = (i + 1) % 3;
|
|
||||||
const edge = edgeId(triangle[i], triangle[next]);
|
|
||||||
|
|
||||||
if (!processedEdges.has(edge)) {
|
|
||||||
processedEdges.add(edge);
|
|
||||||
|
|
||||||
// Add the vertices for the quad (two triangles)
|
|
||||||
const quadVertices = [
|
|
||||||
triangle[i],
|
|
||||||
triangle[next],
|
|
||||||
innerTriangle[i],
|
|
||||||
innerTriangle[next],
|
|
||||||
];
|
|
||||||
|
|
||||||
quadVertices.forEach((vertex) => {
|
|
||||||
vertices.push(vertex);
|
|
||||||
});
|
|
||||||
|
|
||||||
const baseIndex = vertices.length - 4;
|
|
||||||
|
|
||||||
// First triangle of the quad
|
|
||||||
indices.push(baseIndex, baseIndex + 2, baseIndex + 1);
|
|
||||||
// Second triangle of the quad
|
|
||||||
indices.push(baseIndex + 1, baseIndex + 2, baseIndex + 3);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
geometry.setFromPoints(vertices);
|
|
||||||
geometry.setIndex(indices);
|
|
||||||
|
|
||||||
return geometry;
|
|
||||||
};
|
|
||||||
|
|
||||||
// export const createEnclosedGeometry = (
|
|
||||||
// triangles: THREE.Vector3[][],
|
|
||||||
// newTriangles: THREE.Vector3[][]
|
|
||||||
// ): THREE.BufferGeometry => {
|
|
||||||
// const geometry = new THREE.BufferGeometry();
|
|
||||||
|
|
||||||
// const vertices: number[] = [];
|
|
||||||
// const indices: number[] = [];
|
|
||||||
|
|
||||||
// // 添加原始三角形和新三角形的顶点到vertices数组
|
|
||||||
// triangles.forEach((triangle) => {
|
|
||||||
// triangle.forEach((v) => {
|
|
||||||
// vertices.push(v.x, v.y, v.z);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// newTriangles.forEach((triangle) => {
|
|
||||||
// triangle.forEach((v) => {
|
|
||||||
// vertices.push(v.x, v.y, v.z);
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const totalTriangles = triangles.length;
|
|
||||||
|
|
||||||
// // 创建索引来连接triangles和newTriangles中的三角形
|
|
||||||
// for (let i = 0; i < totalTriangles; i++) {
|
|
||||||
// const offset = totalTriangles * 3; // newTriangles在vertices中的偏移量
|
|
||||||
|
|
||||||
// const a1 = i * 3;
|
|
||||||
// const b1 = a1 + 1;
|
|
||||||
// const c1 = a1 + 2;
|
|
||||||
|
|
||||||
// const a2 = offset + i * 3;
|
|
||||||
// const b2 = a2 + 1;
|
|
||||||
// const c2 = a2 + 2;
|
|
||||||
|
|
||||||
// // 添加原始和新三角形的索引
|
|
||||||
// indices.push(a1, b1, c1);
|
|
||||||
// indices.push(a2, c2, b2);
|
|
||||||
|
|
||||||
// // 为每一对相邻的三角形添加边的索引
|
|
||||||
// indices.push(a1, b1, a2);
|
|
||||||
// indices.push(b1, b2, a2);
|
|
||||||
|
|
||||||
// indices.push(b1, c1, b2);
|
|
||||||
// indices.push(c1, c2, b2);
|
|
||||||
|
|
||||||
// indices.push(c1, a1, c2);
|
|
||||||
// indices.push(a1, a2, c2);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// geometry.setIndex(indices);
|
|
||||||
// geometry.setAttribute(
|
|
||||||
// "position",
|
|
||||||
// new THREE.Float32BufferAttribute(vertices, 3)
|
|
||||||
// );
|
|
||||||
// geometry.computeVertexNormals(); // 计算顶点法线
|
|
||||||
|
|
||||||
// return geometry;
|
|
||||||
// };
|
|
|
@ -17,6 +17,10 @@ export const baseRoutes: (RouteObject & ExpandRouteProps)[] = [
|
||||||
element: <Login />,
|
element: <Login />,
|
||||||
title: "登录 - CVPILOT Viewer",
|
title: "登录 - CVPILOT Viewer",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: "root/report/full",
|
||||||
|
element: <ReportFullVersion />,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const commonRoutes: (RouteObject & ExpandRouteProps)[] = [
|
export const commonRoutes: (RouteObject & ExpandRouteProps)[] = [
|
||||||
|
@ -41,10 +45,7 @@ export const commonRoutes: (RouteObject & ExpandRouteProps)[] = [
|
||||||
path: "root/viewer",
|
path: "root/viewer",
|
||||||
element: <RootViewer />,
|
element: <RootViewer />,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: "root/report/full",
|
|
||||||
element: <ReportFullVersion />,
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
path: "peripheral/viewer",
|
path: "peripheral/viewer",
|
||||||
element: <PeripheralViewer />,
|
element: <PeripheralViewer />,
|
||||||
|
|
|
@ -21,10 +21,11 @@ export class AuthController {
|
||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
) {
|
) {
|
||||||
const { username, password, phoneNumber } = userLoginDto;
|
const { username, password, phoneNumber } = userLoginDto;
|
||||||
// TODO: isEnabled、phoneNumber,用于后续验证
|
//* Step1: 询问认证中心服务,用户是否合法
|
||||||
const { isLegal, data, msg } = await firstValueFrom(
|
const { isLegal, data, msg } = await firstValueFrom(
|
||||||
this.client.send('cert.user.account', { username, password }),
|
this.client.send('cert.user.account', { username, password }),
|
||||||
);
|
);
|
||||||
|
//* Step2: 上报登录日志
|
||||||
await firstValueFrom(
|
await firstValueFrom(
|
||||||
this.client.send('logger.user.signIn', {
|
this.client.send('logger.user.signIn', {
|
||||||
platform: 'dmp',
|
platform: 'dmp',
|
||||||
|
@ -35,10 +36,11 @@ export class AuthController {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (isLegal) {
|
if (isLegal) {
|
||||||
// 签发token
|
//* Step3: 签发token
|
||||||
const { token } = await firstValueFrom(
|
const { token } = await firstValueFrom(
|
||||||
this.client.send('cert.token.create', { username }),
|
this.client.send('cert.token.create', { username }),
|
||||||
);
|
);
|
||||||
|
//* Step4: 询问认证中心token存在cookie中的key,统一个业务的token的key,以及过期时间
|
||||||
const { tokenKeyInCookie, expires } = await firstValueFrom(
|
const { tokenKeyInCookie, expires } = await firstValueFrom(
|
||||||
this.client.send('cert.token.config', []),
|
this.client.send('cert.token.config', []),
|
||||||
);
|
);
|
||||||
|
|
|
@ -15,10 +15,11 @@ export class AuthController {
|
||||||
@Res({ passthrough: true }) res: Response,
|
@Res({ passthrough: true }) res: Response,
|
||||||
) {
|
) {
|
||||||
const { username, password } = userLoginDto;
|
const { username, password } = userLoginDto;
|
||||||
|
//* Step1: 询问认证中心服务,用户是否合法
|
||||||
const { isLegal, data, msg } = await firstValueFrom(
|
const { isLegal, data, msg } = await firstValueFrom(
|
||||||
this.client.send('cert.user.account', { username, password }),
|
this.client.send('cert.user.account', { username, password }),
|
||||||
);
|
);
|
||||||
// 日志
|
//* Step2: 上报登录日志
|
||||||
await firstValueFrom(
|
await firstValueFrom(
|
||||||
this.client.send('logger.user.signIn', {
|
this.client.send('logger.user.signIn', {
|
||||||
platform: 'dmp',
|
platform: 'dmp',
|
||||||
|
@ -29,11 +30,12 @@ export class AuthController {
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
if (isLegal) {
|
if (isLegal) {
|
||||||
// 签发token,签用户的角色,前端UI路由鉴权
|
//* Step3: 签发token,签用户的角色,前端UI路由鉴权
|
||||||
const { roles } = data;
|
const { roles } = data;
|
||||||
const { token } = await firstValueFrom(
|
const { token } = await firstValueFrom(
|
||||||
this.client.send('cert.token.create', { username, roles }),
|
this.client.send('cert.token.create', { username, roles }),
|
||||||
);
|
);
|
||||||
|
//* Step4: 询问认证中心token存在cookie中的key,统一个业务的token的key,以及过期时间
|
||||||
const { tokenKeyInCookie, expires } = await firstValueFrom(
|
const { tokenKeyInCookie, expires } = await firstValueFrom(
|
||||||
this.client.send('cert.token.config', []),
|
this.client.send('cert.token.config', []),
|
||||||
);
|
);
|
||||||
|
|
3951
pnpm-lock.yaml
3951
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user