优化整体任务性能

This commit is contained in:
2026-05-28 17:41:02 +08:00
parent e03490279e
commit 6ad22e4466
3 changed files with 139 additions and 42 deletions
+47 -34
View File
@@ -4,6 +4,7 @@
import asyncio
import time
import traceback
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from pathlib import Path
from typing import Optional, Dict, Any
@@ -46,6 +47,7 @@ class ProcessingService:
self.multi_scheme_planner = MultiSchemeMoldPlanner()
self.cad_exporter = CADExporter()
self._export_shapes_cache: Dict[str, Dict[str, Dict[str, Any]]] = {}
self._occ_executor = ThreadPoolExecutor(max_workers=2, thread_name_prefix="occ")
# ─── 对外入口 ───
@@ -115,8 +117,13 @@ class ProcessingService:
)
stage_started = time.perf_counter()
shape = self.stp_parser.load_step_file(Path(file_path))
geometry_data = self.stp_parser.analyze_geometry(shape)
loop = asyncio.get_running_loop()
shape = await loop.run_in_executor(
self._occ_executor, self.stp_parser.load_step_file, Path(file_path)
)
geometry_data = await loop.run_in_executor(
self._occ_executor, self.stp_parser.analyze_geometry, shape
)
stage_timings["parse_stp"] = round(time.perf_counter() - stage_started, 3)
# 2. 生成网格数据并持久化
@@ -206,24 +213,21 @@ class ProcessingService:
pointcloud_data = None
lod_data = None
if mesh_result:
lod0 = mesh_result.get("lods", {}).get("0", {})
pointcloud_data = {
"points": mesh_result.get("points", []),
"normals": mesh_result.get("normals", []),
"vertices": mesh_result.get("vertices", []),
"faces": mesh_result.get("faces", []),
"vertices": lod0.get("vertices", []),
"faces": lod0.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}")
if mesh_result and mesh_result.get("lods"):
lods = mesh_result["lods"]
lod_data = mesh_result
logger.info(f"LOD数据复用成功: {len(lods)} 级 (面数: {[lods[k]['face_count'] for k in sorted(lods.keys())]})")
detailed_cavity_json = await self._attach_scheme_previews(
detailed_cavity_json=detailed_cavity_json,
@@ -260,10 +264,14 @@ class ProcessingService:
# 9. 分析模具设计
stage_started = time.perf_counter()
analysis_result = self.geometry_analyzer.analyze_mold_design(
geometry_data,
product_material=requested_material,
shape=shape,
loop = asyncio.get_running_loop()
analysis_result = await loop.run_in_executor(
self._occ_executor,
lambda: self.geometry_analyzer.analyze_mold_design(
geometry_data,
product_material=requested_material,
shape=shape,
),
)
if analysis_result:
@@ -328,15 +336,14 @@ class ProcessingService:
},
)
# 更新任务缓存状态
# 更新任务缓存状态(仅保留轻量摘要,完整数据由PG+RustFS持久化)
await redis_task_manager.update_task(task_id, {
"status": ProcessingStatus.COMPLETED,
"completed_at": str(datetime.now()),
"geometry_data": geometry_data,
"analysis_result": analysis_result,
"plan_result": detailed_cavity_json,
"candidate_schemes": detailed_cavity_json.get("candidate_schemes", []),
"best_scheme_id": detailed_cavity_json.get("best_scheme_id"),
"cavity_data": best_cavity_data,
"key_info": best_key_info,
"best_scheme_id": detailed_cavity_json.get("best_scheme_id"),
"material": requested_material,
"parameters": process_params,
"stage_timings": stage_timings,
@@ -344,8 +351,6 @@ class ProcessingService:
"verification": verification_result,
"llm_report": llm_report,
"export_artifacts": export_artifacts,
"status": ProcessingStatus.COMPLETED,
"completed_at": str(datetime.now()),
})
logger.info(f"模具型腔生成完成: {task_id}")
@@ -375,18 +380,22 @@ class ProcessingService:
self, shape, geometry_data: dict, file_path: str,
db_session: AsyncSession, stp_file_id: int, task_id: str,
) -> Optional[Dict[str, Any]]:
"""生成网格数据并持久化,失败不影响主流程"""
"""生成多级LOD网格并持久化,一次OCC剖分+trimesh简化,失败不影响主流程"""
mesh_result = None
try:
mesh_result = self.mesh_generator.generate_mesh_from_shape(shape)
loop = asyncio.get_running_loop()
mesh_result = await loop.run_in_executor(
self._occ_executor, self.mesh_generator.generate_multi_lod_mesh, shape
)
vertices = mesh_result.get("vertices", [])
faces = mesh_result.get("faces", [])
lod0 = mesh_result.get("lods", {}).get("0", {})
vertices = lod0.get("vertices", [])
faces = lod0.get("faces", [])
points = mesh_result.get("points", [])
normals = mesh_result.get("normals", [])
point_count = mesh_result.get("point_count", 0)
vertex_count = mesh_result.get("vertex_count", 0)
face_count = mesh_result.get("face_count", 0)
vertex_count = lod0.get("vertex_count", mesh_result.get("vertex_count", 0))
face_count = lod0.get("face_count", mesh_result.get("face_count", 0))
if vertices and faces:
bbox = geometry_data.get("bounding_box", {})
@@ -438,11 +447,15 @@ class ProcessingService:
plan_result = None
try:
if shape:
plan_result = self.multi_scheme_planner.generate_plan(
shape=shape,
material=selected_material,
is_foam_material=is_foam_material,
process_params=process_params,
loop = asyncio.get_running_loop()
plan_result = await loop.run_in_executor(
self._occ_executor,
lambda: self.multi_scheme_planner.generate_plan(
shape=shape,
material=selected_material,
is_foam_material=is_foam_material,
process_params=process_params,
),
)
logger.info(
f"多方案分模完成: 生成 {len(plan_result.get('candidate_schemes', []))} 套方案"
+5 -3
View File
@@ -26,11 +26,13 @@ class TaskQueryService:
Returns:
任务视图字典,如果任务不存在返回 None
"""
# 1. Redis/内存任务(进行中的任务)
# 1. Redis/内存任务(进行中的任务直接返回,已完成/失败的走DB路径获取完整数据)
task = await redis_task_manager.get_task(task_id)
if task:
logger.info(f"返回缓存任务状态:{task_id} - {task.get('status')}")
return task
status = task.get("status")
if status and status not in ("completed", "failed"):
logger.info(f"返回缓存任务状态:{task_id} - {status}")
return task
# 2. 持久化任务(已完成/失败,或服务重启后的任务)
storage_service = StorageIntegrationService()
+87 -5
View File
@@ -35,8 +35,8 @@ class HTMLGenerator:
self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True)
def generate_3d_viewer_html(self, stp_filename: str, data_filename: str) -> str:
"""生成3D可视化HTML页面 — 通过fetch异步加载companion JSON数据"""
def generate_3d_viewer_html(self, stp_filename: str, data_filename: str, summary_filename: str = "") -> str:
"""生成3D可视化HTML页面 — 先加载摘要秒显信息面板,再加载网格数据"""
cavity_html = self._build_cavity_info_panel_template()
@@ -182,6 +182,7 @@ class HTMLGenerator:
import * as THREE from 'three';
import {{ OrbitControls }} from 'three/addons/controls/OrbitControls.js';
const SUMMARY_URL = '{summary_filename}';
const DATA_URL = '{data_filename}';
let productMesh, cavityMesh, coreMesh, partingMesh, pointcloudMesh;
@@ -589,10 +590,48 @@ class HTMLGenerator:
}}
}}
function updateSummaryPanels(summary) {{
const cd = summary.cavity || summary;
const verts = cd?.mold_cavities?.cavity?.vertex_count || '-';
const faces = cd?.mold_cavities?.cavity?.face_count || '-';
const vol = cd?.mold_cavities?.cavity_key_info?.geometric_characteristics?.product_volume || '-';
document.getElementById('info-verts').textContent = typeof verts === 'number' ? verts.toLocaleString() : verts;
document.getElementById('info-faces').textContent = typeof faces === 'number' ? faces.toLocaleString() : faces;
document.getElementById('info-vol').textContent = vol;
const panel = document.getElementById('cavity-info-panel');
if (panel) {{
panel.style.display = 'block';
const meta = cd.metadata || {{}};
const mfg = cd.manufacturing_info || {{}};
const geo = cd.mold_cavities?.cavity_key_info?.geometric_characteristics || {{}};
const setVal = (id, val) => {{ const el = document.getElementById(id); if (el) el.textContent = val || 'N/A'; }};
setVal('cp-shrink', meta.shrinkage_rate);
setVal('cp-draft', meta.draft_angle != null ? meta.draft_angle + '°' : null);
setVal('cp-parting', mfg.parting_line_length);
setVal('cp-vol', geo.product_volume);
setVal('cp-weight', geo.product_weight);
setVal('cp-wall', geo.wall_thickness_range);
setVal('cp-material', mfg.mold_material);
setVal('cp-hardness', mfg.mold_hardness);
setVal('cp-finish', mfg.surface_finish);
setVal('cp-cycle', mfg.estimated_cycle_time);
}}
}}
async function loadData() {{
const statusEl = document.getElementById('loading-status');
try {{
statusEl.textContent = '正在加载几何数据...';
if (SUMMARY_URL) {{
statusEl.textContent = '加载摘要...';
const summaryResp = await fetch(SUMMARY_URL);
if (summaryResp.ok) {{
const summary = await summaryResp.json();
updateSummaryPanels(summary);
}}
}}
statusEl.textContent = '加载几何数据...';
const resp = await fetch(DATA_URL);
if (!resp.ok) throw new Error(`HTTP ${{resp.status}}`);
const data = await resp.json();
@@ -775,6 +814,45 @@ class HTMLGenerator:
}
return data
@staticmethod
def _strip_heavy_geometry(cavity_data: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
"""从型腔数据中移除顶点/面数组,仅保留元数据和统计信息"""
if not cavity_data:
return None
light: Dict[str, Any] = {}
for key in ("metadata", "manufacturing_info", "scheme_id", "best_scheme_id", "cavity_key_info"):
if key in cavity_data:
light[key] = cavity_data[key]
mc = cavity_data.get("mold_cavities", {})
if mc:
light_mc: Dict[str, Any] = {}
if "cavity_key_info" in mc:
light_mc["cavity_key_info"] = mc["cavity_key_info"]
for part_name in ("cavity", "core"):
part = mc.get(part_name, {})
if part:
light_mc[part_name] = {
"vertex_count": part.get("vertex_count", 0),
"face_count": part.get("face_count", 0),
}
light["mold_cavities"] = light_mc
return light
def generate_3d_viewer_summary(
self,
geometry_data: Dict[str, Any],
cavity_data: Optional[Dict[str, Any]] = None,
) -> Dict[str, Any]:
"""生成轻量摘要JSON — 含几何摘要和型腔元数据,不含网格顶点数据"""
return {
"version": "4.1.0",
"generated_at": datetime.now().isoformat(),
"geometry": geometry_data,
"cavity": self._strip_heavy_geometry(cavity_data),
}
def save_html_file(self, html_content: str, filename: str) -> str:
"""保存HTML文件到磁盘"""
try:
@@ -806,7 +884,7 @@ class HTMLGenerator:
suffix: Optional[str] = None,
lod_data: Optional[Dict[str, Any]] = None,
) -> str:
"""生成并保存可视化HTML + companion JSON数据文件。返回HTML文件路径(向后兼容)"""
"""生成并保存可视化HTML + 摘要JSON + 完整数据JSON。返回HTML文件路径(向后兼容)"""
try:
base_stem = Path(stp_filename).stem.replace(" ", "_")
ts = datetime.now().strftime("%Y%m%d_%H%M%S")
@@ -814,14 +892,18 @@ class HTMLGenerator:
base_name = f"mold_{base_stem}{suffix_part}_{ts}"
html_filename = f"{base_name}.html"
summary_filename = f"{base_name}_summary.json"
data_filename = f"{base_name}_data.json"
summary_content = self.generate_3d_viewer_summary(geometry_data, cavity_data)
self.save_data_file(summary_content, summary_filename)
data_content = self.generate_3d_viewer_data(
geometry_data, cavity_data, pointcloud_data, lod_data=lod_data
)
self.save_data_file(data_content, data_filename)
html_content = self.generate_3d_viewer_html(stp_filename, data_filename)
html_content = self.generate_3d_viewer_html(stp_filename, data_filename, summary_filename)
html_file_path = self.save_html_file(html_content, html_filename)
return html_file_path