UI升级改动
This commit is contained in:
@@ -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
@@ -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])
|
||||
|
||||
@@ -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}"
|
||||
|
||||
|
||||
+561
-530
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user