2026-02-11 22:40:35 +08:00
|
|
|
|
# utils/html_generator.py
|
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
|
from typing import Dict, Any, Optional
|
|
|
|
|
|
import json
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from utils.logger import get_logger
|
|
|
|
|
|
|
|
|
|
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
class HTMLGenerator:
|
|
|
|
|
|
"""HTML文件生成器"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self, output_dir: str = "./html_output"):
|
|
|
|
|
|
self.output_dir = Path(output_dir)
|
|
|
|
|
|
self.output_dir.mkdir(exist_ok=True)
|
|
|
|
|
|
|
|
|
|
|
|
def generate_3d_viewer_html(
|
|
|
|
|
|
self,
|
|
|
|
|
|
geometry_data: Dict[str, Any],
|
|
|
|
|
|
stp_filename: str,
|
|
|
|
|
|
cavity_data: Optional[Dict[str, Any]] = None,
|
2026-03-07 01:18:25 +08:00
|
|
|
|
pointcloud_data: Optional[Dict[str, Any]] = None
|
2026-02-11 22:40:35 +08:00
|
|
|
|
) -> str:
|
|
|
|
|
|
"""生成3D可视化HTML页面"""
|
|
|
|
|
|
|
|
|
|
|
|
# 提取几何数据
|
|
|
|
|
|
bounding_box = geometry_data.get("bounding_box", {})
|
|
|
|
|
|
volume = geometry_data.get("volume", 0) or 0
|
|
|
|
|
|
surface_area = geometry_data.get("surface_area", 0) or 0
|
|
|
|
|
|
topology = geometry_data.get("topology", {})
|
|
|
|
|
|
center_of_mass = geometry_data.get("center_of_mass", [0, 0, 0])
|
|
|
|
|
|
cavity_html = ""
|
2026-02-15 01:09:53 +08:00
|
|
|
|
# 强制测试:无论cavity_data如何,都显示面板
|
|
|
|
|
|
if True:
|
2026-02-15 01:01:03 +08:00
|
|
|
|
# 安全获取嵌套数据
|
2026-02-15 01:09:53 +08:00
|
|
|
|
metadata = cavity_data.get("metadata", {}) if cavity_data else {}
|
|
|
|
|
|
manufacturing_info = cavity_data.get("manufacturing_info", {}) if cavity_data else {}
|
|
|
|
|
|
mold_cavities = cavity_data.get("mold_cavities", {}) if cavity_data else {}
|
2026-02-15 01:01:03 +08:00
|
|
|
|
cavity_key_info = mold_cavities.get("cavity_key_info", {})
|
|
|
|
|
|
geo_chars = cavity_key_info.get("geometric_characteristics", {})
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"生成HTML - 收缩率: {metadata.get('shrinkage_rate')}")
|
|
|
|
|
|
logger.info(f"生成HTML - 型腔材料: {manufacturing_info.get('mold_material')}")
|
|
|
|
|
|
logger.info(f"生成HTML - 产品体积: {geo_chars.get('product_volume')}")
|
|
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
cavity_html = f"""
|
2026-02-15 01:09:53 +08:00
|
|
|
|
<div id="cavity-info-panel" style="position: absolute; top: 10px; right: 10px; background: rgba(255,255,255,0.95); padding: 15px; border-radius: 8px; box-shadow: 0 2px 10px rgba(0,0,0,0.1); max-width: 320px;">
|
|
|
|
|
|
<h3 style="margin: 0 0 10px 0;">🔧 关键工艺参数</h3>
|
|
|
|
|
|
<div style="margin: 10px 0; border-bottom: 1px solid #ddd; padding-bottom: 5px;">
|
|
|
|
|
|
<strong style="color: #333;">模具参数</strong>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
2026-02-11 22:40:35 +08:00
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">收缩率:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{metadata.get("shrinkage_rate", "N/A")}</span>
|
2026-02-11 22:40:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">拔模角:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{metadata.get("draft_angle", "N/A")}°</span>
|
2026-02-11 22:40:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
2026-02-15 00:52:46 +08:00
|
|
|
|
<span class="metric-label">分型线长度:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{manufacturing_info.get("parting_line_length", "N/A")}</span>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
2026-02-15 01:09:53 +08:00
|
|
|
|
<div style="margin: 10px 0; border-bottom: 1px solid #ddd; padding-bottom: 5px;">
|
|
|
|
|
|
<strong style="color: #333;">几何特性</strong>
|
2026-02-11 22:40:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
2026-02-15 00:52:46 +08:00
|
|
|
|
<span class="metric-label">产品体积:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{geo_chars.get("product_volume", "N/A")}</span>
|
2026-02-11 22:40:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">产品重量:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{geo_chars.get("product_weight", "N/A")}</span>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">壁厚范围:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{geo_chars.get("wall_thickness_range", "N/A")}</span>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
2026-02-15 01:09:53 +08:00
|
|
|
|
<div style="margin: 10px 0; border-bottom: 1px solid #ddd; padding-bottom: 5px;">
|
|
|
|
|
|
<strong style="color: #333;">制造要求</strong>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">型腔材料:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{manufacturing_info.get("mold_material", "N/A")}</span>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">硬度:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{manufacturing_info.get("mold_hardness", "N/A")}</span>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">表面光洁度:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{manufacturing_info.get("surface_finish", "N/A")}</span>
|
2026-02-15 00:52:46 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">预估周期:</span>
|
2026-02-15 01:01:03 +08:00
|
|
|
|
<span class="metric-value">{manufacturing_info.get("estimated_cycle_time", "N/A")}</span>
|
2026-02-11 22:40:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
"""
|
2026-02-15 01:01:03 +08:00
|
|
|
|
else:
|
2026-02-15 01:03:16 +08:00
|
|
|
|
logger.warning(f"cavity_data 为空,无法显示工艺参数 - 文件: {stp_filename}")
|
2026-02-15 01:09:53 +08:00
|
|
|
|
cavity_html = ""
|
2026-02-11 22:40:35 +08:00
|
|
|
|
html_content = f"""
|
|
|
|
|
|
<!DOCTYPE html>
|
|
|
|
|
|
<html lang="zh-CN">
|
|
|
|
|
|
<head>
|
|
|
|
|
|
<meta charset="UTF-8">
|
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
|
|
|
<title>3D模具几何可视化 - {stp_filename}</title>
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/build/three.min.js"></script>
|
|
|
|
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.132.2/examples/js/controls/OrbitControls.js"></script>
|
|
|
|
|
|
<style>
|
|
|
|
|
|
body {{ margin: 0; overflow: hidden; font-family: Arial, sans-serif; }}
|
|
|
|
|
|
#container {{ position: relative; width: 100vw; height: 100vh; }}
|
|
|
|
|
|
#canvas {{ display: block; }}
|
|
|
|
|
|
#info-panel {{
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: 10px;
|
|
|
|
|
|
left: 10px;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
max-width: 300px;
|
|
|
|
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
|
|
|
|
}}
|
|
|
|
|
|
#controls {{
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
bottom: 10px;
|
|
|
|
|
|
left: 10px;
|
|
|
|
|
|
background: rgba(255, 255, 255, 0.9);
|
|
|
|
|
|
padding: 10px;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
}}
|
|
|
|
|
|
.metric {{ margin: 5px 0; }}
|
|
|
|
|
|
.metric-label {{ font-weight: bold; color: #333; }}
|
|
|
|
|
|
.metric-value {{ color: #666; }}
|
|
|
|
|
|
</style>
|
|
|
|
|
|
</head>
|
|
|
|
|
|
<body>
|
|
|
|
|
|
<div id="container">
|
|
|
|
|
|
<canvas id="canvas"></canvas>
|
|
|
|
|
|
{cavity_html}
|
|
|
|
|
|
<div id="info-panel">
|
|
|
|
|
|
<h3>模具几何信息</h3>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">文件名:</span>
|
|
|
|
|
|
<span class="metric-value">{stp_filename}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">体积:</span>
|
|
|
|
|
|
<span class="metric-value">{volume:.2f} mm³</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">表面积:</span>
|
|
|
|
|
|
<span class="metric-value">{surface_area:.2f} mm²</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">边界框:</span>
|
|
|
|
|
|
<span class="metric-value">{bounding_box.get('dimensions', [0, 0, 0])[0]:.1f} × {bounding_box.get('dimensions', [0, 0, 0])[1]:.1f} × {bounding_box.get('dimensions', [0, 0, 0])[2]:.1f} mm</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">面数:</span>
|
|
|
|
|
|
<span class="metric-value">{topology.get('faces', 0)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">边数:</span>
|
|
|
|
|
|
<span class="metric-value">{topology.get('edges', 0)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
<div class="metric">
|
|
|
|
|
|
<span class="metric-label">顶点数:</span>
|
|
|
|
|
|
<span class="metric-value">{topology.get('vertices', 0)}</span>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<div id="controls">
|
|
|
|
|
|
<button onclick="resetView()">重置视图</button>
|
|
|
|
|
|
<button onclick="toggleWireframe()">切换线框</button>
|
2026-03-07 01:09:13 +08:00
|
|
|
|
<button onclick="toggleProduct()">显示/隐藏产品</button>
|
|
|
|
|
|
<button onclick="toggleMold()">显示/隐藏模具</button>
|
|
|
|
|
|
<button onclick="toggleParting()">显示/隐藏分型面</button>
|
2026-03-07 01:18:25 +08:00
|
|
|
|
<button onclick="togglePointcloud()">显示/隐藏点云</button>
|
2026-02-11 22:40:35 +08:00
|
|
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
|
|
<script>
|
2026-03-07 01:09:13 +08:00
|
|
|
|
// 全局变量
|
2026-03-07 01:28:03 +08:00
|
|
|
|
let productMesh, cavityMesh, coreMesh, partingMesh, pointcloudMesh;
|
2026-03-07 01:09:13 +08:00
|
|
|
|
let productVisible = true;
|
|
|
|
|
|
let moldVisible = true;
|
|
|
|
|
|
let partingVisible = true;
|
2026-03-07 01:28:03 +08:00
|
|
|
|
let pointcloudVisible = true;
|
2026-03-07 01:09:13 +08:00
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
// 初始化Three.js场景
|
|
|
|
|
|
const scene = new THREE.Scene();
|
|
|
|
|
|
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
|
|
|
|
|
|
const renderer = new THREE.WebGLRenderer({{ canvas: document.getElementById('canvas') }});
|
|
|
|
|
|
|
|
|
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
|
|
|
renderer.setClearColor(0xf0f0f0);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加光源
|
|
|
|
|
|
const ambientLight = new THREE.AmbientLight(0x404040);
|
|
|
|
|
|
scene.add(ambientLight);
|
|
|
|
|
|
|
|
|
|
|
|
const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
|
|
|
|
|
|
directionalLight.position.set(1, 1, 1);
|
|
|
|
|
|
scene.add(directionalLight);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加坐标轴
|
|
|
|
|
|
const axesHelper = new THREE.AxesHelper(50);
|
|
|
|
|
|
scene.add(axesHelper);
|
|
|
|
|
|
|
2026-03-07 01:46:38 +08:00
|
|
|
|
// 添加轨道控制器 - 必须在使用之前定义
|
|
|
|
|
|
const controls = new THREE.OrbitControls(camera, renderer.domElement);
|
|
|
|
|
|
controls.enableDamping = true;
|
|
|
|
|
|
controls.dampingFactor = 0.25;
|
|
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
// 创建几何体(使用点云数据或模拟模具形状)
|
2026-02-11 22:40:35 +08:00
|
|
|
|
const geometryData = {json.dumps(geometry_data, indent=2)};
|
2026-03-07 01:09:13 +08:00
|
|
|
|
const cavityData = {json.dumps(cavity_data, indent=2) if cavity_data else 'null'};
|
2026-03-07 01:18:25 +08:00
|
|
|
|
const pointcloudData = {json.dumps(pointcloud_data, indent=2) if pointcloud_data else 'null'};
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
// 根据边界框创建几何体
|
2026-02-11 22:40:35 +08:00
|
|
|
|
const bbox = geometryData.bounding_box;
|
|
|
|
|
|
if (bbox) {{
|
|
|
|
|
|
const width = bbox.dimensions ? bbox.dimensions[0] : 100;
|
|
|
|
|
|
const height = bbox.dimensions ? bbox.dimensions[1] : 100;
|
|
|
|
|
|
const depth = bbox.dimensions ? bbox.dimensions[2] : 100;
|
|
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
// 如果有点云数据,创建点云模型
|
|
|
|
|
|
if (pointcloudData && pointcloudData.points && pointcloudData.points.length > 0) {{
|
|
|
|
|
|
// 创建点云几何体
|
|
|
|
|
|
const pointGeometry = new THREE.BufferGeometry();
|
|
|
|
|
|
const positions = new Float32Array(pointcloudData.points.flat());
|
|
|
|
|
|
pointGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
|
|
|
|
|
|
|
|
|
|
|
|
// 添加法向量(如果有)
|
|
|
|
|
|
if (pointcloudData.normals && pointcloudData.normals.length > 0) {{
|
|
|
|
|
|
const normals = new Float32Array(pointcloudData.normals.flat());
|
|
|
|
|
|
pointGeometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
// 创建点云材质
|
|
|
|
|
|
const pointMaterial = new THREE.PointsMaterial({{
|
|
|
|
|
|
color: 0x4CAF50,
|
2026-03-07 01:46:38 +08:00
|
|
|
|
size: 1.0,
|
2026-03-07 01:18:25 +08:00
|
|
|
|
transparent: true,
|
2026-03-07 01:46:38 +08:00
|
|
|
|
opacity: 0.9
|
2026-03-07 01:18:25 +08:00
|
|
|
|
}});
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2026-03-07 01:46:38 +08:00
|
|
|
|
|
|
|
|
|
|
// 根据边界框大小调整相机距离
|
|
|
|
|
|
const boundingSphere = new THREE.Sphere();
|
|
|
|
|
|
pointGeometry.boundingBox.getBoundingSphere(boundingSphere);
|
|
|
|
|
|
const cameraDistance = boundingSphere.radius * 3;
|
|
|
|
|
|
camera.position.set(cameraDistance, cameraDistance, cameraDistance);
|
2026-03-07 01:18:25 +08:00
|
|
|
|
}} else {{
|
|
|
|
|
|
// 创建产品几何体(半透明绿色)
|
|
|
|
|
|
const productGeometry = new THREE.BoxGeometry(width * 0.9, height * 0.9, depth * 0.9);
|
|
|
|
|
|
const productMaterial = new THREE.MeshPhongMaterial({{
|
|
|
|
|
|
color: 0x4CAF50,
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: 0.6,
|
|
|
|
|
|
wireframe: false
|
|
|
|
|
|
}});
|
|
|
|
|
|
|
|
|
|
|
|
productMesh = new THREE.Mesh(productGeometry, productMaterial);
|
|
|
|
|
|
productMesh.position.set(0, 0, 0);
|
|
|
|
|
|
scene.add(productMesh);
|
|
|
|
|
|
}}
|
2026-03-07 01:09:13 +08:00
|
|
|
|
|
|
|
|
|
|
// 创建型腔(上半模 - 蓝色)
|
|
|
|
|
|
const cavityGeometry = new THREE.BoxGeometry(width * 1.1, height * 0.3, depth * 1.1);
|
|
|
|
|
|
const cavityMaterial = new THREE.MeshPhongMaterial({{
|
|
|
|
|
|
color: 0x2196F3,
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: 0.4,
|
2026-02-11 22:40:35 +08:00
|
|
|
|
wireframe: false
|
|
|
|
|
|
}});
|
|
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
cavityMesh = new THREE.Mesh(cavityGeometry, cavityMaterial);
|
2026-03-07 01:09:13 +08:00
|
|
|
|
cavityMesh.position.set(0, height * 0.6, 0);
|
|
|
|
|
|
scene.add(cavityMesh);
|
2026-02-11 22:40:35 +08:00
|
|
|
|
|
2026-03-07 01:09:13 +08:00
|
|
|
|
// 创建型芯(下半模 - 橙色)
|
|
|
|
|
|
const coreGeometry = new THREE.BoxGeometry(width * 1.1, height * 0.3, depth * 1.1);
|
|
|
|
|
|
const coreMaterial = new THREE.MeshPhongMaterial({{
|
|
|
|
|
|
color: 0xFF9800,
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: 0.4,
|
|
|
|
|
|
wireframe: false
|
|
|
|
|
|
}});
|
|
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
|
2026-03-07 01:09:13 +08:00
|
|
|
|
coreMesh.position.set(0, -height * 0.6, 0);
|
|
|
|
|
|
scene.add(coreMesh);
|
|
|
|
|
|
|
|
|
|
|
|
// 创建分型面(红色平面)
|
|
|
|
|
|
const partingGeometry = new THREE.PlaneGeometry(width * 1.2, depth * 1.2);
|
|
|
|
|
|
const partingMaterial = new THREE.MeshBasicMaterial({{
|
|
|
|
|
|
color: 0xF44336,
|
|
|
|
|
|
transparent: true,
|
|
|
|
|
|
opacity: 0.3,
|
|
|
|
|
|
side: THREE.DoubleSide
|
|
|
|
|
|
}});
|
|
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
partingMesh = new THREE.Mesh(partingGeometry, partingMaterial);
|
2026-03-07 01:09:13 +08:00
|
|
|
|
partingMesh.rotation.x = Math.PI / 2;
|
|
|
|
|
|
partingMesh.position.set(0, 0, 0);
|
|
|
|
|
|
scene.add(partingMesh);
|
|
|
|
|
|
|
2026-03-07 01:18:25 +08:00
|
|
|
|
// 添加线框
|
|
|
|
|
|
if (productMesh) {{
|
|
|
|
|
|
const productWireframe = new THREE.WireframeGeometry(productMesh.geometry);
|
|
|
|
|
|
const productLine = new THREE.LineSegments(productWireframe);
|
|
|
|
|
|
productLine.material.depthTest = false;
|
|
|
|
|
|
productLine.material.opacity = 0.5;
|
|
|
|
|
|
productLine.material.transparent = true;
|
|
|
|
|
|
productLine.material.color = new THREE.Color(0x2E7D32);
|
|
|
|
|
|
productMesh.add(productLine);
|
|
|
|
|
|
}}
|
2026-03-07 01:09:13 +08:00
|
|
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
|
|
|
|
|
|
|
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);
|
2026-02-11 22:40:35 +08:00
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
// 设置相机位置
|
|
|
|
|
|
camera.position.set(200, 200, 200);
|
|
|
|
|
|
camera.lookAt(0, 0, 0);
|
|
|
|
|
|
|
|
|
|
|
|
// 动画循环
|
|
|
|
|
|
function animate() {{
|
|
|
|
|
|
requestAnimationFrame(animate);
|
|
|
|
|
|
controls.update();
|
|
|
|
|
|
renderer.render(scene, camera);
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
animate();
|
|
|
|
|
|
|
|
|
|
|
|
// 窗口大小调整
|
|
|
|
|
|
window.addEventListener('resize', () => {{
|
|
|
|
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
|
|
|
|
camera.updateProjectionMatrix();
|
|
|
|
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
|
|
|
|
}});
|
|
|
|
|
|
|
|
|
|
|
|
// 控制函数
|
|
|
|
|
|
function resetView() {{
|
|
|
|
|
|
controls.reset();
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleWireframe() {{
|
|
|
|
|
|
scene.traverse((child) => {{
|
|
|
|
|
|
if (child.isMesh) {{
|
|
|
|
|
|
child.material.wireframe = !child.material.wireframe;
|
|
|
|
|
|
}}
|
|
|
|
|
|
}});
|
|
|
|
|
|
}}
|
2026-03-07 01:09:13 +08:00
|
|
|
|
|
|
|
|
|
|
function toggleProduct() {{
|
|
|
|
|
|
if (productMesh) {{
|
|
|
|
|
|
productVisible = !productVisible;
|
|
|
|
|
|
productMesh.visible = productVisible;
|
|
|
|
|
|
}}
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleMold() {{
|
|
|
|
|
|
if (cavityMesh && coreMesh) {{
|
|
|
|
|
|
moldVisible = !moldVisible;
|
|
|
|
|
|
cavityMesh.visible = moldVisible;
|
|
|
|
|
|
coreMesh.visible = moldVisible;
|
|
|
|
|
|
}}
|
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
|
|
function toggleParting() {{
|
|
|
|
|
|
if (partingMesh) {{
|
|
|
|
|
|
partingVisible = !partingVisible;
|
|
|
|
|
|
partingMesh.visible = partingVisible;
|
|
|
|
|
|
}}
|
|
|
|
|
|
}}
|
2026-03-07 01:18:25 +08:00
|
|
|
|
|
|
|
|
|
|
function togglePointcloud() {{
|
|
|
|
|
|
if (pointcloudMesh) {{
|
|
|
|
|
|
pointcloudVisible = !pointcloudVisible;
|
|
|
|
|
|
pointcloudMesh.visible = pointcloudVisible;
|
2026-03-07 01:23:42 +08:00
|
|
|
|
}}
|
2026-03-07 01:18:25 +08:00
|
|
|
|
}}
|
2026-02-11 22:40:35 +08:00
|
|
|
|
</script>
|
|
|
|
|
|
</body>
|
|
|
|
|
|
</html>
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
|
|
return html_content
|
|
|
|
|
|
|
|
|
|
|
|
def save_html_file(self, html_content: str, filename: str) -> str:
|
|
|
|
|
|
"""保存HTML文件到磁盘"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
file_path = self.output_dir / filename
|
|
|
|
|
|
|
|
|
|
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
|
|
|
|
f.write(html_content)
|
|
|
|
|
|
|
|
|
|
|
|
logger.info(f"HTML文件保存成功: {file_path}")
|
|
|
|
|
|
return str(file_path)
|
|
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"保存HTML文件失败: {e}")
|
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
def generate_and_save_visualization(
|
2026-02-15 00:54:58 +08:00
|
|
|
|
self,
|
|
|
|
|
|
geometry_data: Dict[str, Any],
|
|
|
|
|
|
stp_filename: str,
|
2026-03-07 01:18:25 +08:00
|
|
|
|
cavity_data: Optional[Dict[str, Any]] = None,
|
|
|
|
|
|
pointcloud_data: Optional[Dict[str, Any]] = None
|
2026-02-11 22:40:35 +08:00
|
|
|
|
) -> str:
|
|
|
|
|
|
"""生成并保存可视化HTML文件"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
# 生成HTML内容
|
2026-02-15 00:54:58 +08:00
|
|
|
|
html_content = self.generate_3d_viewer_html(
|
|
|
|
|
|
geometry_data,
|
|
|
|
|
|
stp_filename,
|
2026-03-07 01:18:25 +08:00
|
|
|
|
cavity_data=cavity_data,
|
|
|
|
|
|
pointcloud_data=pointcloud_data
|
2026-02-15 00:54:58 +08:00
|
|
|
|
)
|
|
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
# 创建文件名
|
|
|
|
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
|
|
|
|
safe_filename = stp_filename.replace('.', '_').replace(' ', '_')
|
|
|
|
|
|
html_filename = f"{safe_filename}_{timestamp}.html"
|
2026-02-15 00:54:58 +08:00
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
# 保存文件
|
|
|
|
|
|
file_path = self.save_html_file(html_content, html_filename)
|
2026-02-15 00:54:58 +08:00
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
return file_path
|
2026-02-15 00:54:58 +08:00
|
|
|
|
|
2026-02-11 22:40:35 +08:00
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.error(f"生成可视化文件失败: {e}")
|
|
|
|
|
|
raise
|
|
|
|
|
|
|