This commit is contained in:
2026-05-02 00:39:04 +08:00
parent f9acdb767b
commit d1a3f22785
16 changed files with 1181 additions and 113 deletions
+198 -90
View File
@@ -224,7 +224,7 @@ class HTMLGenerator:
function toFlatArray(data) {{
if (!Array.isArray(data)) return [];
if (data.length === 0) return [];
return Array.isArray(data[0]) ? data.flat() : data;
return Array.isArray(data[0]) ? data.flat(Infinity) : data;
}}
function normalizePositions(rawPositions, center) {{
@@ -247,62 +247,137 @@ class HTMLGenerator:
}}
function fitCameraToScene() {{
const sceneBox = new THREE.Box3().setFromObject(scene);
const objects = [productMesh, cavityMesh, coreMesh, partingMesh, pointcloudMesh].filter(Boolean);
if (!objects.length) return;
const sceneBox = new THREE.Box3();
objects.forEach(obj => sceneBox.expandByObject(obj));
if (sceneBox.isEmpty()) return;
const center = new THREE.Vector3();
const size = new THREE.Vector3();
sceneBox.getCenter(center);
sceneBox.getSize(size);
const maxDim = Math.max(size.x, size.y, size.z) || 100;
const distance = maxDim * 2.2;
const distance = Math.max(maxDim * 1.8, 30);
camera.near = Math.max(maxDim / 2000, 0.01);
camera.far = Math.max(maxDim * 200, 5000);
camera.updateProjectionMatrix();
camera.position.set(center.x + distance, center.y + distance, center.z + distance);
controls.target.copy(center);
controls.minDistance = Math.max(maxDim * 0.03, 0.5);
controls.maxDistance = Math.max(maxDim * 30, 2000);
controls.update();
}}
function registerInitialPose(mesh) {{
if (!mesh) return;
mesh.userData.initialPosition = mesh.position.clone();
mesh.userData.initialVisible = mesh.visible;
}}
function computeBounds(rawPositionsList) {{
let minX = Infinity, minY = Infinity, minZ = Infinity;
let maxX = -Infinity, maxY = -Infinity, maxZ = -Infinity;
let hasPoint = false;
for (const raw of rawPositionsList) {{
const flat = toFlatArray(raw);
for (let i = 0; i + 2 < flat.length; i += 3) {{
const x = Number(flat[i]);
const y = Number(flat[i + 1]);
const z = Number(flat[i + 2]);
if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) continue;
hasPoint = true;
minX = Math.min(minX, x); minY = Math.min(minY, y); minZ = Math.min(minZ, z);
maxX = Math.max(maxX, x); maxY = Math.max(maxY, y); maxZ = Math.max(maxZ, z);
}}
}}
if (!hasPoint) return null;
return {{
center: [(minX + maxX) / 2, (minY + maxY) / 2, (minZ + maxZ) / 2],
dimensions: [Math.max(maxX - minX, 1), Math.max(maxY - minY, 1), Math.max(maxZ - minZ, 1)],
}};
}}
function isValidIndexedGeometry(positions, indices) {{
if (!positions || !indices) return false;
if (positions.length < 9 || indices.length < 3) return false;
if (positions.length % 3 !== 0 || indices.length % 3 !== 0) return false;
const vertexCount = positions.length / 3;
for (let i = 0; i < indices.length; i++) {{
const idx = Number(indices[i]);
if (!Number.isFinite(idx) || idx < 0 || idx >= vertexCount) return false;
}}
return true;
}}
// 根据边界框创建几何体
const bbox = geometryData.bounding_box;
const fallbackBBox = computeBounds([
pointcloudData?.points,
cavityData?.mold_cavities?.cavity?.vertices,
cavityData?.mold_cavities?.core?.vertices
]);
const bbox = geometryData.bounding_box || fallbackBBox || {{
center: [0, 0, 0],
dimensions: [100, 100, 100]
}};
if (bbox) {{
const centerOffset = bbox.center || [0, 0, 0];
const width = bbox.dimensions ? bbox.dimensions[0] : 100;
const height = bbox.dimensions ? bbox.dimensions[1] : 100;
const depth = bbox.dimensions ? bbox.dimensions[2] : 100;
const coreRequired = cavityData?.metadata?.core_required !== false;
// 如果有点云数据,创建点云模型
if (pointcloudData && pointcloudData.points && pointcloudData.points.length > 0) {{
// 创建点云几何体
const pointGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(normalizePositions(pointcloudData.points, centerOffset));
pointGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
// 添加法向量(如果有)
if (pointcloudData.normals && pointcloudData.normals.length > 0) {{
const normals = new Float32Array(toFlatArray(pointcloudData.normals));
pointGeometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
let productRendered = false;
// 优先用真实三角网格渲染产品
if (pointcloudData.vertices && pointcloudData.faces) {{
const productVerts = normalizePositions(pointcloudData.vertices, centerOffset);
const productFaces = toFlatArray(pointcloudData.faces);
if (productVerts.length >= 9 && productFaces.length >= 3) {{
const productGeometry = new THREE.BufferGeometry();
productGeometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(productVerts), 3));
productGeometry.setIndex(new THREE.BufferAttribute(new Uint32Array(productFaces), 1));
productGeometry.computeVertexNormals();
const productMaterial = new THREE.MeshPhongMaterial({{
color: 0x4CAF50,
transparent: true,
opacity: 0.45,
side: THREE.DoubleSide
}});
productMesh = new THREE.Mesh(productGeometry, productMaterial);
scene.add(productMesh);
registerInitialPose(productMesh);
productRendered = true;
}}
}}
// 创建点云材质
const pointMaterial = new THREE.PointsMaterial({{
color: 0x4CAF50,
size: 1.0,
transparent: true,
opacity: 0.9
}});
pointcloudMesh = new THREE.Points(pointGeometry, pointMaterial);
scene.add(pointcloudMesh);
// 计算中心点并调整相机位置
const center = new THREE.Vector3();
pointGeometry.computeBoundingBox();
pointGeometry.boundingBox.getCenter(center);
controls.target.set(center.x, center.y, center.z);
// 根据边界框大小调整相机距离
const boundingSphere = new THREE.Sphere();
pointGeometry.boundingBox.getBoundingSphere(boundingSphere);
const cameraDistance = boundingSphere.radius * 3;
camera.position.set(cameraDistance, cameraDistance, cameraDistance);
// 网格不可用时回退点云
if (!productRendered) {{
const pointPositions = normalizePositions(pointcloudData.points, centerOffset);
if (pointPositions.length >= 3) {{
const pointGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(pointPositions);
pointGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
if (pointcloudData.normals && pointcloudData.normals.length > 0) {{
const normals = new Float32Array(toFlatArray(pointcloudData.normals));
if (normals.length === positions.length) {{
pointGeometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
}}
}}
const pointMaterial = new THREE.PointsMaterial({{
color: 0x4CAF50,
size: 1.0,
transparent: true,
opacity: 0.9
}});
pointcloudMesh = new THREE.Points(pointGeometry, pointMaterial);
scene.add(pointcloudMesh);
registerInitialPose(pointcloudMesh);
}}
}}
}} else {{
// 创建产品几何体(半透明绿色)
const productGeometry = new THREE.BoxGeometry(width * 0.9, height * 0.9, depth * 0.9);
@@ -316,6 +391,7 @@ class HTMLGenerator:
productMesh = new THREE.Mesh(productGeometry, productMaterial);
productMesh.position.set(0, 0, 0);
scene.add(productMesh);
registerInitialPose(productMesh);
}}
// 创建A板/定模(蓝色,分型面以上)
@@ -328,35 +404,36 @@ class HTMLGenerator:
const cavityGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(normalizePositions(cavityVerts, centerOffset));
const indices = new Uint32Array(toFlatArray(cavityFaces));
cavityGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
cavityGeometry.setIndex(new THREE.BufferAttribute(indices, 1));
cavityGeometry.computeVertexNormals();
const cavityMaterial = new THREE.MeshPhongMaterial({{
color: 0x2196F3,
transparent: true,
opacity: 0.5,
wireframe: false,
side: THREE.DoubleSide
}});
cavityMesh = new THREE.Mesh(cavityGeometry, cavityMaterial);
scene.add(cavityMesh);
// 添加线框
const cavityWireframe = new THREE.WireframeGeometry(cavityGeometry);
const cavityLine = new THREE.LineSegments(cavityWireframe);
cavityLine.material.depthTest = false;
cavityLine.material.opacity = 0.6;
cavityLine.material.transparent = true;
cavityLine.material.color = new THREE.Color(0x1565C0);
cavityMesh.add(cavityLine);
// 设置相机目标为型腔中心
cavityGeometry.computeBoundingBox();
const cavityCenter = new THREE.Vector3();
cavityGeometry.boundingBox.getCenter(cavityCenter);
controls.target.set(cavityCenter.x, cavityCenter.y, cavityCenter.z);
let cavityBuilt = false;
if (isValidIndexedGeometry(positions, indices)) {{
cavityGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
cavityGeometry.setIndex(new THREE.BufferAttribute(indices, 1));
cavityGeometry.computeVertexNormals();
cavityBuilt = true;
}}
if (cavityBuilt) {{
const cavityMaterial = new THREE.MeshPhongMaterial({{
color: 0x2196F3,
transparent: true,
opacity: 0.5,
wireframe: false,
side: THREE.DoubleSide
}});
cavityMesh = new THREE.Mesh(cavityGeometry, cavityMaterial);
scene.add(cavityMesh);
registerInitialPose(cavityMesh);
const cavityWireframe = new THREE.WireframeGeometry(cavityGeometry);
const cavityLine = new THREE.LineSegments(cavityWireframe);
cavityLine.material.depthTest = false;
cavityLine.material.opacity = 0.6;
cavityLine.material.transparent = true;
cavityLine.material.color = new THREE.Color(0x1565C0);
cavityMesh.add(cavityLine);
}} else {{
createSimpleCavity(width, height, depth);
}}
}} else {{
createSimpleCavity(width, height, depth);
}}
@@ -365,7 +442,7 @@ class HTMLGenerator:
}}
// 创建B板/动模(橙色,分型面以下)
if (cavityData && cavityData.mold_cavities && cavityData.mold_cavities.core && cavityData.mold_cavities.core.vertices) {{
if (coreRequired && cavityData && cavityData.mold_cavities && cavityData.mold_cavities.core && cavityData.mold_cavities.core.vertices) {{
const coreVerts = cavityData.mold_cavities.core.vertices;
const coreFaces = cavityData.mold_cavities.core.faces;
@@ -373,33 +450,40 @@ class HTMLGenerator:
const coreGeometry = new THREE.BufferGeometry();
const positions = new Float32Array(normalizePositions(coreVerts, centerOffset));
const indices = new Uint32Array(toFlatArray(coreFaces));
coreGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
coreGeometry.setIndex(new THREE.BufferAttribute(indices, 1));
coreGeometry.computeVertexNormals();
const coreMaterial = new THREE.MeshPhongMaterial({{
color: 0xFF9800,
transparent: true,
opacity: 0.5,
wireframe: false,
side: THREE.DoubleSide
}});
coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
scene.add(coreMesh);
// 添加线框
const coreWireframe = new THREE.WireframeGeometry(coreGeometry);
const coreLine = new THREE.LineSegments(coreWireframe);
coreLine.material.depthTest = false;
coreLine.material.opacity = 0.6;
coreLine.material.transparent = true;
coreLine.material.color = new THREE.Color(0xE65100);
coreMesh.add(coreLine);
let coreBuilt = false;
if (isValidIndexedGeometry(positions, indices)) {{
coreGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
coreGeometry.setIndex(new THREE.BufferAttribute(indices, 1));
coreGeometry.computeVertexNormals();
coreBuilt = true;
}}
if (coreBuilt) {{
const coreMaterial = new THREE.MeshPhongMaterial({{
color: 0xFF9800,
transparent: true,
opacity: 0.5,
wireframe: false,
side: THREE.DoubleSide
}});
coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
scene.add(coreMesh);
registerInitialPose(coreMesh);
const coreWireframe = new THREE.WireframeGeometry(coreGeometry);
const coreLine = new THREE.LineSegments(coreWireframe);
coreLine.material.depthTest = false;
coreLine.material.opacity = 0.6;
coreLine.material.transparent = true;
coreLine.material.color = new THREE.Color(0xE65100);
coreMesh.add(coreLine);
}} else {{
createSimpleCore(width, height, depth);
}}
}} else {{
createSimpleCore(width, height, depth);
}}
}} else {{
}} else if (coreRequired) {{
createSimpleCore(width, height, depth);
}}
@@ -415,6 +499,7 @@ class HTMLGenerator:
partingMesh = new THREE.Mesh(partingGeometry, partingMaterial);
partingMesh.position.set(0, 0, 0);
scene.add(partingMesh);
registerInitialPose(partingMesh);
// 添加产品线框
if (productMesh) {{
@@ -444,6 +529,7 @@ class HTMLGenerator:
// Z轴开模:A板放在分型面以上
cavityMesh.position.set(0, 0, halfDepth / 2 + 5);
scene.add(cavityMesh);
registerInitialPose(cavityMesh);
const cavityWireframe = new THREE.WireframeGeometry(cavityGeometry);
const cavityLine = new THREE.LineSegments(cavityWireframe);
@@ -470,6 +556,7 @@ class HTMLGenerator:
// Z轴开模:B板放在分型面以下
coreMesh.position.set(0, 0, -halfDepth / 2 - 5);
scene.add(coreMesh);
registerInitialPose(coreMesh);
const coreWireframe = new THREE.WireframeGeometry(coreGeometry);
const coreLine = new THREE.LineSegments(coreWireframe);
@@ -501,7 +588,28 @@ class HTMLGenerator:
// 控制函数
function resetView() {{
controls.reset();
if (splitAnimId) {{
cancelAnimationFrame(splitAnimId);
splitAnimId = null;
}}
isSplit = false;
const btn = document.getElementById('splitBtn');
if (btn) btn.textContent = '分模拆分';
[productMesh, cavityMesh, coreMesh, partingMesh, pointcloudMesh].forEach(mesh => {{
if (!mesh) return;
if (mesh.userData.initialPosition) {{
mesh.position.copy(mesh.userData.initialPosition);
}} else {{
mesh.position.set(0, 0, 0);
}}
mesh.visible = mesh.userData.initialVisible !== false;
}});
if (partingMesh && partingMesh.material) {{
partingMesh.material.opacity = 0.3;
partingMesh.visible = true;
}}
fitCameraToScene();
}}
function toggleWireframe() {{