x
This commit is contained in:
+6
-2
@@ -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文件信息
|
||||
|
||||
+105
-80
@@ -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])
|
||||
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)
|
||||
}
|
||||
|
||||
+79
-30
@@ -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:
|
||||
<button onclick="toggleProduct()">显示/隐藏产品</button>
|
||||
<button onclick="toggleMold()">显示/隐藏模具</button>
|
||||
<button onclick="toggleParting()">显示/隐藏分型面</button>
|
||||
<button onclick="togglePointcloud()">显示/隐藏点云</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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
|
||||
)
|
||||
|
||||
# 创建文件名
|
||||
|
||||
Reference in New Issue
Block a user