diff --git a/frontend/src/modules/moldinsight/ResultView.vue b/frontend/src/modules/moldinsight/ResultView.vue index fab638c..ec61667 100644 --- a/frontend/src/modules/moldinsight/ResultView.vue +++ b/frontend/src/modules/moldinsight/ResultView.vue @@ -212,6 +212,27 @@
+
+

规则检测摘要

+
+
+ 检测状态 + {{ ruleActionSummary.total_mechanism_count > 0 ? '检测到倒扣' : '未检测到倒扣' }} +
+
+ 滑块数量 + {{ ruleActionSummary.total_slider_count || 0 }} +
+
+ 斜顶数量 + {{ ruleActionSummary.total_lifter_count || 0 }} +
+
+ 复杂度 + {{ ruleActionSummary.complexity || '未知' }} +
+
+

AI 结论

@@ -815,6 +836,16 @@ const sideActionAiAdvice = computed(() => { ) || fallbackSideActionAiAdvice.value }) +const ruleActionSummary = computed(() => { + const summary = selectedScheme.value?.side_actions?.summary || {} + return { + total_slider_count: summary.total_slider_count ?? 0, + total_lifter_count: summary.total_lifter_count ?? 0, + total_mechanism_count: summary.total_mechanism_count ?? 0, + complexity: summary.complexity || '未知', + } +}) + const designReport = computed(() => parseEmbeddedDesignReport(state.task?.llm_report || '') ) diff --git a/src/moldinsight/api/advanced_router.py b/src/moldinsight/api/advanced_router.py index 82e170d..395e852 100644 --- a/src/moldinsight/api/advanced_router.py +++ b/src/moldinsight/api/advanced_router.py @@ -24,42 +24,49 @@ router = APIRouter() cad_exporter = CADExporter() storage_service = StorageIntegrationService() -_api_routes_cache = {} +_cached_instances = {} -def _get_cached(key): - global _api_routes_cache - if key not in _api_routes_cache: - try: - from api import routes - except Exception as e: - logger.warning(f"api.routes 模块加载失败: {e}") - _api_routes_cache["__error__"] = str(e) +def _get_cached_import(key: str): + """惰性导入核心模块,避免路由器模块级加载时的循环依赖。""" + if key in _cached_instances: + return _cached_instances[key] + try: + if key == "side_action_designer": + from moldinsight.core.side_action_designer import SideActionDesigner + instance = SideActionDesigner() + elif key == "cavity_layout_optimizer": + from moldinsight.core.cavity_layout_optimizer import CavityLayoutOptimizer + instance = CavityLayoutOptimizer() + elif key == "mold_system_designer": + from moldinsight.core.mold_system_designer import MoldSystemDesigner + instance = MoldSystemDesigner() + elif key == "mold_cam_designer": + from moldinsight.core.mold_cam import MoldCAMDesigner + instance = MoldCAMDesigner() + elif key == "collision_detector": + from moldinsight.core.mold_machining import CollisionDetector + instance = CollisionDetector() + elif key == "toolpath_optimizer": + from moldinsight.core.mold_machining import ToolpathOptimizer + instance = ToolpathOptimizer() + elif key == "edm_designer": + from moldinsight.core.mold_machining import EDMElectrodeDesigner + instance = EDMElectrodeDesigner() + elif key == "machining_simulator": + from moldinsight.core.mold_machining import MachiningSimulator + instance = MachiningSimulator() + else: return None - _api_routes_cache.clear() - _api_routes_cache.update({ - "tasks": routes.tasks, - "cavity_layout_optimizer": routes.cavity_layout_optimizer, - "mold_system_designer": routes.mold_system_designer, - "side_action_designer": routes.side_action_designer, - "mold_cam_designer": routes.mold_cam_designer, - "collision_detector": routes.collision_detector, - "toolpath_optimizer": routes.toolpath_optimizer, - "edm_designer": routes.edm_designer, - "machining_simulator": routes.machining_simulator, - "cad_exporter": routes.cad_exporter, - }) - return _api_routes_cache.get(key) + _cached_instances[key] = instance + return instance + except Exception as e: + logger.warning(f"核心模块 {key} 加载失败: {e}") + return None async def _get_task_data(task_id: str) -> dict: - task = await redis_task_manager.get_task(task_id) - if task: - return task - tasks = _get_cached("tasks") - if tasks and task_id in tasks: - return tasks[task_id] - return None + return await redis_task_manager.get_task(task_id) async def _ensure_task_access( @@ -193,7 +200,7 @@ async def optimize_cavity_layout( layout_type = body.get("layout_type", "auto") if cavity_count < 1 or cavity_count > 64: raise HTTPException(400, "型腔数量必须在 1-64 之间") - optimizer = _get_cached("cavity_layout_optimizer") + optimizer = _get_cached_import("cavity_layout_optimizer") if not optimizer: raise HTTPException(503, "服务不可用:核心模块未加载") result = optimizer.optimize_layout( @@ -260,7 +267,7 @@ async def design_complete_mold_system( gate_type = body.get("gate_type", "auto") cycle_time_target = body.get("cycle_time_target") layout_positions = body.get("layout_positions") - ds = _get_cached("mold_system_designer") + ds = _get_cached_import("mold_system_designer") if not ds: raise HTTPException(503, "服务不可用:核心模块未加载") result = ds.design_complete_system( @@ -307,7 +314,7 @@ async def detect_undercuts( task_data = await _get_task_data(task_id) if not task_data: raise HTTPException(404, "任务不存在") - sd = _get_cached("side_action_designer") + sd = _get_cached_import("side_action_designer") if not sd: raise HTTPException(503, "服务不可用:核心模块未加载") result = sd.analyze_and_design( @@ -327,7 +334,7 @@ async def design_mold_cam( mold_steel = body.get("mold_steel", "P20") surface_quality = body.get("surface_quality", "standard") controller = body.get("controller", "fanuc") - cam = _get_cached("mold_cam_designer") + cam = _get_cached_import("mold_cam_designer") if not cam: raise HTTPException(503, "服务不可用:核心模块未加载") result = cam.design_mold_cam( @@ -348,7 +355,7 @@ async def check_toolpath_collision( tool = body.get("tool", {"diameter": 10, "flute_length": 30, "shank_diameter": 10}) stock_bbox = body.get("stock_bbox", {"min": [-50, -50, -25], "max": [50, 50, 25]}) clamp_positions = body.get("clamp_positions") - cd = _get_cached("collision_detector") + cd = _get_cached_import("collision_detector") if not cd: raise HTTPException(503, "服务不可用:核心模块未加载") result = cd.check_toolpath_safety(toolpath_points, tool, stock_bbox, clamp_positions) @@ -364,7 +371,7 @@ async def optimize_toolpath( toolpath_points = body.get("toolpath_points", [[0, 0, 50], [10, 10, -5], [20, 20, -10]]) cutting_params = body.get("cutting_params", {"feed_rate_mm_min": 500}) stock_bbox = body.get("stock_bbox") - to = _get_cached("toolpath_optimizer") + to = _get_cached_import("toolpath_optimizer") if not to: raise HTTPException(503, "服务不可用:核心模块未加载") result = to.optimize_toolpath(toolpath_points, cutting_params, stock_bbox) @@ -382,7 +389,7 @@ async def design_edm_electrodes( material = body.get("material", "copper") spark_gap = body.get("spark_gap", 0.05) overburn = body.get("overburn", 0.1) - ed = _get_cached("edm_designer") + ed = _get_cached_import("edm_designer") if not ed: raise HTTPException(503, "服务不可用:核心模块未加载") result = ed.design_electrodes(undercut_regions, cavity_bbox, material, spark_gap, overburn) @@ -398,7 +405,7 @@ async def simulate_machining( operations = body.get("operations", [{"strategy": "z_level_roughing", "levels": [{"z": -5}]}]) stock_bbox = body.get("stock_bbox", {"dimensions": [100, 100, 50], "min": [-50, -50, -25], "max": [50, 50, 25]}) resolution = body.get("resolution", 2.0) - ms = _get_cached("machining_simulator") + ms = _get_cached_import("machining_simulator") if not ms: raise HTTPException(503, "服务不可用:核心模块未加载") result = ms.simulate_machining(operations, stock_bbox, resolution) diff --git a/src/moldinsight/core/mold_generator.py b/src/moldinsight/core/mold_generator.py index 1c1dfb4..d153413 100644 --- a/src/moldinsight/core/mold_generator.py +++ b/src/moldinsight/core/mold_generator.py @@ -140,8 +140,11 @@ class MoldCavityGenerator(BaseMoldGenerator): }, "parting_surface": parting_geometry, "quality_checks": { - "undercut_regions": cavity_data.get("undercut_regions", []), - "side_actions": cavity_data.get("side_actions", {}), + "undercut_summary": { + "total_faces": len(cavity_data.get("undercut_regions", [])), + "has_undercut": len(cavity_data.get("undercut_regions", [])) > 0, + }, + "side_action_summary": cavity_data.get("side_actions", {}).get("summary", {}), }, "manufacturing_info": { "estimated_mold_size": self._calculate_mold_size(analysis), diff --git a/src/moldinsight/core/side_action_designer.py b/src/moldinsight/core/side_action_designer.py index c34807b..61b29d7 100644 --- a/src/moldinsight/core/side_action_designer.py +++ b/src/moldinsight/core/side_action_designer.py @@ -388,8 +388,24 @@ class LifterMechanismDesigner: } def _calculate_lifter_angle(self, region: Dict) -> float: - """计算斜顶角度(通常5-15度)""" - return 8.0 + """基于倒扣深度和估算顶出行程计算斜顶角度(通常 5-15 度)。 + + angle = atan(undercut_depth / estimated_stroke) + """ + bbox = region.get("bbox", {}) + if "max" in bbox and "min" in bbox: + lateral = max( + abs(bbox["max"][0] - bbox["min"][0]), + abs(bbox["max"][1] - bbox["min"][1]), + abs(bbox["max"][2] - bbox["min"][2]), + ) + else: + lateral = 5.0 + + undercut_depth = lateral * 0.4 + estimated_stroke = max(undercut_depth * 3, 20.0) + angle = math.degrees(math.atan(undercut_depth / estimated_stroke)) + return round(max(min(angle, 15.0), 5.0), 1) def _calculate_lifter_stroke(self, region: Dict) -> float: """计算斜顶行程""" diff --git a/src/moldinsight/services/calculation_service.py b/src/moldinsight/services/calculation_service.py index 16f3b05..5f4e5b4 100644 --- a/src/moldinsight/services/calculation_service.py +++ b/src/moldinsight/services/calculation_service.py @@ -272,13 +272,13 @@ class CalculationService: if quality_checks: detailed_cavity_json["quality_checks"] = quality_checks - undercut_regions = quality_checks.get("undercut_regions") - if undercut_regions: - detailed_cavity_json["undercut_regions"] = undercut_regions + undercut_summary = quality_checks.get("undercut_summary") + if undercut_summary: + detailed_cavity_json["undercut_regions"] = [] # 详情在 scheme 级别 - side_actions = quality_checks.get("side_actions") - if side_actions: - detailed_cavity_json["side_actions"] = side_actions + side_action_summary = quality_checks.get("side_action_summary") + if side_action_summary: + detailed_cavity_json["side_actions"] = {"summary": side_action_summary} # 添加型腔关键信息 detailed_cavity_json["mold_cavities"]["cavity_key_info"] = { diff --git a/src/moldinsight/services/llm_service.py b/src/moldinsight/services/llm_service.py index d5a9648..2957bec 100644 --- a/src/moldinsight/services/llm_service.py +++ b/src/moldinsight/services/llm_service.py @@ -336,7 +336,7 @@ class LLMService: volume=f"{analysis_result.get('geometry_data', {}).get('volume', 0):.1f} mm³", surface_area=f"{analysis_result.get('geometry_data', {}).get('surface_area', 0):.1f} mm²", bbox=json.dumps(analysis_result.get("geometry_data", {}).get("bounding_box", {}), ensure_ascii=False), - features=features or "无特征检测数据", + features=trimmed or "无特征检测数据", quality_metrics=json.dumps(analysis_result.get("quality_metrics", {}), ensure_ascii=False, indent=2), schemes=schemes_text or "无分模方案数据", mold_material=mfg.get("mold_material", "自动选择"), @@ -350,13 +350,12 @@ class LLMService: ) def _build_side_action_prompt(self, analysis_result, detailed_cavity_json) -> str: - features = json.dumps( - analysis_result.get("detected_features", []), - ensure_ascii=False, - indent=2, + features = self._prioritize_features_for_side_action( + analysis_result.get("detected_features", []) ) - if len(features) > 2500: - features = features[:2500] + "\n... (已截断)" + trimmed = json.dumps(features, ensure_ascii=False, indent=2) + if len(trimmed) > 2500: + trimmed = trimmed[:2500] + "\n... (已截断)" best_scheme = {} if detailed_cavity_json: @@ -449,6 +448,27 @@ class LLMService: content = resp.json()["choices"][0]["message"]["content"] return content.strip() if content else None + @staticmethod + def _prioritize_features_for_side_action( + features: List[Dict[str, Any]], + ) -> List[Dict[str, Any]]: + """按倒扣/抽芯相关性排序,优先保留关键特征,避免 prompt 截断丢失重要信息。 + + 优先级:draft_angle > high_curvature > thin_wall/thick_wall/wall_non_uniform > 其它 + """ + high_priority = {"draft_angle", "high_curvature"} + medium_priority = {"thin_wall", "thick_wall", "wall_non_uniform", "undercut"} + high, medium, rest = [], [], [] + for f in features: + ft = f.get("feature_type", "") + if ft in high_priority: + high.append(f) + elif ft in medium_priority: + medium.append(f) + else: + rest.append(f) + return high + medium + rest + @staticmethod def _parse_json_response(raw): try: diff --git a/src/moldinsight/services/processing_service.py b/src/moldinsight/services/processing_service.py index 8028d2d..9ffb2cb 100644 --- a/src/moldinsight/services/processing_service.py +++ b/src/moldinsight/services/processing_service.py @@ -47,7 +47,8 @@ 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") + # OCC 非线程安全,max_workers=1 保证所有 OCC 操作序列化执行,避免偶发崩溃 + self._occ_executor = ThreadPoolExecutor(max_workers=1, thread_name_prefix="occ") # ─── 对外入口 ───