UI升级改动

This commit is contained in:
2026-05-02 02:38:56 +08:00
parent 58d045354a
commit 06b3e54a39
4 changed files with 760 additions and 625 deletions
+1
View File
@@ -59,6 +59,7 @@ email-validator>=2.0.0
# 工具库
# ============================================
aiofiles>=23.0.0
orjson>=3.9.0
python-dotenv>=1.0.0
jinja2>=3.1.0
pyyaml>=6.0
+103 -16
View File
@@ -14,7 +14,7 @@ logger = logging.getLogger(__name__)
class MeshGenerator:
"""网格生成器 - 从PythonOCC形状生成点云"""
"""网格生成器 - 从PythonOCC形状生成点云,支持多级LOD"""
def __init__(self, quality: str = "medium"):
self.quality_settings = {
@@ -27,17 +27,14 @@ class MeshGenerator:
def generate_mesh_from_shape(self, shape, num_points: int = 20000) -> Dict:
"""从PythonOCC形状生成点云数据"""
try:
# 生成网格 - 使用更精细的网格
mesh = BRepMesh_IncrementalMesh(shape, self.quality, False, 0.5, True)
mesh.Perform()
logger.info(f"OCC网格生成完成, 网格状态: {mesh.IsDone()}")
# 提取三角形面数据
all_vertices = []
all_faces = []
vertex_offset = 0
# 遍历所有面
explorer = TopExp_Explorer(shape, TopAbs_FACE)
face_count = 0
@@ -45,7 +42,6 @@ class MeshGenerator:
face = explorer.Current()
face_count += 1
# 获取面的三角形剖分
location = TopLoc_Location()
face_triangulation = BRep_Tool.Triangulation(face, location)
@@ -54,20 +50,16 @@ class MeshGenerator:
explorer.Next()
continue
# 获取变换矩阵
trsf = location.Transformation()
# 获取顶点数量
nb_nodes = face_triangulation.NbNodes()
nb_triangles = face_triangulation.NbTriangles()
logger.info(f"面 {face_count}: {nb_nodes} 个顶点, {nb_triangles} 个三角形")
# 提取顶点并应用变换
face_vertices = []
for i in range(1, nb_nodes + 1):
pnt = face_triangulation.Node(i)
# 使用变换后的拷贝点,避免潜在的原地变换副作用
transformed = pnt.Transformed(trsf)
face_vertices.append([
float(transformed.X()),
@@ -75,15 +67,12 @@ class MeshGenerator:
float(transformed.Z()),
])
# 提取三角形索引
face_indices = []
for i in range(1, nb_triangles + 1):
tri = face_triangulation.Triangle(i)
# 三角形索引从1开始,需要转换为从0开始
idx1 = tri.Value(1)
idx2 = tri.Value(2)
idx3 = tri.Value(3)
# 转换为全局索引
face_indices.append([
vertex_offset + idx1 - 1,
vertex_offset + idx2 - 1,
@@ -105,17 +94,13 @@ class MeshGenerator:
logger.info(f"总共提取了 {len(vertices)} 个顶点, {len(faces)} 个三角形面, {face_count} 个面")
# 创建Trimesh对象
tri_mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=True)
# 根据网格大小动态调整采样点数
actual_num_points = min(num_points, len(faces) * 2)
logger.info(f"采样点数: {actual_num_points}")
# 在网格表面采样点云
points, face_idx = trimesh.sample.sample_surface(tri_mesh, actual_num_points)
# 获取法向量
normals = tri_mesh.face_normals[face_idx]
logger.info(f"生成了 {len(points)} 个点云点")
@@ -136,6 +121,108 @@ class MeshGenerator:
logger.error(traceback.format_exc())
return self._create_sample_pointcloud()
def generate_multi_lod_mesh(self, shape) -> Dict:
"""生成多级LOD网格 - 一次OCC剖分,trimesh简化,避免重复计算
返回结构:
{
"lods": {
"0": { "vertices": [...], "faces": [...], "vertex_count": N, "face_count": N },
"1": { ... 50%简化 ... },
"2": { ... 80%简化 ... }
},
"points": [...], "normals": [...], "point_count": N,
"vertex_count": N, "face_count": N
}
"""
try:
full_mesh_result = self.generate_mesh_from_shape(shape, num_points=20000)
vertices = np.array(full_mesh_result["vertices"], dtype=np.float32)
faces = np.array(full_mesh_result["faces"], dtype=np.int32)
if len(vertices) == 0 or len(faces) == 0:
sample = self._create_sample_pointcloud()
return self._wrap_sample_as_lod(sample)
tri_mesh = trimesh.Trimesh(vertices=vertices, faces=faces, process=True)
full_face_count = len(tri_mesh.faces)
logger.info(f"全精度网格: {len(tri_mesh.vertices)} 顶点, {full_face_count} 面")
lods = {
"0": self._mesh_to_lod_entry(tri_mesh, "LOD0-全精度")
}
lod_ratios = {"1": 0.50, "2": 0.20}
for lod_level, ratio in lod_ratios.items():
if full_face_count < 300:
lods[lod_level] = lods["0"]
continue
target_faces = max(int(full_face_count * ratio), 200)
try:
simplified = tri_mesh.simplify_quadric_decimation(target_faces)
if simplified is None or len(simplified.faces) < 3:
simplified = self._fast_decimate(tri_mesh, target_faces)
lods[lod_level] = self._mesh_to_lod_entry(simplified, f"LOD{lod_level}-简化{int((1-ratio)*100)}%")
logger.info(f"LOD{lod_level}: {len(simplified.vertices)} 顶点, {len(simplified.faces)} 面 (目标{target_faces})")
except Exception as dec_err:
logger.warning(f"LOD{lod_level} 简化失败,回退到全精度: {dec_err}")
lods[lod_level] = lods["0"]
result = {
"lods": lods,
"points": full_mesh_result["points"],
"normals": full_mesh_result["normals"],
"point_count": full_mesh_result["point_count"],
"vertex_count": full_mesh_result["vertex_count"],
"face_count": full_mesh_result["face_count"],
}
return result
except Exception as e:
logger.error(f"多级LOD网格生成失败: {e}")
import traceback
logger.error(traceback.format_exc())
sample = self._create_sample_pointcloud()
return self._wrap_sample_as_lod(sample)
def _mesh_to_lod_entry(self, mesh: trimesh.Trimesh, label: str) -> Dict:
return {
"vertices": mesh.vertices.tolist(),
"faces": mesh.faces.tolist(),
"vertex_count": int(len(mesh.vertices)),
"face_count": int(len(mesh.faces)),
}
def _fast_decimate(self, mesh: trimesh.Trimesh, target_faces: int) -> trimesh.Trimesh:
"""快速回退降采样:按面索引均匀采样"""
if target_faces >= len(mesh.faces):
return mesh
step = max(len(mesh.faces) // target_faces, 1)
indices = np.arange(0, len(mesh.faces), step)[:target_faces]
return mesh.submesh([np.array(indices)], only_watertight=False, append=True)
def _wrap_sample_as_lod(self, sample: Dict) -> Dict:
lods = {
"0": {
"vertices": sample["vertices"],
"faces": sample["faces"],
"vertex_count": sample["vertex_count"],
"face_count": sample["face_count"],
}
}
lods["1"] = lods["0"]
lods["2"] = lods["0"]
return {
"lods": lods,
"points": sample["points"],
"normals": sample["normals"],
"point_count": sample["point_count"],
"vertex_count": sample["vertex_count"],
"face_count": sample["face_count"],
}
def _create_sample_pointcloud(self) -> Dict:
"""创建示例点云(备用)"""
mesh = trimesh.creation.box([100, 80, 50])
+16
View File
@@ -176,6 +176,7 @@ class ProcessingService:
)
pointcloud_data = None
lod_data = None
if mesh_result:
pointcloud_data = {
"points": mesh_result.get("points", []),
@@ -183,13 +184,25 @@ class ProcessingService:
"vertices": mesh_result.get("vertices", []),
"faces": mesh_result.get("faces", []),
"point_count": mesh_result.get("point_count", 0),
"vertex_count": mesh_result.get("vertex_count", 0),
"face_count": mesh_result.get("face_count", 0),
}
# 生成多级LOD数据(用于前端按距离切换精度)
try:
lod_result = self.mesh_generator.generate_multi_lod_mesh(shape)
if lod_result and lod_result.get("lods"):
lod_data = lod_result
logger.info(f"LOD数据生成成功: {len(lod_result['lods'])} 级 (面数: {[lod_result['lods'][k]['face_count'] for k in sorted(lod_result['lods'].keys())]})")
except Exception as lod_err:
logger.warning(f"LOD数据生成失败,使用单级精度: {lod_err}")
detailed_cavity_json = await self._attach_scheme_previews(
detailed_cavity_json=detailed_cavity_json,
geometry_data=geometry_data,
stp_filename=Path(file_path).name,
pointcloud_data=pointcloud_data,
lod_data=lod_data,
)
best_scheme = CalculationService.get_best_scheme(detailed_cavity_json)
@@ -206,6 +219,7 @@ class ProcessingService:
Path(file_path).name,
cavity_data=best_cavity_data,
pointcloud_data=pointcloud_data,
lod_data=lod_data,
)
await self.storage_service.save_html_file(
@@ -405,6 +419,7 @@ class ProcessingService:
geometry_data: Dict[str, Any],
stp_filename: str,
pointcloud_data: Optional[Dict[str, Any]] = None,
lod_data: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""为每个候选分模方案生成独立HTML预览链接。"""
candidate_schemes = detailed_cavity_json.get("candidate_schemes", [])
@@ -422,6 +437,7 @@ class ProcessingService:
cavity_data=cavity_data,
pointcloud_data=pointcloud_data,
suffix=suffix,
lod_data=lod_data,
)
scheme["html_file"] = f"/html/{Path(html_path).name}"
File diff suppressed because it is too large Load Diff