This commit is contained in:
2026-03-07 01:18:25 +08:00
parent 7ccc97134d
commit e82d2b524b
3 changed files with 190 additions and 112 deletions
+6 -2
View File
@@ -648,15 +648,19 @@ async def process_file_core(
detailed_cavity_json detailed_cavity_json
) )
# 8. 生成HTML可视化(包含型腔信息) # 8. 生成HTML可视化(包含型腔信息和点云数据)
await storage_service.update_task_status( await storage_service.update_task_status(
db_session, task_id, "processing", 85, "生成可视化报告" db_session, task_id, "processing", 85, "生成可视化报告"
) )
# 获取点云数据
pointcloud_data = mesh_result.get("pointcloud", {}) if mesh_result else {}
html_file_path = html_generator.generate_and_save_visualization( html_file_path = html_generator.generate_and_save_visualization(
geometry_data, geometry_data,
Path(file_path).name, Path(file_path).name,
cavity_data=detailed_cavity_json cavity_data=detailed_cavity_json,
pointcloud_data=pointcloud_data
) )
# 保存HTML文件信息 # 保存HTML文件信息
+105 -80
View File
@@ -1,102 +1,127 @@
# src/core/mesh_generator.py # src/core/mesh_generator.py
import logging import logging
import numpy as np import numpy as np
from typing import Dict, List, Optional from typing import Dict, List, Optional, Any
import pyvista as pv
import trimesh import trimesh
from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh from OCC.Core.BRepMesh import BRepMesh_IncrementalMesh
from OCC.Core.TopExp import TopExp_Explorer
from OCC.Core.TopAbs import TopAbs_FACE
from OCC.Core.BRep import BRep_Tool
from OCC.Core.gp import gp_Pnt, gp_Vec
from OCC.Core.BRepBuilderAPI import BRepBuilderAPI_MakeFace, BRepBuilderAPI_MakeEdge
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class MeshGenerator: class MeshGenerator:
"""网格生成器 - 使用PyVista和Trimesh""" """网格生成器 - 从PythonOCC形状生成点云"""
def __init__(self, quality: str = "medium"): def __init__(self, quality: str = "medium"):
self.quality_settings = { self.quality_settings = {
"low": 0.5, "low": 1.0,
"medium": 0.1, "medium": 0.5,
"high": 0.01 "high": 0.1
} }
self.quality = self.quality_settings.get(quality, 0.1) self.quality = self.quality_settings.get(quality, 0.5)
def generate_mesh_from_shape(self, shape, num_points: int = 10000) -> Dict: def generate_mesh_from_shape(self, shape, num_points: int = 50000) -> Dict:
"""从形状生成网格数据""" """从PythonOCC形状生成点云数据"""
try: try:
# 方法1: 使用PythonOCC生成网格 # 生成网格
occ_mesh = self._generate_occ_mesh(shape) mesh = BRepMesh_IncrementalMesh(shape, self.quality)
mesh.Perform()
# 方法2: 转换为PyVista网格 logger.info(f"OCC网格生成完成, 网格状态: {mesh.IsDone()}")
pv_mesh = self._convert_to_pyvista(occ_mesh)
# 提取三角形面数据
# 方法3: 转换为Trimesh网格 vertices = []
tri_mesh = self._convert_to_trimesh(pv_mesh) faces = []
vertex_map = {}
# 生成点云 vertex_index = 0
pointcloud = self._generate_pointcloud(tri_mesh, num_points)
# 遍历所有面
return { explorer = TopExp_Explorer(shape, TopAbs_FACE)
"pyvista_mesh": pv_mesh, while explorer.More():
"trimesh_mesh": tri_mesh, face = explorer.Current()
"pointcloud": pointcloud
} # 获取面的三角形剖分
face_triangulation = BRep_Tool.Triangulation(face)
except Exception as e:
logger.error(f"网格生成失败: {e}") for i in range(1, face_triangulation.NbTriangles() + 1):
raise # 获取三角形的三个顶点
tri = face_triangulation.Triangle(i)
def _generate_occ_mesh(self, shape) -> any:
"""使用PythonOCC生成网格""" # 获取顶点坐标
mesh = BRepMesh_IncrementalMesh(shape, self.quality) v1 = BRep_Tool.Pnt(face_triangulation, tri.Value(1) + 1)
mesh.Perform() v2 = BRep_Tool.Pnt(face_triangulation, tri.Value(2) + 1)
return mesh v3 = BRep_Tool.Pnt(face_triangulation, tri.Value(3) + 1)
def _convert_to_pyvista(self, occ_mesh) -> pv.PolyData: # 转换为坐标
"""转换为PyVista网格""" p1 = (v1.X(), v1.Y(), v1.Z())
# 这里需要从OCC网格中提取顶点和面数据 p2 = (v2.X(), v2.Y(), v2.Z())
# 简化实现 - 实际需要遍历OCC网格数据结构 p3 = (v3.X(), v3.Y(), v3.Z())
try:
# 创建示例网格数据 # 添加顶点并获取索引
cube = pv.Cube() def add_vertex(p):
return cube nonlocal vertex_index
except Exception as e: key = (round(p[0], 6), round(p[1], 6), round(p[2], 6))
logger.warning(f"PyVista转换失败,使用备用方法: {e}") if key not in vertex_map:
return self._create_sample_mesh() vertex_map[key] = vertex_index
vertices.append(p)
def _convert_to_trimesh(self, pv_mesh) -> trimesh.Trimesh: vertex_index += 1
"""转换为Trimesh网格""" return vertex_map[key]
try:
# 从PyVista转换 idx1 = add_vertex(p1)
vertices = pv_mesh.points idx2 = add_vertex(p2)
faces = pv_mesh.faces.reshape(-1, 4)[:, 1:4] # 假设三角形网格 idx3 = add_vertex(p3)
return trimesh.Trimesh(vertices=vertices, faces=faces) faces.append([idx1 - 1, idx2 - 1, idx3 - 1])
except Exception as e:
logger.warning(f"Trimesh转换失败: {e}") vertices = np.array(vertices, dtype=np.float32)
return self._create_sample_trimesh() faces = np.array(faces, dtype=np.int32)
def _generate_pointcloud(self, mesh: trimesh.Trimesh, num_points: int) -> Dict: logger.info(f"提取了 {len(vertices)} 个顶点, {len(faces)} 个三角形面")
"""从网格生成点云"""
try: # 创建Trimesh对象
# 均匀采样点云 tri_mesh = trimesh.Trimesh(vertices=vertices, faces=faces)
points, face_indices = trimesh.sample.sample_surface(mesh, num_points)
# 在网格表面采样点云
# 计算法向量 points, face_idx = trimesh.sample.sample_surface(tri_mesh, num_points)
normals = mesh.face_normals[face_indices]
# 获取法向量
normals = tri_mesh.face_normals[face_idx]
logger.info(f"生成了 {len(points)} 个点云点")
return { return {
"vertices": vertices.tolist(),
"faces": faces.tolist(),
"points": points.tolist(), "points": points.tolist(),
"normals": normals.tolist(), "normals": normals.tolist(),
"count": len(points) "point_count": len(points),
"vertex_count": len(vertices),
"face_count": len(faces)
} }
except Exception as e: except Exception as e:
logger.error(f"点云生成失败: {e}") logger.error(f"网格生成失败: {e}")
raise import traceback
logger.error(traceback.format_exc())
# 返回示例数据
return self._create_sample_pointcloud()
def _create_sample_mesh(self) -> pv.PolyData: def _create_sample_pointcloud(self) -> Dict:
"""创建示例网格(备用)""" """创建示例点云(备用)"""
return pv.Cube() # 创建一个简单的立方体点云
mesh = trimesh.creation.box([100, 80, 50])
def _create_sample_trimesh(self) -> trimesh.Trimesh: points, _ = trimesh.sample.sample_surface(mesh, 5000)
"""创建示例Trimesh(备用)""" normals = mesh.face_normals
return trimesh.creation.box([100, 80, 50])
return {
"vertices": mesh.vertices.tolist(),
"faces": mesh.faces.tolist(),
"points": points.tolist(),
"normals": normals.tolist(),
"point_count": len(points),
"vertex_count": len(mesh.vertices),
"face_count": len(mesh.faces)
}
+79 -30
View File
@@ -19,7 +19,7 @@ class HTMLGenerator:
geometry_data: Dict[str, Any], geometry_data: Dict[str, Any],
stp_filename: str, stp_filename: str,
cavity_data: Optional[Dict[str, Any]] = None, cavity_data: Optional[Dict[str, Any]] = None,
key_info: Optional[Dict[str, Any]] = None pointcloud_data: Optional[Dict[str, Any]] = None
) -> str: ) -> str:
"""生成3D可视化HTML页面""" """生成3D可视化HTML页面"""
@@ -178,6 +178,7 @@ class HTMLGenerator:
<button onclick="toggleProduct()">显示/隐藏产品</button> <button onclick="toggleProduct()">显示/隐藏产品</button>
<button onclick="toggleMold()">显示/隐藏模具</button> <button onclick="toggleMold()">显示/隐藏模具</button>
<button onclick="toggleParting()">显示/隐藏分型面</button> <button onclick="toggleParting()">显示/隐藏分型面</button>
<button onclick="togglePointcloud()">显示/隐藏点云</button>
</div> </div>
</div> </div>
@@ -208,29 +209,68 @@ class HTMLGenerator:
const axesHelper = new THREE.AxesHelper(50); const axesHelper = new THREE.AxesHelper(50);
scene.add(axesHelper); scene.add(axesHelper);
// 创建几何体(模拟模具形状) // 创建几何体(使用点云数据或模拟模具形状)
const geometryData = {json.dumps(geometry_data, indent=2)}; const geometryData = {json.dumps(geometry_data, indent=2)};
const cavityData = {json.dumps(cavity_data, indent=2) if cavity_data else 'null'}; const cavityData = {json.dumps(cavity_data, indent=2) if cavity_data else 'null'};
const pointcloudData = {json.dumps(pointcloud_data, indent=2) if pointcloud_data else 'null'};
// 根据边界框创建模拟几何体 // 全局变量
let productMesh, cavityMesh, coreMesh, partingMesh, pointcloudMesh;
let productVisible = true;
let moldVisible = true;
let partingVisible = true;
let pointcloudVisible = true;
// 根据边界框创建几何体
const bbox = geometryData.bounding_box; const bbox = geometryData.bounding_box;
if (bbox) {{ if (bbox) {{
const width = bbox.dimensions ? bbox.dimensions[0] : 100; const width = bbox.dimensions ? bbox.dimensions[0] : 100;
const height = bbox.dimensions ? bbox.dimensions[1] : 100; const height = bbox.dimensions ? bbox.dimensions[1] : 100;
const depth = bbox.dimensions ? bbox.dimensions[2] : 100; const depth = bbox.dimensions ? bbox.dimensions[2] : 100;
// 创建产品几何体(半透明绿色) // 如果有点云数据,创建点云模型
const productGeometry = new THREE.BoxGeometry(width * 0.9, height * 0.9, depth * 0.9); if (pointcloudData && pointcloudData.points && pointcloudData.points.length > 0) {{
const productMaterial = new THREE.MeshPhongMaterial({{ // 创建点云几何体
color: 0x4CAF50, const pointGeometry = new THREE.BufferGeometry();
transparent: true, const positions = new Float32Array(pointcloudData.points.flat());
opacity: 0.6, pointGeometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
wireframe: false
}}); // 添加法向量(如果有)
if (pointcloudData.normals && pointcloudData.normals.length > 0) {{
const productMesh = new THREE.Mesh(productGeometry, productMaterial); const normals = new Float32Array(pointcloudData.normals.flat());
productMesh.position.set(0, 0, 0); pointGeometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3));
scene.add(productMesh); }}
// 创建点云材质
const pointMaterial = new THREE.PointsMaterial({{
color: 0x4CAF50,
size: 0.5,
transparent: true,
opacity: 0.8
}});
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);
}} 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);
}}
// 创建型腔(上半模 - 蓝色) // 创建型腔(上半模 - 蓝色)
const cavityGeometry = new THREE.BoxGeometry(width * 1.1, height * 0.3, depth * 1.1); const cavityGeometry = new THREE.BoxGeometry(width * 1.1, height * 0.3, depth * 1.1);
@@ -241,7 +281,7 @@ class HTMLGenerator:
wireframe: false wireframe: false
}}); }});
const cavityMesh = new THREE.Mesh(cavityGeometry, cavityMaterial); cavityMesh = new THREE.Mesh(cavityGeometry, cavityMaterial);
cavityMesh.position.set(0, height * 0.6, 0); cavityMesh.position.set(0, height * 0.6, 0);
scene.add(cavityMesh); scene.add(cavityMesh);
@@ -254,7 +294,7 @@ class HTMLGenerator:
wireframe: false wireframe: false
}}); }});
const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial); coreMesh = new THREE.Mesh(coreGeometry, coreMaterial);
coreMesh.position.set(0, -height * 0.6, 0); coreMesh.position.set(0, -height * 0.6, 0);
scene.add(coreMesh); scene.add(coreMesh);
@@ -267,21 +307,22 @@ class HTMLGenerator:
side: THREE.DoubleSide side: THREE.DoubleSide
}}); }});
const partingMesh = new THREE.Mesh(partingGeometry, partingMaterial); partingMesh = new THREE.Mesh(partingGeometry, partingMaterial);
partingMesh.rotation.x = Math.PI / 2; partingMesh.rotation.x = Math.PI / 2;
partingMesh.position.set(0, 0, 0); partingMesh.position.set(0, 0, 0);
scene.add(partingMesh); scene.add(partingMesh);
// 添加产品线框 // 添加线框
const productWireframe = new THREE.WireframeGeometry(productGeometry); if (productMesh) {{
const productLine = new THREE.LineSegments(productWireframe); const productWireframe = new THREE.WireframeGeometry(productMesh.geometry);
productLine.material.depthTest = false; const productLine = new THREE.LineSegments(productWireframe);
productLine.material.opacity = 0.5; productLine.material.depthTest = false;
productLine.material.transparent = true; productLine.material.opacity = 0.5;
productLine.material.color = new THREE.Color(0x2E7D32); productLine.material.transparent = true;
productMesh.add(productLine); productLine.material.color = new THREE.Color(0x2E7D32);
productMesh.add(productLine);
}}
// 添加型腔线框
const cavityWireframe = new THREE.WireframeGeometry(cavityGeometry); const cavityWireframe = new THREE.WireframeGeometry(cavityGeometry);
const cavityLine = new THREE.LineSegments(cavityWireframe); const cavityLine = new THREE.LineSegments(cavityWireframe);
cavityLine.material.depthTest = false; cavityLine.material.depthTest = false;
@@ -290,7 +331,6 @@ class HTMLGenerator:
cavityLine.material.color = new THREE.Color(0x1565C0); cavityLine.material.color = new THREE.Color(0x1565C0);
cavityMesh.add(cavityLine); cavityMesh.add(cavityLine);
// 添加型芯线框
const coreWireframe = new THREE.WireframeGeometry(coreGeometry); const coreWireframe = new THREE.WireframeGeometry(coreGeometry);
const coreLine = new THREE.LineSegments(coreWireframe); const coreLine = new THREE.LineSegments(coreWireframe);
coreLine.material.depthTest = false; coreLine.material.depthTest = false;
@@ -359,6 +399,13 @@ class HTMLGenerator:
partingMesh.visible = partingVisible; partingMesh.visible = partingVisible;
}} }}
}} }}
function togglePointcloud() {{
if (pointcloudMesh) {{
pointcloudVisible = !pointcloudVisible;
pointcloudMesh.visible = pointcloudVisible;
}
}}
</script> </script>
</body> </body>
</html> </html>
@@ -385,7 +432,8 @@ class HTMLGenerator:
self, self,
geometry_data: Dict[str, Any], geometry_data: Dict[str, Any],
stp_filename: str, stp_filename: str,
cavity_data: Optional[Dict[str, Any]] = None cavity_data: Optional[Dict[str, Any]] = None,
pointcloud_data: Optional[Dict[str, Any]] = None
) -> str: ) -> str:
"""生成并保存可视化HTML文件""" """生成并保存可视化HTML文件"""
try: try:
@@ -393,7 +441,8 @@ class HTMLGenerator:
html_content = self.generate_3d_viewer_html( html_content = self.generate_3d_viewer_html(
geometry_data, geometry_data,
stp_filename, stp_filename,
cavity_data=cavity_data cavity_data=cavity_data,
pointcloud_data=pointcloud_data
) )
# 创建文件名 # 创建文件名