From e82d2b524b339cf83a94a255467e9036c27b2d02 Mon Sep 17 00:00:00 2001 From: SZCJW <792430652@qq.com> Date: Sat, 7 Mar 2026 01:18:25 +0800 Subject: [PATCH] x --- src/api/routes.py | 8 +- src/core/mesh_generator.py | 185 ++++++++++++++++++++---------------- src/utils/html_generator.py | 109 +++++++++++++++------ 3 files changed, 190 insertions(+), 112 deletions(-) diff --git a/src/api/routes.py b/src/api/routes.py index 95f79dd..5a212a2 100644 --- a/src/api/routes.py +++ b/src/api/routes.py @@ -648,15 +648,19 @@ async def process_file_core( detailed_cavity_json ) - # 8. 生成HTML可视化(包含型腔信息) + # 8. 生成HTML可视化(包含型腔信息和点云数据) await storage_service.update_task_status( 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( geometry_data, Path(file_path).name, - cavity_data=detailed_cavity_json + cavity_data=detailed_cavity_json, + pointcloud_data=pointcloud_data ) # 保存HTML文件信息 diff --git a/src/core/mesh_generator.py b/src/core/mesh_generator.py index 1971f0f..7c8d953 100644 --- a/src/core/mesh_generator.py +++ b/src/core/mesh_generator.py @@ -1,102 +1,127 @@ # src/core/mesh_generator.py import logging import numpy as np -from typing import Dict, List, Optional -import pyvista as pv +from typing import Dict, List, Optional, Any import trimesh 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__) class MeshGenerator: - """网格生成器 - 使用PyVista和Trimesh""" + """网格生成器 - 从PythonOCC形状生成点云""" def __init__(self, quality: str = "medium"): self.quality_settings = { - "low": 0.5, - "medium": 0.1, - "high": 0.01 + "low": 1.0, + "medium": 0.5, + "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: - # 方法1: 使用PythonOCC生成网格 - occ_mesh = self._generate_occ_mesh(shape) - - # 方法2: 转换为PyVista网格 - pv_mesh = self._convert_to_pyvista(occ_mesh) - - # 方法3: 转换为Trimesh网格 - tri_mesh = self._convert_to_trimesh(pv_mesh) - - # 生成点云 - pointcloud = self._generate_pointcloud(tri_mesh, num_points) - - return { - "pyvista_mesh": pv_mesh, - "trimesh_mesh": tri_mesh, - "pointcloud": pointcloud - } - - except Exception as e: - logger.error(f"网格生成失败: {e}") - raise - - def _generate_occ_mesh(self, shape) -> any: - """使用PythonOCC生成网格""" - mesh = BRepMesh_IncrementalMesh(shape, self.quality) - mesh.Perform() - return mesh - - def _convert_to_pyvista(self, occ_mesh) -> pv.PolyData: - """转换为PyVista网格""" - # 这里需要从OCC网格中提取顶点和面数据 - # 简化实现 - 实际需要遍历OCC网格数据结构 - try: - # 创建示例网格数据 - cube = pv.Cube() - return cube - except Exception as e: - logger.warning(f"PyVista转换失败,使用备用方法: {e}") - return self._create_sample_mesh() - - def _convert_to_trimesh(self, pv_mesh) -> trimesh.Trimesh: - """转换为Trimesh网格""" - try: - # 从PyVista转换 - vertices = pv_mesh.points - faces = pv_mesh.faces.reshape(-1, 4)[:, 1:4] # 假设三角形网格 - - return trimesh.Trimesh(vertices=vertices, faces=faces) - except Exception as e: - logger.warning(f"Trimesh转换失败: {e}") - return self._create_sample_trimesh() - - def _generate_pointcloud(self, mesh: trimesh.Trimesh, num_points: int) -> Dict: - """从网格生成点云""" - try: - # 均匀采样点云 - points, face_indices = trimesh.sample.sample_surface(mesh, num_points) - - # 计算法向量 - normals = mesh.face_normals[face_indices] - + # 生成网格 + mesh = BRepMesh_IncrementalMesh(shape, self.quality) + mesh.Perform() + logger.info(f"OCC网格生成完成, 网格状态: {mesh.IsDone()}") + + # 提取三角形面数据 + vertices = [] + faces = [] + vertex_map = {} + vertex_index = 0 + + # 遍历所有面 + explorer = TopExp_Explorer(shape, TopAbs_FACE) + while explorer.More(): + face = explorer.Current() + + # 获取面的三角形剖分 + face_triangulation = BRep_Tool.Triangulation(face) + + for i in range(1, face_triangulation.NbTriangles() + 1): + # 获取三角形的三个顶点 + tri = face_triangulation.Triangle(i) + + # 获取顶点坐标 + v1 = BRep_Tool.Pnt(face_triangulation, tri.Value(1) + 1) + v2 = BRep_Tool.Pnt(face_triangulation, tri.Value(2) + 1) + v3 = BRep_Tool.Pnt(face_triangulation, tri.Value(3) + 1) + + # 转换为坐标 + p1 = (v1.X(), v1.Y(), v1.Z()) + p2 = (v2.X(), v2.Y(), v2.Z()) + p3 = (v3.X(), v3.Y(), v3.Z()) + + # 添加顶点并获取索引 + def add_vertex(p): + nonlocal vertex_index + key = (round(p[0], 6), round(p[1], 6), round(p[2], 6)) + if key not in vertex_map: + vertex_map[key] = vertex_index + vertices.append(p) + vertex_index += 1 + return vertex_map[key] + + idx1 = add_vertex(p1) + idx2 = add_vertex(p2) + idx3 = add_vertex(p3) + + faces.append([idx1 - 1, idx2 - 1, idx3 - 1]) + + vertices = np.array(vertices, dtype=np.float32) + faces = np.array(faces, dtype=np.int32) + + logger.info(f"提取了 {len(vertices)} 个顶点, {len(faces)} 个三角形面") + + # 创建Trimesh对象 + tri_mesh = trimesh.Trimesh(vertices=vertices, faces=faces) + + # 在网格表面采样点云 + points, face_idx = trimesh.sample.sample_surface(tri_mesh, num_points) + + # 获取法向量 + normals = tri_mesh.face_normals[face_idx] + + logger.info(f"生成了 {len(points)} 个点云点") + return { + "vertices": vertices.tolist(), + "faces": faces.tolist(), "points": points.tolist(), "normals": normals.tolist(), - "count": len(points) + "point_count": len(points), + "vertex_count": len(vertices), + "face_count": len(faces) } + except Exception as e: - logger.error(f"点云生成失败: {e}") - raise + logger.error(f"网格生成失败: {e}") + import traceback + logger.error(traceback.format_exc()) + # 返回示例数据 + return self._create_sample_pointcloud() - def _create_sample_mesh(self) -> pv.PolyData: - """创建示例网格(备用)""" - return pv.Cube() - - def _create_sample_trimesh(self) -> trimesh.Trimesh: - """创建示例Trimesh(备用)""" - return trimesh.creation.box([100, 80, 50]) \ No newline at end of file + def _create_sample_pointcloud(self) -> Dict: + """创建示例点云(备用)""" + # 创建一个简单的立方体点云 + mesh = trimesh.creation.box([100, 80, 50]) + points, _ = trimesh.sample.sample_surface(mesh, 5000) + normals = mesh.face_normals + + 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) + } diff --git a/src/utils/html_generator.py b/src/utils/html_generator.py index c1f1ed0..617eb0d 100644 --- a/src/utils/html_generator.py +++ b/src/utils/html_generator.py @@ -19,7 +19,7 @@ class HTMLGenerator: geometry_data: Dict[str, Any], stp_filename: str, cavity_data: Optional[Dict[str, Any]] = None, - key_info: Optional[Dict[str, Any]] = None + pointcloud_data: Optional[Dict[str, Any]] = None ) -> str: """生成3D可视化HTML页面""" @@ -178,6 +178,7 @@ class HTMLGenerator: + @@ -208,29 +209,68 @@ class HTMLGenerator: const axesHelper = new THREE.AxesHelper(50); scene.add(axesHelper); - // 创建几何体(模拟模具形状) + // 创建几何体(使用点云数据或模拟模具形状) const geometryData = {json.dumps(geometry_data, indent=2)}; 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; 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; - // 创建产品几何体(半透明绿色) - 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 - }}); - - const productMesh = new THREE.Mesh(productGeometry, productMaterial); - productMesh.position.set(0, 0, 0); - scene.add(productMesh); + // 如果有点云数据,创建点云模型 + 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, + 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); @@ -241,7 +281,7 @@ class HTMLGenerator: wireframe: false }}); - const cavityMesh = new THREE.Mesh(cavityGeometry, cavityMaterial); + cavityMesh = new THREE.Mesh(cavityGeometry, cavityMaterial); cavityMesh.position.set(0, height * 0.6, 0); scene.add(cavityMesh); @@ -254,7 +294,7 @@ class HTMLGenerator: wireframe: false }}); - const coreMesh = new THREE.Mesh(coreGeometry, coreMaterial); + coreMesh = new THREE.Mesh(coreGeometry, coreMaterial); coreMesh.position.set(0, -height * 0.6, 0); scene.add(coreMesh); @@ -267,21 +307,22 @@ class HTMLGenerator: side: THREE.DoubleSide }}); - const partingMesh = new THREE.Mesh(partingGeometry, partingMaterial); + partingMesh = new THREE.Mesh(partingGeometry, partingMaterial); partingMesh.rotation.x = Math.PI / 2; partingMesh.position.set(0, 0, 0); scene.add(partingMesh); - // 添加产品线框 - const productWireframe = new THREE.WireframeGeometry(productGeometry); - 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); + // 添加线框 + 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); + }} - // 添加型腔线框 const cavityWireframe = new THREE.WireframeGeometry(cavityGeometry); const cavityLine = new THREE.LineSegments(cavityWireframe); cavityLine.material.depthTest = false; @@ -290,7 +331,6 @@ class HTMLGenerator: 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; @@ -359,6 +399,13 @@ class HTMLGenerator: partingMesh.visible = partingVisible; }} }} + + function togglePointcloud() {{ + if (pointcloudMesh) {{ + pointcloudVisible = !pointcloudVisible; + pointcloudMesh.visible = pointcloudVisible; + } + }} @@ -385,7 +432,8 @@ class HTMLGenerator: self, geometry_data: Dict[str, Any], 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: """生成并保存可视化HTML文件""" try: @@ -393,7 +441,8 @@ class HTMLGenerator: html_content = self.generate_3d_viewer_html( geometry_data, stp_filename, - cavity_data=cavity_data + cavity_data=cavity_data, + pointcloud_data=pointcloud_data ) # 创建文件名