x
This commit is contained in:
+198
-90
@@ -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() {{
|
||||
|
||||
Reference in New Issue
Block a user