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;
+ }
+ }}