优化整体任务性能

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 asyncio
import time import time
import traceback import traceback
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
from typing import Optional, Dict, Any from typing import Optional, Dict, Any
@@ -46,6 +47,7 @@ class ProcessingService:
self.multi_scheme_planner = MultiSchemeMoldPlanner() self.multi_scheme_planner = MultiSchemeMoldPlanner()
self.cad_exporter = CADExporter() self.cad_exporter = CADExporter()
self._export_shapes_cache: Dict[str, Dict[str, Dict[str, Any]]] = {} 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() stage_started = time.perf_counter()
shape = self.stp_parser.load_step_file(Path(file_path)) loop = asyncio.get_running_loop()
geometry_data = self.stp_parser.analyze_geometry(shape) 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) stage_timings["parse_stp"] = round(time.perf_counter() - stage_started, 3)
# 2. 生成网格数据并持久化 # 2. 生成网格数据并持久化
@@ -206,24 +213,21 @@ class ProcessingService:
pointcloud_data = None pointcloud_data = None
lod_data = None lod_data = None
if mesh_result: if mesh_result:
lod0 = mesh_result.get("lods", {}).get("0", {})
pointcloud_data = { pointcloud_data = {
"points": mesh_result.get("points", []), "points": mesh_result.get("points", []),
"normals": mesh_result.get("normals", []), "normals": mesh_result.get("normals", []),
"vertices": mesh_result.get("vertices", []), "vertices": lod0.get("vertices", []),
"faces": mesh_result.get("faces", []), "faces": lod0.get("faces", []),
"point_count": mesh_result.get("point_count", 0), "point_count": mesh_result.get("point_count", 0),
"vertex_count": mesh_result.get("vertex_count", 0), "vertex_count": mesh_result.get("vertex_count", 0),
"face_count": mesh_result.get("face_count", 0), "face_count": mesh_result.get("face_count", 0),
} }
# 生成多级LOD数据(用于前端按距离切换精度) if mesh_result and mesh_result.get("lods"):
try: lods = mesh_result["lods"]
lod_result = self.mesh_generator.generate_multi_lod_mesh(shape) lod_data = mesh_result
if lod_result and lod_result.get("lods"): logger.info(f"LOD数据复用成功: {len(lods)} 级 (面数: {[lods[k]['face_count'] for k in sorted(lods.keys())]})")
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 = await self._attach_scheme_previews(
detailed_cavity_json=detailed_cavity_json, detailed_cavity_json=detailed_cavity_json,
@@ -260,10 +264,14 @@ class ProcessingService:
# 9. 分析模具设计 # 9. 分析模具设计
stage_started = time.perf_counter() stage_started = time.perf_counter()
analysis_result = self.geometry_analyzer.analyze_mold_design( loop = asyncio.get_running_loop()
geometry_data, analysis_result = await loop.run_in_executor(
product_material=requested_material, self._occ_executor,
shape=shape, lambda: self.geometry_analyzer.analyze_mold_design(
geometry_data,
product_material=requested_material,
shape=shape,
),
) )
if analysis_result: if analysis_result:
@@ -328,15 +336,14 @@ class ProcessingService:
}, },
) )
# 更新任务缓存状态 # 更新任务缓存状态(仅保留轻量摘要,完整数据由PG+RustFS持久化)
await redis_task_manager.update_task(task_id, { await redis_task_manager.update_task(task_id, {
"status": ProcessingStatus.COMPLETED,
"completed_at": str(datetime.now()),
"geometry_data": geometry_data, "geometry_data": geometry_data,
"analysis_result": analysis_result, "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, "key_info": best_key_info,
"best_scheme_id": detailed_cavity_json.get("best_scheme_id"),
"material": requested_material, "material": requested_material,
"parameters": process_params, "parameters": process_params,
"stage_timings": stage_timings, "stage_timings": stage_timings,
@@ -344,8 +351,6 @@ class ProcessingService:
"verification": verification_result, "verification": verification_result,
"llm_report": llm_report, "llm_report": llm_report,
"export_artifacts": export_artifacts, "export_artifacts": export_artifacts,
"status": ProcessingStatus.COMPLETED,
"completed_at": str(datetime.now()),
}) })
logger.info(f"模具型腔生成完成: {task_id}") logger.info(f"模具型腔生成完成: {task_id}")
@@ -375,18 +380,22 @@ class ProcessingService:
self, shape, geometry_data: dict, file_path: str, self, shape, geometry_data: dict, file_path: str,
db_session: AsyncSession, stp_file_id: int, task_id: str, db_session: AsyncSession, stp_file_id: int, task_id: str,
) -> Optional[Dict[str, Any]]: ) -> Optional[Dict[str, Any]]:
"""生成网格数据并持久化,失败不影响主流程""" """生成多级LOD网格并持久化,一次OCC剖分+trimesh简化,失败不影响主流程"""
mesh_result = None mesh_result = None
try: 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", []) lod0 = mesh_result.get("lods", {}).get("0", {})
faces = mesh_result.get("faces", []) vertices = lod0.get("vertices", [])
faces = lod0.get("faces", [])
points = mesh_result.get("points", []) points = mesh_result.get("points", [])
normals = mesh_result.get("normals", []) normals = mesh_result.get("normals", [])
point_count = mesh_result.get("point_count", 0) point_count = mesh_result.get("point_count", 0)
vertex_count = mesh_result.get("vertex_count", 0) vertex_count = lod0.get("vertex_count", mesh_result.get("vertex_count", 0))
face_count = mesh_result.get("face_count", 0) face_count = lod0.get("face_count", mesh_result.get("face_count", 0))
if vertices and faces: if vertices and faces:
bbox = geometry_data.get("bounding_box", {}) bbox = geometry_data.get("bounding_box", {})
@@ -438,11 +447,15 @@ class ProcessingService:
plan_result = None plan_result = None
try: try:
if shape: if shape:
plan_result = self.multi_scheme_planner.generate_plan( loop = asyncio.get_running_loop()
shape=shape, plan_result = await loop.run_in_executor(
material=selected_material, self._occ_executor,
is_foam_material=is_foam_material, lambda: self.multi_scheme_planner.generate_plan(
process_params=process_params, shape=shape,
material=selected_material,
is_foam_material=is_foam_material,
process_params=process_params,
),
) )
logger.info( logger.info(
f"多方案分模完成: 生成 {len(plan_result.get('candidate_schemes', []))} 套方案" f"多方案分模完成: 生成 {len(plan_result.get('candidate_schemes', []))} 套方案"
+5 -3
View File
@@ -26,11 +26,13 @@ class TaskQueryService:
Returns: Returns:
任务视图字典,如果任务不存在返回 None 任务视图字典,如果任务不存在返回 None
""" """
# 1. Redis/内存任务(进行中的任务) # 1. Redis/内存任务(进行中的任务直接返回,已完成/失败的走DB路径获取完整数据)
task = await redis_task_manager.get_task(task_id) task = await redis_task_manager.get_task(task_id)
if task: if task:
logger.info(f"返回缓存任务状态:{task_id} - {task.get('status')}") status = task.get("status")
return task if status and status not in ("completed", "failed"):
logger.info(f"返回缓存任务状态:{task_id} - {status}")
return task
# 2. 持久化任务(已完成/失败,或服务重启后的任务) # 2. 持久化任务(已完成/失败,或服务重启后的任务)
storage_service = StorageIntegrationService() storage_service = StorageIntegrationService()
+87 -5
View File
@@ -35,8 +35,8 @@ class HTMLGenerator:
self.output_dir = Path(output_dir) self.output_dir = Path(output_dir)
self.output_dir.mkdir(exist_ok=True) self.output_dir.mkdir(exist_ok=True)
def generate_3d_viewer_html(self, stp_filename: str, data_filename: str) -> str: def generate_3d_viewer_html(self, stp_filename: str, data_filename: str, summary_filename: str = "") -> str:
"""生成3D可视化HTML页面 — 通过fetch异步加载companion JSON数据""" """生成3D可视化HTML页面 — 先加载摘要秒显信息面板,再加载网格数据"""
cavity_html = self._build_cavity_info_panel_template() cavity_html = self._build_cavity_info_panel_template()
@@ -182,6 +182,7 @@ class HTMLGenerator:
import * as THREE from 'three'; import * as THREE from 'three';
import {{ OrbitControls }} from 'three/addons/controls/OrbitControls.js'; import {{ OrbitControls }} from 'three/addons/controls/OrbitControls.js';
const SUMMARY_URL = '{summary_filename}';
const DATA_URL = '{data_filename}'; const DATA_URL = '{data_filename}';
let productMesh, cavityMesh, coreMesh, partingMesh, pointcloudMesh; 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() {{ async function loadData() {{
const statusEl = document.getElementById('loading-status'); const statusEl = document.getElementById('loading-status');
try {{ 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); const resp = await fetch(DATA_URL);
if (!resp.ok) throw new Error(`HTTP ${{resp.status}}`); if (!resp.ok) throw new Error(`HTTP ${{resp.status}}`);
const data = await resp.json(); const data = await resp.json();
@@ -775,6 +814,45 @@ class HTMLGenerator:
} }
return data 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: def save_html_file(self, html_content: str, filename: str) -> str:
"""保存HTML文件到磁盘""" """保存HTML文件到磁盘"""
try: try:
@@ -806,7 +884,7 @@ class HTMLGenerator:
suffix: Optional[str] = None, suffix: Optional[str] = None,
lod_data: Optional[Dict[str, Any]] = None, lod_data: Optional[Dict[str, Any]] = None,
) -> str: ) -> str:
"""生成并保存可视化HTML + companion JSON数据文件。返回HTML文件路径(向后兼容)""" """生成并保存可视化HTML + 摘要JSON + 完整数据JSON。返回HTML文件路径(向后兼容)"""
try: try:
base_stem = Path(stp_filename).stem.replace(" ", "_") base_stem = Path(stp_filename).stem.replace(" ", "_")
ts = datetime.now().strftime("%Y%m%d_%H%M%S") 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}" base_name = f"mold_{base_stem}{suffix_part}_{ts}"
html_filename = f"{base_name}.html" html_filename = f"{base_name}.html"
summary_filename = f"{base_name}_summary.json"
data_filename = f"{base_name}_data.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( data_content = self.generate_3d_viewer_data(
geometry_data, cavity_data, pointcloud_data, lod_data=lod_data geometry_data, cavity_data, pointcloud_data, lod_data=lod_data
) )
self.save_data_file(data_content, data_filename) 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) html_file_path = self.save_html_file(html_content, html_filename)
return html_file_path return html_file_path